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