1// Copyright The OpenTelemetry Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package trace // import "go.opentelemetry.io/otel/sdk/trace" 16 17import ( 18 "errors" 19 "fmt" 20 "reflect" 21 "sync" 22 "time" 23 24 "go.opentelemetry.io/otel" 25 "go.opentelemetry.io/otel/codes" 26 "go.opentelemetry.io/otel/label" 27 "go.opentelemetry.io/otel/trace" 28 29 export "go.opentelemetry.io/otel/sdk/export/trace" 30 "go.opentelemetry.io/otel/sdk/internal" 31) 32 33const ( 34 errorTypeKey = label.Key("error.type") 35 errorMessageKey = label.Key("error.message") 36 errorEventName = "error" 37) 38 39var emptySpanContext = trace.SpanContext{} 40 41// span is an implementation of the OpenTelemetry Span API representing the 42// individual component of a trace. 43type span struct { 44 // mu protects the contents of this span. 45 mu sync.Mutex 46 47 // data contains information recorded about the span. 48 // 49 // It will be non-nil if we are exporting the span or recording events for it. 50 // Otherwise, data is nil, and the span is simply a carrier for the 51 // SpanContext, so that the trace ID is propagated. 52 data *export.SpanData 53 spanContext trace.SpanContext 54 55 // attributes are capped at configured limit. When the capacity is reached an oldest entry 56 // is removed to create room for a new entry. 57 attributes *attributesMap 58 59 // messageEvents are stored in FIFO queue capped by configured limit. 60 messageEvents *evictedQueue 61 62 // links are stored in FIFO queue capped by configured limit. 63 links *evictedQueue 64 65 // endOnce ensures End is only called once. 66 endOnce sync.Once 67 68 // executionTracerTaskEnd ends the execution tracer span. 69 executionTracerTaskEnd func() 70 71 // tracer is the SDK tracer that created this span. 72 tracer *tracer 73} 74 75var _ trace.Span = &span{} 76 77func (s *span) SpanContext() trace.SpanContext { 78 if s == nil { 79 return trace.SpanContext{} 80 } 81 return s.spanContext 82} 83 84func (s *span) IsRecording() bool { 85 if s == nil { 86 return false 87 } 88 return s.data != nil 89} 90 91func (s *span) SetStatus(code codes.Code, msg string) { 92 if s == nil { 93 return 94 } 95 if !s.IsRecording() { 96 return 97 } 98 s.mu.Lock() 99 s.data.StatusCode = code 100 s.data.StatusMessage = msg 101 s.mu.Unlock() 102} 103 104func (s *span) SetAttributes(attributes ...label.KeyValue) { 105 if !s.IsRecording() { 106 return 107 } 108 s.copyToCappedAttributes(attributes...) 109} 110 111// End ends the span. 112// 113// The only SpanOption currently supported is WithTimestamp which will set the 114// end time for a Span's life-cycle. 115// 116// If this method is called while panicking an error event is added to the 117// Span before ending it and the panic is continued. 118func (s *span) End(options ...trace.SpanOption) { 119 if s == nil { 120 return 121 } 122 123 if recovered := recover(); recovered != nil { 124 // Record but don't stop the panic. 125 defer panic(recovered) 126 s.addEvent( 127 errorEventName, 128 trace.WithAttributes( 129 errorTypeKey.String(typeStr(recovered)), 130 errorMessageKey.String(fmt.Sprint(recovered)), 131 ), 132 ) 133 } 134 135 if s.executionTracerTaskEnd != nil { 136 s.executionTracerTaskEnd() 137 } 138 if !s.IsRecording() { 139 return 140 } 141 config := trace.NewSpanConfig(options...) 142 s.endOnce.Do(func() { 143 sps, ok := s.tracer.provider.spanProcessors.Load().(spanProcessorStates) 144 mustExportOrProcess := ok && len(sps) > 0 145 if mustExportOrProcess { 146 sd := s.makeSpanData() 147 if config.Timestamp.IsZero() { 148 sd.EndTime = internal.MonotonicEndTime(sd.StartTime) 149 } else { 150 sd.EndTime = config.Timestamp 151 } 152 for _, sp := range sps { 153 sp.sp.OnEnd(sd) 154 } 155 } 156 }) 157} 158 159func (s *span) RecordError(err error, opts ...trace.EventOption) { 160 if s == nil || err == nil || !s.IsRecording() { 161 return 162 } 163 164 s.SetStatus(codes.Error, "") 165 opts = append(opts, trace.WithAttributes( 166 errorTypeKey.String(typeStr(err)), 167 errorMessageKey.String(err.Error()), 168 )) 169 s.addEvent(errorEventName, opts...) 170} 171 172func typeStr(i interface{}) string { 173 t := reflect.TypeOf(i) 174 if t.PkgPath() == "" && t.Name() == "" { 175 // Likely a builtin type. 176 return t.String() 177 } 178 return fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) 179} 180 181func (s *span) Tracer() trace.Tracer { 182 return s.tracer 183} 184 185func (s *span) AddEvent(name string, o ...trace.EventOption) { 186 if !s.IsRecording() { 187 return 188 } 189 s.addEvent(name, o...) 190} 191 192func (s *span) addEvent(name string, o ...trace.EventOption) { 193 c := trace.NewEventConfig(o...) 194 195 s.mu.Lock() 196 defer s.mu.Unlock() 197 s.messageEvents.add(export.Event{ 198 Name: name, 199 Attributes: c.Attributes, 200 Time: c.Timestamp, 201 }) 202} 203 204var errUninitializedSpan = errors.New("failed to set name on uninitialized span") 205 206func (s *span) SetName(name string) { 207 s.mu.Lock() 208 defer s.mu.Unlock() 209 210 if s.data == nil { 211 otel.Handle(errUninitializedSpan) 212 return 213 } 214 s.data.Name = name 215 // SAMPLING 216 noParent := !s.data.ParentSpanID.IsValid() 217 var ctx trace.SpanContext 218 if noParent { 219 ctx = trace.SpanContext{} 220 } else { 221 // FIXME: Where do we get the parent context from? 222 ctx = s.data.SpanContext 223 } 224 data := samplingData{ 225 noParent: noParent, 226 remoteParent: s.data.HasRemoteParent, 227 parent: ctx, 228 name: name, 229 cfg: s.tracer.provider.config.Load().(*Config), 230 span: s, 231 attributes: s.data.Attributes, 232 links: s.data.Links, 233 kind: s.data.SpanKind, 234 } 235 sampled := makeSamplingDecision(data) 236 237 // Adding attributes directly rather than using s.SetAttributes() 238 // as s.mu is already locked and attempting to do so would deadlock. 239 for _, a := range sampled.Attributes { 240 s.attributes.add(a) 241 } 242} 243 244func (s *span) addLink(link trace.Link) { 245 if !s.IsRecording() { 246 return 247 } 248 s.mu.Lock() 249 defer s.mu.Unlock() 250 s.links.add(link) 251} 252 253// makeSpanData produces a SpanData representing the current state of the span. 254// It requires that s.data is non-nil. 255func (s *span) makeSpanData() *export.SpanData { 256 var sd export.SpanData 257 s.mu.Lock() 258 defer s.mu.Unlock() 259 sd = *s.data 260 261 s.attributes.toSpanData(&sd) 262 263 if len(s.messageEvents.queue) > 0 { 264 sd.MessageEvents = s.interfaceArrayToMessageEventArray() 265 sd.DroppedMessageEventCount = s.messageEvents.droppedCount 266 } 267 if len(s.links.queue) > 0 { 268 sd.Links = s.interfaceArrayToLinksArray() 269 sd.DroppedLinkCount = s.links.droppedCount 270 } 271 return &sd 272} 273 274func (s *span) interfaceArrayToLinksArray() []trace.Link { 275 linkArr := make([]trace.Link, 0) 276 for _, value := range s.links.queue { 277 linkArr = append(linkArr, value.(trace.Link)) 278 } 279 return linkArr 280} 281 282func (s *span) interfaceArrayToMessageEventArray() []export.Event { 283 messageEventArr := make([]export.Event, 0) 284 for _, value := range s.messageEvents.queue { 285 messageEventArr = append(messageEventArr, value.(export.Event)) 286 } 287 return messageEventArr 288} 289 290func (s *span) copyToCappedAttributes(attributes ...label.KeyValue) { 291 s.mu.Lock() 292 defer s.mu.Unlock() 293 for _, a := range attributes { 294 if a.Value.Type() != label.INVALID { 295 s.attributes.add(a) 296 } 297 } 298} 299 300func (s *span) addChild() { 301 if !s.IsRecording() { 302 return 303 } 304 s.mu.Lock() 305 s.data.ChildSpanCount++ 306 s.mu.Unlock() 307} 308 309func startSpanInternal(tr *tracer, name string, parent trace.SpanContext, remoteParent bool, o *trace.SpanConfig) *span { 310 var noParent bool 311 span := &span{} 312 span.spanContext = parent 313 314 cfg := tr.provider.config.Load().(*Config) 315 316 if parent == emptySpanContext { 317 span.spanContext.TraceID = cfg.IDGenerator.NewTraceID() 318 noParent = true 319 } 320 span.spanContext.SpanID = cfg.IDGenerator.NewSpanID() 321 data := samplingData{ 322 noParent: noParent, 323 remoteParent: remoteParent, 324 parent: parent, 325 name: name, 326 cfg: cfg, 327 span: span, 328 attributes: o.Attributes, 329 links: o.Links, 330 kind: o.SpanKind, 331 } 332 sampled := makeSamplingDecision(data) 333 334 if !span.spanContext.IsSampled() && !o.Record { 335 return span 336 } 337 338 startTime := o.Timestamp 339 if startTime.IsZero() { 340 startTime = time.Now() 341 } 342 span.data = &export.SpanData{ 343 SpanContext: span.spanContext, 344 StartTime: startTime, 345 SpanKind: trace.ValidateSpanKind(o.SpanKind), 346 Name: name, 347 HasRemoteParent: remoteParent, 348 Resource: cfg.Resource, 349 InstrumentationLibrary: tr.instrumentationLibrary, 350 } 351 span.attributes = newAttributesMap(cfg.MaxAttributesPerSpan) 352 span.messageEvents = newEvictedQueue(cfg.MaxEventsPerSpan) 353 span.links = newEvictedQueue(cfg.MaxLinksPerSpan) 354 355 span.SetAttributes(sampled.Attributes...) 356 357 if !noParent { 358 span.data.ParentSpanID = parent.SpanID 359 } 360 361 return span 362} 363 364type samplingData struct { 365 noParent bool 366 remoteParent bool 367 parent trace.SpanContext 368 name string 369 cfg *Config 370 span *span 371 attributes []label.KeyValue 372 links []trace.Link 373 kind trace.SpanKind 374} 375 376func makeSamplingDecision(data samplingData) SamplingResult { 377 sampler := data.cfg.DefaultSampler 378 spanContext := &data.span.spanContext 379 sampled := sampler.ShouldSample(SamplingParameters{ 380 ParentContext: data.parent, 381 TraceID: spanContext.TraceID, 382 Name: data.name, 383 HasRemoteParent: data.remoteParent, 384 Kind: data.kind, 385 Attributes: data.attributes, 386 Links: data.links, 387 }) 388 if sampled.Decision == RecordAndSample { 389 spanContext.TraceFlags |= trace.FlagsSampled 390 } else { 391 spanContext.TraceFlags &^= trace.FlagsSampled 392 } 393 return sampled 394} 395