mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2024-12-12 10:04:29 +02:00
9f4525847f
* Convert XConfigure into constructors Previously, we discussed the possibility of converting the config types into internal ones. But due to the cyclic dependencies it introduces, we are only converting XConfigure into constructors and document that XConfig types are most likely are not going to be directly used by developers. In package documents, constructors will be nicely listed under the config types and they won't be yet another standalone symbol developers need to learn about. Fixes #1130. * Add the changes to the CHANGELOG
441 lines
11 KiB
Go
441 lines
11 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package trace
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"sync"
|
|
"time"
|
|
|
|
"go.opentelemetry.io/otel/api/global"
|
|
apitrace "go.opentelemetry.io/otel/api/trace"
|
|
"go.opentelemetry.io/otel/codes"
|
|
"go.opentelemetry.io/otel/label"
|
|
export "go.opentelemetry.io/otel/sdk/export/trace"
|
|
"go.opentelemetry.io/otel/sdk/internal"
|
|
)
|
|
|
|
const (
|
|
errorTypeKey = label.Key("error.type")
|
|
errorMessageKey = label.Key("error.message")
|
|
errorEventName = "error"
|
|
)
|
|
|
|
// span implements apitrace.Span interface.
|
|
type span struct {
|
|
// data contains information recorded about the span.
|
|
//
|
|
// It will be non-nil if we are exporting the span or recording events for it.
|
|
// Otherwise, data is nil, and the span is simply a carrier for the
|
|
// SpanContext, so that the trace ID is propagated.
|
|
data *export.SpanData
|
|
mu sync.Mutex // protects the contents of *data (but not the pointer value.)
|
|
spanContext apitrace.SpanContext
|
|
|
|
// attributes are capped at configured limit. When the capacity is reached an oldest entry
|
|
// is removed to create room for a new entry.
|
|
attributes *attributesMap
|
|
|
|
// messageEvents are stored in FIFO queue capped by configured limit.
|
|
messageEvents *evictedQueue
|
|
|
|
// links are stored in FIFO queue capped by configured limit.
|
|
links *evictedQueue
|
|
|
|
// spanStore is the spanStore this span belongs to, if any, otherwise it is nil.
|
|
//*spanStore
|
|
endOnce sync.Once
|
|
|
|
executionTracerTaskEnd func() // ends the execution tracer span
|
|
tracer *tracer // tracer used to create span.
|
|
}
|
|
|
|
var _ apitrace.Span = &span{}
|
|
|
|
func (s *span) SpanContext() apitrace.SpanContext {
|
|
if s == nil {
|
|
return apitrace.EmptySpanContext()
|
|
}
|
|
return s.spanContext
|
|
}
|
|
|
|
func (s *span) IsRecording() bool {
|
|
if s == nil {
|
|
return false
|
|
}
|
|
return s.data != nil
|
|
}
|
|
|
|
func (s *span) SetStatus(code codes.Code, msg string) {
|
|
if s == nil {
|
|
return
|
|
}
|
|
if !s.IsRecording() {
|
|
return
|
|
}
|
|
s.mu.Lock()
|
|
s.data.StatusCode = internal.ConvertCode(code)
|
|
s.data.StatusMessage = msg
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
func (s *span) SetAttributes(attributes ...label.KeyValue) {
|
|
if !s.IsRecording() {
|
|
return
|
|
}
|
|
s.copyToCappedAttributes(attributes...)
|
|
}
|
|
|
|
func (s *span) SetAttribute(k string, v interface{}) {
|
|
attr := label.Any(k, v)
|
|
if attr.Value.Type() != label.INVALID {
|
|
s.SetAttributes(attr)
|
|
}
|
|
}
|
|
|
|
// End ends the span.
|
|
//
|
|
// The only SpanOption currently supported is WithTimestamp which will set the
|
|
// end time for a Span's life-cycle.
|
|
//
|
|
// If this method is called while panicking an error event is added to the
|
|
// Span before ending it and the panic is continued.
|
|
func (s *span) End(options ...apitrace.SpanOption) {
|
|
if s == nil {
|
|
return
|
|
}
|
|
|
|
if recovered := recover(); recovered != nil {
|
|
// Record but don't stop the panic.
|
|
defer panic(recovered)
|
|
s.addEventWithTimestamp(
|
|
time.Now(),
|
|
errorEventName,
|
|
errorTypeKey.String(typeStr(recovered)),
|
|
errorMessageKey.String(fmt.Sprint(recovered)),
|
|
)
|
|
}
|
|
|
|
if s.executionTracerTaskEnd != nil {
|
|
s.executionTracerTaskEnd()
|
|
}
|
|
if !s.IsRecording() {
|
|
return
|
|
}
|
|
config := apitrace.NewSpanConfig(options...)
|
|
s.endOnce.Do(func() {
|
|
sps, _ := s.tracer.provider.spanProcessors.Load().(spanProcessorMap)
|
|
mustExportOrProcess := len(sps) > 0
|
|
if mustExportOrProcess {
|
|
sd := s.makeSpanData()
|
|
if config.Timestamp.IsZero() {
|
|
sd.EndTime = internal.MonotonicEndTime(sd.StartTime)
|
|
} else {
|
|
sd.EndTime = config.Timestamp
|
|
}
|
|
for sp := range sps {
|
|
sp.OnEnd(sd)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func (s *span) RecordError(ctx context.Context, err error, opts ...apitrace.ErrorOption) {
|
|
if s == nil || err == nil {
|
|
return
|
|
}
|
|
|
|
if !s.IsRecording() {
|
|
return
|
|
}
|
|
|
|
cfg := apitrace.ErrorConfig{}
|
|
|
|
for _, o := range opts {
|
|
o(&cfg)
|
|
}
|
|
|
|
if cfg.Timestamp.IsZero() {
|
|
cfg.Timestamp = time.Now()
|
|
}
|
|
|
|
if cfg.StatusCode != codes.OK {
|
|
s.SetStatus(cfg.StatusCode, "")
|
|
}
|
|
|
|
s.AddEventWithTimestamp(ctx, cfg.Timestamp, errorEventName,
|
|
errorTypeKey.String(typeStr(err)),
|
|
errorMessageKey.String(err.Error()),
|
|
)
|
|
}
|
|
|
|
func typeStr(i interface{}) string {
|
|
t := reflect.TypeOf(i)
|
|
if t.PkgPath() == "" && t.Name() == "" {
|
|
// Likely a builtin type.
|
|
return t.String()
|
|
}
|
|
return fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
|
|
}
|
|
|
|
func (s *span) Tracer() apitrace.Tracer {
|
|
return s.tracer
|
|
}
|
|
|
|
func (s *span) AddEvent(ctx context.Context, name string, attrs ...label.KeyValue) {
|
|
if !s.IsRecording() {
|
|
return
|
|
}
|
|
s.addEventWithTimestamp(time.Now(), name, attrs...)
|
|
}
|
|
|
|
func (s *span) AddEventWithTimestamp(ctx context.Context, timestamp time.Time, name string, attrs ...label.KeyValue) {
|
|
if !s.IsRecording() {
|
|
return
|
|
}
|
|
s.addEventWithTimestamp(timestamp, name, attrs...)
|
|
}
|
|
|
|
func (s *span) addEventWithTimestamp(timestamp time.Time, name string, attrs ...label.KeyValue) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.messageEvents.add(export.Event{
|
|
Name: name,
|
|
Attributes: attrs,
|
|
Time: timestamp,
|
|
})
|
|
}
|
|
|
|
var errUninitializedSpan = errors.New("failed to set name on uninitialized span")
|
|
|
|
func (s *span) SetName(name string) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if s.data == nil {
|
|
global.Handle(errUninitializedSpan)
|
|
return
|
|
}
|
|
s.data.Name = name
|
|
// SAMPLING
|
|
noParent := !s.data.ParentSpanID.IsValid()
|
|
var ctx apitrace.SpanContext
|
|
if noParent {
|
|
ctx = apitrace.EmptySpanContext()
|
|
} else {
|
|
// FIXME: Where do we get the parent context from?
|
|
// From SpanStore?
|
|
ctx = s.data.SpanContext
|
|
}
|
|
data := samplingData{
|
|
noParent: noParent,
|
|
remoteParent: s.data.HasRemoteParent,
|
|
parent: ctx,
|
|
name: name,
|
|
cfg: s.tracer.provider.config.Load().(*Config),
|
|
span: s,
|
|
attributes: s.data.Attributes,
|
|
links: s.data.Links,
|
|
kind: s.data.SpanKind,
|
|
}
|
|
sampled := makeSamplingDecision(data)
|
|
|
|
// Adding attributes directly rather than using s.SetAttributes()
|
|
// as s.mu is already locked and attempting to do so would deadlock.
|
|
for _, a := range sampled.Attributes {
|
|
s.attributes.add(a)
|
|
}
|
|
}
|
|
|
|
func (s *span) addLink(link apitrace.Link) {
|
|
if !s.IsRecording() {
|
|
return
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.links.add(link)
|
|
}
|
|
|
|
// makeSpanData produces a SpanData representing the current state of the span.
|
|
// It requires that s.data is non-nil.
|
|
func (s *span) makeSpanData() *export.SpanData {
|
|
var sd export.SpanData
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
sd = *s.data
|
|
|
|
s.attributes.toSpanData(&sd)
|
|
|
|
if len(s.messageEvents.queue) > 0 {
|
|
sd.MessageEvents = s.interfaceArrayToMessageEventArray()
|
|
sd.DroppedMessageEventCount = s.messageEvents.droppedCount
|
|
}
|
|
if len(s.links.queue) > 0 {
|
|
sd.Links = s.interfaceArrayToLinksArray()
|
|
sd.DroppedLinkCount = s.links.droppedCount
|
|
}
|
|
return &sd
|
|
}
|
|
|
|
func (s *span) interfaceArrayToLinksArray() []apitrace.Link {
|
|
linkArr := make([]apitrace.Link, 0)
|
|
for _, value := range s.links.queue {
|
|
linkArr = append(linkArr, value.(apitrace.Link))
|
|
}
|
|
return linkArr
|
|
}
|
|
|
|
func (s *span) interfaceArrayToMessageEventArray() []export.Event {
|
|
messageEventArr := make([]export.Event, 0)
|
|
for _, value := range s.messageEvents.queue {
|
|
messageEventArr = append(messageEventArr, value.(export.Event))
|
|
}
|
|
return messageEventArr
|
|
}
|
|
|
|
func (s *span) copyToCappedAttributes(attributes ...label.KeyValue) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
for _, a := range attributes {
|
|
if a.Value.Type() != label.INVALID {
|
|
s.attributes.add(a)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *span) addChild() {
|
|
if !s.IsRecording() {
|
|
return
|
|
}
|
|
s.mu.Lock()
|
|
s.data.ChildSpanCount++
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
func startSpanInternal(tr *tracer, name string, parent apitrace.SpanContext, remoteParent bool, o *apitrace.SpanConfig) *span {
|
|
var noParent bool
|
|
span := &span{}
|
|
span.spanContext = parent
|
|
|
|
cfg := tr.provider.config.Load().(*Config)
|
|
|
|
if parent == apitrace.EmptySpanContext() {
|
|
span.spanContext.TraceID = cfg.IDGenerator.NewTraceID()
|
|
noParent = true
|
|
}
|
|
span.spanContext.SpanID = cfg.IDGenerator.NewSpanID()
|
|
data := samplingData{
|
|
noParent: noParent,
|
|
remoteParent: remoteParent,
|
|
parent: parent,
|
|
name: name,
|
|
cfg: cfg,
|
|
span: span,
|
|
attributes: o.Attributes,
|
|
links: o.Links,
|
|
kind: o.SpanKind,
|
|
}
|
|
sampled := makeSamplingDecision(data)
|
|
|
|
// TODO: [rghetia] restore when spanstore is added.
|
|
// if !internal.LocalSpanStoreEnabled && !span.spanContext.IsSampled() && !o.Record {
|
|
if !span.spanContext.IsSampled() && !o.Record {
|
|
return span
|
|
}
|
|
|
|
startTime := o.Timestamp
|
|
if startTime.IsZero() {
|
|
startTime = time.Now()
|
|
}
|
|
span.data = &export.SpanData{
|
|
SpanContext: span.spanContext,
|
|
StartTime: startTime,
|
|
SpanKind: apitrace.ValidateSpanKind(o.SpanKind),
|
|
Name: name,
|
|
HasRemoteParent: remoteParent,
|
|
Resource: cfg.Resource,
|
|
InstrumentationLibrary: tr.instrumentationLibrary,
|
|
}
|
|
span.attributes = newAttributesMap(cfg.MaxAttributesPerSpan)
|
|
span.messageEvents = newEvictedQueue(cfg.MaxEventsPerSpan)
|
|
span.links = newEvictedQueue(cfg.MaxLinksPerSpan)
|
|
|
|
span.SetAttributes(sampled.Attributes...)
|
|
|
|
if !noParent {
|
|
span.data.ParentSpanID = parent.SpanID
|
|
}
|
|
// TODO: [rghetia] restore when spanstore is added.
|
|
//if internal.LocalSpanStoreEnabled {
|
|
// ss := spanStoreForNameCreateIfNew(name)
|
|
// if ss != nil {
|
|
// span.spanStore = ss
|
|
// ss.add(span)
|
|
// }
|
|
//}
|
|
|
|
return span
|
|
}
|
|
|
|
type samplingData struct {
|
|
noParent bool
|
|
remoteParent bool
|
|
parent apitrace.SpanContext
|
|
name string
|
|
cfg *Config
|
|
span *span
|
|
attributes []label.KeyValue
|
|
links []apitrace.Link
|
|
kind apitrace.SpanKind
|
|
}
|
|
|
|
func makeSamplingDecision(data samplingData) SamplingResult {
|
|
if data.noParent || data.remoteParent {
|
|
// If this span is the child of a local span and no
|
|
// Sampler is set in the options, keep the parent's
|
|
// TraceFlags.
|
|
//
|
|
// Otherwise, consult the Sampler in the options if it
|
|
// is non-nil, otherwise the default sampler.
|
|
sampler := data.cfg.DefaultSampler
|
|
//if o.Sampler != nil {
|
|
// sampler = o.Sampler
|
|
//}
|
|
spanContext := &data.span.spanContext
|
|
sampled := sampler.ShouldSample(SamplingParameters{
|
|
ParentContext: data.parent,
|
|
TraceID: spanContext.TraceID,
|
|
Name: data.name,
|
|
HasRemoteParent: data.remoteParent,
|
|
Kind: data.kind,
|
|
Attributes: data.attributes,
|
|
Links: data.links,
|
|
})
|
|
if sampled.Decision == RecordAndSampled {
|
|
spanContext.TraceFlags |= apitrace.FlagsSampled
|
|
} else {
|
|
spanContext.TraceFlags &^= apitrace.FlagsSampled
|
|
}
|
|
return sampled
|
|
}
|
|
if data.parent.TraceFlags&apitrace.FlagsSampled != 0 {
|
|
return SamplingResult{Decision: RecordAndSampled}
|
|
}
|
|
return SamplingResult{Decision: NotRecord}
|
|
}
|