auto.go raw

   1  // Copyright The OpenTelemetry Authors
   2  // SPDX-License-Identifier: Apache-2.0
   3  
   4  package trace // import "go.opentelemetry.io/otel/trace"
   5  
   6  import (
   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.37.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.
  34  func newAutoTracerProvider() TracerProvider { return tracerProviderInstance }
  35  
  36  var tracerProviderInstance = new(autoTracerProvider)
  37  
  38  type autoTracerProvider struct{ embedded.TracerProvider }
  39  
  40  var _ TracerProvider = autoTracerProvider{}
  41  
  42  func (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  
  51  type autoTracer struct {
  52  	embedded.Tracer
  53  
  54  	name, schemaURL, version string
  55  }
  56  
  57  var _ Tracer = autoTracer{}
  58  
  59  func (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
  84  func (*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.
  95  var start = func(context.Context, *autoSpan, *SpanContext, *bool, *SpanContext) {}
  96  
  97  func (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  
 149  func 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  
 165  type 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  
 176  func (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  
 184  func (s *autoSpan) IsRecording() bool {
 185  	if s == nil {
 186  		return false
 187  	}
 188  
 189  	return s.sampled.Load()
 190  }
 191  
 192  func (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  
 216  func (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.
 265  func 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  
 288  func 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  
 306  func 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.
 361  func 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  
 426  func (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  
 435  func (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
 453  func (*autoSpan) ended(buf []byte) { ended(buf) }
 454  
 455  // ended is used for testing.
 456  var ended = func([]byte) {}
 457  
 458  func (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  
 482  func 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  
 491  func (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.
 506  func (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  
 527  func (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  
 552  func 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  
 560  func 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  
 572  func (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  
 583  func (*autoSpan) TracerProvider() TracerProvider { return newAutoTracerProvider() }
 584  
 585  // maxSpan are the span limits resolved during startup.
 586  var maxSpan = newSpanLimits()
 587  
 588  type 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  
 625  func 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.
 647  func 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  }
 663