auto.go

  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}