1// Copyright The OpenTelemetry Authors
2// SPDX-License-Identifier: Apache-2.0
3
4package trace // import "go.opentelemetry.io/otel/trace"
5
6import (
7 "context"
8 "encoding/json"
9 "fmt"
10 "math"
11 "os"
12 "reflect"
13 "runtime"
14 "strconv"
15 "strings"
16 "sync"
17 "sync/atomic"
18 "time"
19 "unicode/utf8"
20
21 "go.opentelemetry.io/otel/attribute"
22 "go.opentelemetry.io/otel/codes"
23 semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
24 "go.opentelemetry.io/otel/trace/embedded"
25 "go.opentelemetry.io/otel/trace/internal/telemetry"
26)
27
28// newAutoTracerProvider returns an auto-instrumentable [trace.TracerProvider].
29// If an [go.opentelemetry.io/auto.Instrumentation] is configured to instrument
30// the process using the returned TracerProvider, all of the telemetry it
31// produces will be processed and handled by that Instrumentation. By default,
32// if no Instrumentation instruments the TracerProvider it will not generate
33// any trace telemetry.
34func newAutoTracerProvider() TracerProvider { return tracerProviderInstance }
35
36var tracerProviderInstance = new(autoTracerProvider)
37
38type autoTracerProvider struct{ embedded.TracerProvider }
39
40var _ TracerProvider = autoTracerProvider{}
41
42func (p autoTracerProvider) Tracer(name string, opts ...TracerOption) Tracer {
43 cfg := NewTracerConfig(opts...)
44 return autoTracer{
45 name: name,
46 version: cfg.InstrumentationVersion(),
47 schemaURL: cfg.SchemaURL(),
48 }
49}
50
51type autoTracer struct {
52 embedded.Tracer
53
54 name, schemaURL, version string
55}
56
57var _ Tracer = autoTracer{}
58
59func (t autoTracer) Start(ctx context.Context, name string, opts ...SpanStartOption) (context.Context, Span) {
60 var psc SpanContext
61 sampled := true
62 span := new(autoSpan)
63
64 // Ask eBPF for sampling decision and span context info.
65 t.start(ctx, span, &psc, &sampled, &span.spanContext)
66
67 span.sampled.Store(sampled)
68
69 ctx = ContextWithSpan(ctx, span)
70
71 if sampled {
72 // Only build traces if sampled.
73 cfg := NewSpanStartConfig(opts...)
74 span.traces, span.span = t.traces(name, cfg, span.spanContext, psc)
75 }
76
77 return ctx, span
78}
79
80// Expected to be implemented in eBPF.
81//
82//go:noinline
83func (t *autoTracer) start(
84 ctx context.Context,
85 spanPtr *autoSpan,
86 psc *SpanContext,
87 sampled *bool,
88 sc *SpanContext,
89) {
90 start(ctx, spanPtr, psc, sampled, sc)
91}
92
93// start is used for testing.
94var start = func(context.Context, *autoSpan, *SpanContext, *bool, *SpanContext) {}
95
96func (t autoTracer) traces(name string, cfg SpanConfig, sc, psc SpanContext) (*telemetry.Traces, *telemetry.Span) {
97 span := &telemetry.Span{
98 TraceID: telemetry.TraceID(sc.TraceID()),
99 SpanID: telemetry.SpanID(sc.SpanID()),
100 Flags: uint32(sc.TraceFlags()),
101 TraceState: sc.TraceState().String(),
102 ParentSpanID: telemetry.SpanID(psc.SpanID()),
103 Name: name,
104 Kind: spanKind(cfg.SpanKind()),
105 }
106
107 span.Attrs, span.DroppedAttrs = convCappedAttrs(maxSpan.Attrs, cfg.Attributes())
108
109 links := cfg.Links()
110 if limit := maxSpan.Links; limit == 0 {
111 n := int64(len(links))
112 if n > 0 {
113 span.DroppedLinks = uint32(min(n, math.MaxUint32)) // nolint: gosec // Bounds checked.
114 }
115 } else {
116 if limit > 0 {
117 n := int64(max(len(links)-limit, 0))
118 span.DroppedLinks = uint32(min(n, math.MaxUint32)) // nolint: gosec // Bounds checked.
119 links = links[n:]
120 }
121 span.Links = convLinks(links)
122 }
123
124 if t := cfg.Timestamp(); !t.IsZero() {
125 span.StartTime = cfg.Timestamp()
126 } else {
127 span.StartTime = time.Now()
128 }
129
130 return &telemetry.Traces{
131 ResourceSpans: []*telemetry.ResourceSpans{
132 {
133 ScopeSpans: []*telemetry.ScopeSpans{
134 {
135 Scope: &telemetry.Scope{
136 Name: t.name,
137 Version: t.version,
138 },
139 Spans: []*telemetry.Span{span},
140 SchemaURL: t.schemaURL,
141 },
142 },
143 },
144 },
145 }, span
146}
147
148func spanKind(kind SpanKind) telemetry.SpanKind {
149 switch kind {
150 case SpanKindInternal:
151 return telemetry.SpanKindInternal
152 case SpanKindServer:
153 return telemetry.SpanKindServer
154 case SpanKindClient:
155 return telemetry.SpanKindClient
156 case SpanKindProducer:
157 return telemetry.SpanKindProducer
158 case SpanKindConsumer:
159 return telemetry.SpanKindConsumer
160 }
161 return telemetry.SpanKind(0) // undefined.
162}
163
164type autoSpan struct {
165 embedded.Span
166
167 spanContext SpanContext
168 sampled atomic.Bool
169
170 mu sync.Mutex
171 traces *telemetry.Traces
172 span *telemetry.Span
173}
174
175func (s *autoSpan) SpanContext() SpanContext {
176 if s == nil {
177 return SpanContext{}
178 }
179 // s.spanContext is immutable, do not acquire lock s.mu.
180 return s.spanContext
181}
182
183func (s *autoSpan) IsRecording() bool {
184 if s == nil {
185 return false
186 }
187
188 return s.sampled.Load()
189}
190
191func (s *autoSpan) SetStatus(c codes.Code, msg string) {
192 if s == nil || !s.sampled.Load() {
193 return
194 }
195
196 s.mu.Lock()
197 defer s.mu.Unlock()
198
199 if s.span.Status == nil {
200 s.span.Status = new(telemetry.Status)
201 }
202
203 s.span.Status.Message = msg
204
205 switch c {
206 case codes.Unset:
207 s.span.Status.Code = telemetry.StatusCodeUnset
208 case codes.Error:
209 s.span.Status.Code = telemetry.StatusCodeError
210 case codes.Ok:
211 s.span.Status.Code = telemetry.StatusCodeOK
212 }
213}
214
215func (s *autoSpan) SetAttributes(attrs ...attribute.KeyValue) {
216 if s == nil || !s.sampled.Load() {
217 return
218 }
219
220 s.mu.Lock()
221 defer s.mu.Unlock()
222
223 limit := maxSpan.Attrs
224 if limit == 0 {
225 // No attributes allowed.
226 n := int64(len(attrs))
227 if n > 0 {
228 s.span.DroppedAttrs += uint32(min(n, math.MaxUint32)) // nolint: gosec // Bounds checked.
229 }
230 return
231 }
232
233 m := make(map[string]int)
234 for i, a := range s.span.Attrs {
235 m[a.Key] = i
236 }
237
238 for _, a := range attrs {
239 val := convAttrValue(a.Value)
240 if val.Empty() {
241 s.span.DroppedAttrs++
242 continue
243 }
244
245 if idx, ok := m[string(a.Key)]; ok {
246 s.span.Attrs[idx] = telemetry.Attr{
247 Key: string(a.Key),
248 Value: val,
249 }
250 } else if limit < 0 || len(s.span.Attrs) < limit {
251 s.span.Attrs = append(s.span.Attrs, telemetry.Attr{
252 Key: string(a.Key),
253 Value: val,
254 })
255 m[string(a.Key)] = len(s.span.Attrs) - 1
256 } else {
257 s.span.DroppedAttrs++
258 }
259 }
260}
261
262// convCappedAttrs converts up to limit attrs into a []telemetry.Attr. The
263// number of dropped attributes is also returned.
264func convCappedAttrs(limit int, attrs []attribute.KeyValue) ([]telemetry.Attr, uint32) {
265 n := len(attrs)
266 if limit == 0 {
267 var out uint32
268 if n > 0 {
269 out = uint32(min(int64(n), math.MaxUint32)) // nolint: gosec // Bounds checked.
270 }
271 return nil, out
272 }
273
274 if limit < 0 {
275 // Unlimited.
276 return convAttrs(attrs), 0
277 }
278
279 if n < 0 {
280 n = 0
281 }
282
283 limit = min(n, limit)
284 return convAttrs(attrs[:limit]), uint32(n - limit) // nolint: gosec // Bounds checked.
285}
286
287func convAttrs(attrs []attribute.KeyValue) []telemetry.Attr {
288 if len(attrs) == 0 {
289 // Avoid allocations if not necessary.
290 return nil
291 }
292
293 out := make([]telemetry.Attr, 0, len(attrs))
294 for _, attr := range attrs {
295 key := string(attr.Key)
296 val := convAttrValue(attr.Value)
297 if val.Empty() {
298 continue
299 }
300 out = append(out, telemetry.Attr{Key: key, Value: val})
301 }
302 return out
303}
304
305func convAttrValue(value attribute.Value) telemetry.Value {
306 switch value.Type() {
307 case attribute.BOOL:
308 return telemetry.BoolValue(value.AsBool())
309 case attribute.INT64:
310 return telemetry.Int64Value(value.AsInt64())
311 case attribute.FLOAT64:
312 return telemetry.Float64Value(value.AsFloat64())
313 case attribute.STRING:
314 v := truncate(maxSpan.AttrValueLen, value.AsString())
315 return telemetry.StringValue(v)
316 case attribute.BOOLSLICE:
317 slice := value.AsBoolSlice()
318 out := make([]telemetry.Value, 0, len(slice))
319 for _, v := range slice {
320 out = append(out, telemetry.BoolValue(v))
321 }
322 return telemetry.SliceValue(out...)
323 case attribute.INT64SLICE:
324 slice := value.AsInt64Slice()
325 out := make([]telemetry.Value, 0, len(slice))
326 for _, v := range slice {
327 out = append(out, telemetry.Int64Value(v))
328 }
329 return telemetry.SliceValue(out...)
330 case attribute.FLOAT64SLICE:
331 slice := value.AsFloat64Slice()
332 out := make([]telemetry.Value, 0, len(slice))
333 for _, v := range slice {
334 out = append(out, telemetry.Float64Value(v))
335 }
336 return telemetry.SliceValue(out...)
337 case attribute.STRINGSLICE:
338 slice := value.AsStringSlice()
339 out := make([]telemetry.Value, 0, len(slice))
340 for _, v := range slice {
341 v = truncate(maxSpan.AttrValueLen, v)
342 out = append(out, telemetry.StringValue(v))
343 }
344 return telemetry.SliceValue(out...)
345 }
346 return telemetry.Value{}
347}
348
349// truncate returns a truncated version of s such that it contains less than
350// the limit number of characters. Truncation is applied by returning the limit
351// number of valid characters contained in s.
352//
353// If limit is negative, it returns the original string.
354//
355// UTF-8 is supported. When truncating, all invalid characters are dropped
356// before applying truncation.
357//
358// If s already contains less than the limit number of bytes, it is returned
359// unchanged. No invalid characters are removed.
360func truncate(limit int, s string) string {
361 // This prioritize performance in the following order based on the most
362 // common expected use-cases.
363 //
364 // - Short values less than the default limit (128).
365 // - Strings with valid encodings that exceed the limit.
366 // - No limit.
367 // - Strings with invalid encodings that exceed the limit.
368 if limit < 0 || len(s) <= limit {
369 return s
370 }
371
372 // Optimistically, assume all valid UTF-8.
373 var b strings.Builder
374 count := 0
375 for i, c := range s {
376 if c != utf8.RuneError {
377 count++
378 if count > limit {
379 return s[:i]
380 }
381 continue
382 }
383
384 _, size := utf8.DecodeRuneInString(s[i:])
385 if size == 1 {
386 // Invalid encoding.
387 b.Grow(len(s) - 1)
388 _, _ = b.WriteString(s[:i])
389 s = s[i:]
390 break
391 }
392 }
393
394 // Fast-path, no invalid input.
395 if b.Cap() == 0 {
396 return s
397 }
398
399 // Truncate while validating UTF-8.
400 for i := 0; i < len(s) && count < limit; {
401 c := s[i]
402 if c < utf8.RuneSelf {
403 // Optimization for single byte runes (common case).
404 _ = b.WriteByte(c)
405 i++
406 count++
407 continue
408 }
409
410 _, size := utf8.DecodeRuneInString(s[i:])
411 if size == 1 {
412 // We checked for all 1-byte runes above, this is a RuneError.
413 i++
414 continue
415 }
416
417 _, _ = b.WriteString(s[i : i+size])
418 i += size
419 count++
420 }
421
422 return b.String()
423}
424
425func (s *autoSpan) End(opts ...SpanEndOption) {
426 if s == nil || !s.sampled.Swap(false) {
427 return
428 }
429
430 // s.end exists so the lock (s.mu) is not held while s.ended is called.
431 s.ended(s.end(opts))
432}
433
434func (s *autoSpan) end(opts []SpanEndOption) []byte {
435 s.mu.Lock()
436 defer s.mu.Unlock()
437
438 cfg := NewSpanEndConfig(opts...)
439 if t := cfg.Timestamp(); !t.IsZero() {
440 s.span.EndTime = cfg.Timestamp()
441 } else {
442 s.span.EndTime = time.Now()
443 }
444
445 b, _ := json.Marshal(s.traces) // TODO: do not ignore this error.
446 return b
447}
448
449// Expected to be implemented in eBPF.
450//
451//go:noinline
452func (*autoSpan) ended(buf []byte) { ended(buf) }
453
454// ended is used for testing.
455var ended = func([]byte) {}
456
457func (s *autoSpan) RecordError(err error, opts ...EventOption) {
458 if s == nil || err == nil || !s.sampled.Load() {
459 return
460 }
461
462 cfg := NewEventConfig(opts...)
463
464 attrs := cfg.Attributes()
465 attrs = append(attrs,
466 semconv.ExceptionType(typeStr(err)),
467 semconv.ExceptionMessage(err.Error()),
468 )
469 if cfg.StackTrace() {
470 buf := make([]byte, 2048)
471 n := runtime.Stack(buf, false)
472 attrs = append(attrs, semconv.ExceptionStacktrace(string(buf[0:n])))
473 }
474
475 s.mu.Lock()
476 defer s.mu.Unlock()
477
478 s.addEvent(semconv.ExceptionEventName, cfg.Timestamp(), attrs)
479}
480
481func typeStr(i any) string {
482 t := reflect.TypeOf(i)
483 if t.PkgPath() == "" && t.Name() == "" {
484 // Likely a builtin type.
485 return t.String()
486 }
487 return fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
488}
489
490func (s *autoSpan) AddEvent(name string, opts ...EventOption) {
491 if s == nil || !s.sampled.Load() {
492 return
493 }
494
495 cfg := NewEventConfig(opts...)
496
497 s.mu.Lock()
498 defer s.mu.Unlock()
499
500 s.addEvent(name, cfg.Timestamp(), cfg.Attributes())
501}
502
503// addEvent adds an event with name and attrs at tStamp to the span. The span
504// lock (s.mu) needs to be held by the caller.
505func (s *autoSpan) addEvent(name string, tStamp time.Time, attrs []attribute.KeyValue) {
506 limit := maxSpan.Events
507
508 if limit == 0 {
509 s.span.DroppedEvents++
510 return
511 }
512
513 if limit > 0 && len(s.span.Events) == limit {
514 // Drop head while avoiding allocation of more capacity.
515 copy(s.span.Events[:limit-1], s.span.Events[1:])
516 s.span.Events = s.span.Events[:limit-1]
517 s.span.DroppedEvents++
518 }
519
520 e := &telemetry.SpanEvent{Time: tStamp, Name: name}
521 e.Attrs, e.DroppedAttrs = convCappedAttrs(maxSpan.EventAttrs, attrs)
522
523 s.span.Events = append(s.span.Events, e)
524}
525
526func (s *autoSpan) AddLink(link Link) {
527 if s == nil || !s.sampled.Load() {
528 return
529 }
530
531 l := maxSpan.Links
532
533 s.mu.Lock()
534 defer s.mu.Unlock()
535
536 if l == 0 {
537 s.span.DroppedLinks++
538 return
539 }
540
541 if l > 0 && len(s.span.Links) == l {
542 // Drop head while avoiding allocation of more capacity.
543 copy(s.span.Links[:l-1], s.span.Links[1:])
544 s.span.Links = s.span.Links[:l-1]
545 s.span.DroppedLinks++
546 }
547
548 s.span.Links = append(s.span.Links, convLink(link))
549}
550
551func convLinks(links []Link) []*telemetry.SpanLink {
552 out := make([]*telemetry.SpanLink, 0, len(links))
553 for _, link := range links {
554 out = append(out, convLink(link))
555 }
556 return out
557}
558
559func convLink(link Link) *telemetry.SpanLink {
560 l := &telemetry.SpanLink{
561 TraceID: telemetry.TraceID(link.SpanContext.TraceID()),
562 SpanID: telemetry.SpanID(link.SpanContext.SpanID()),
563 TraceState: link.SpanContext.TraceState().String(),
564 Flags: uint32(link.SpanContext.TraceFlags()),
565 }
566 l.Attrs, l.DroppedAttrs = convCappedAttrs(maxSpan.LinkAttrs, link.Attributes)
567
568 return l
569}
570
571func (s *autoSpan) SetName(name string) {
572 if s == nil || !s.sampled.Load() {
573 return
574 }
575
576 s.mu.Lock()
577 defer s.mu.Unlock()
578
579 s.span.Name = name
580}
581
582func (*autoSpan) TracerProvider() TracerProvider { return newAutoTracerProvider() }
583
584// maxSpan are the span limits resolved during startup.
585var maxSpan = newSpanLimits()
586
587type spanLimits struct {
588 // Attrs is the number of allowed attributes for a span.
589 //
590 // This is resolved from the environment variable value for the
591 // OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT key if it exists. Otherwise, the
592 // environment variable value for OTEL_ATTRIBUTE_COUNT_LIMIT, or 128 if
593 // that is not set, is used.
594 Attrs int
595 // AttrValueLen is the maximum attribute value length allowed for a span.
596 //
597 // This is resolved from the environment variable value for the
598 // OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT key if it exists. Otherwise, the
599 // environment variable value for OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, or -1
600 // if that is not set, is used.
601 AttrValueLen int
602 // Events is the number of allowed events for a span.
603 //
604 // This is resolved from the environment variable value for the
605 // OTEL_SPAN_EVENT_COUNT_LIMIT key, or 128 is used if that is not set.
606 Events int
607 // EventAttrs is the number of allowed attributes for a span event.
608 //
609 // The is resolved from the environment variable value for the
610 // OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT key, or 128 is used if that is not set.
611 EventAttrs int
612 // Links is the number of allowed Links for a span.
613 //
614 // This is resolved from the environment variable value for the
615 // OTEL_SPAN_LINK_COUNT_LIMIT, or 128 is used if that is not set.
616 Links int
617 // LinkAttrs is the number of allowed attributes for a span link.
618 //
619 // This is resolved from the environment variable value for the
620 // OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, or 128 is used if that is not set.
621 LinkAttrs int
622}
623
624func newSpanLimits() spanLimits {
625 return spanLimits{
626 Attrs: firstEnv(
627 128,
628 "OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT",
629 "OTEL_ATTRIBUTE_COUNT_LIMIT",
630 ),
631 AttrValueLen: firstEnv(
632 -1, // Unlimited.
633 "OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT",
634 "OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT",
635 ),
636 Events: firstEnv(128, "OTEL_SPAN_EVENT_COUNT_LIMIT"),
637 EventAttrs: firstEnv(128, "OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT"),
638 Links: firstEnv(128, "OTEL_SPAN_LINK_COUNT_LIMIT"),
639 LinkAttrs: firstEnv(128, "OTEL_LINK_ATTRIBUTE_COUNT_LIMIT"),
640 }
641}
642
643// firstEnv returns the parsed integer value of the first matching environment
644// variable from keys. The defaultVal is returned if the value is not an
645// integer or no match is found.
646func firstEnv(defaultVal int, keys ...string) int {
647 for _, key := range keys {
648 strV := os.Getenv(key)
649 if strV == "" {
650 continue
651 }
652
653 v, err := strconv.Atoi(strV)
654 if err == nil {
655 return v
656 }
657 // Ignore invalid environment variable.
658 }
659
660 return defaultVal
661}