span.go raw

   1  // Copyright The OpenTelemetry Authors
   2  // SPDX-License-Identifier: Apache-2.0
   3  
   4  package sdk
   5  
   6  import (
   7  	"encoding/json"
   8  	"fmt"
   9  	"math"
  10  	"reflect"
  11  	"runtime"
  12  	"strings"
  13  	"sync"
  14  	"sync/atomic"
  15  	"time"
  16  	"unicode/utf8"
  17  
  18  	"go.opentelemetry.io/otel/attribute"
  19  	"go.opentelemetry.io/otel/codes"
  20  	semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
  21  	"go.opentelemetry.io/otel/trace"
  22  	"go.opentelemetry.io/otel/trace/noop"
  23  
  24  	"go.opentelemetry.io/auto/sdk/internal/telemetry"
  25  )
  26  
  27  type span struct {
  28  	noop.Span
  29  
  30  	spanContext trace.SpanContext
  31  	sampled     atomic.Bool
  32  
  33  	mu     sync.Mutex
  34  	traces *telemetry.Traces
  35  	span   *telemetry.Span
  36  }
  37  
  38  func (s *span) SpanContext() trace.SpanContext {
  39  	if s == nil {
  40  		return trace.SpanContext{}
  41  	}
  42  	// s.spanContext is immutable, do not acquire lock s.mu.
  43  	return s.spanContext
  44  }
  45  
  46  func (s *span) IsRecording() bool {
  47  	if s == nil {
  48  		return false
  49  	}
  50  
  51  	return s.sampled.Load()
  52  }
  53  
  54  func (s *span) SetStatus(c codes.Code, msg string) {
  55  	if s == nil || !s.sampled.Load() {
  56  		return
  57  	}
  58  
  59  	s.mu.Lock()
  60  	defer s.mu.Unlock()
  61  
  62  	if s.span.Status == nil {
  63  		s.span.Status = new(telemetry.Status)
  64  	}
  65  
  66  	s.span.Status.Message = msg
  67  
  68  	switch c {
  69  	case codes.Unset:
  70  		s.span.Status.Code = telemetry.StatusCodeUnset
  71  	case codes.Error:
  72  		s.span.Status.Code = telemetry.StatusCodeError
  73  	case codes.Ok:
  74  		s.span.Status.Code = telemetry.StatusCodeOK
  75  	}
  76  }
  77  
  78  func (s *span) SetAttributes(attrs ...attribute.KeyValue) {
  79  	if s == nil || !s.sampled.Load() {
  80  		return
  81  	}
  82  
  83  	s.mu.Lock()
  84  	defer s.mu.Unlock()
  85  
  86  	limit := maxSpan.Attrs
  87  	if limit == 0 {
  88  		// No attributes allowed.
  89  		n := int64(len(attrs))
  90  		if n > 0 {
  91  			s.span.DroppedAttrs += uint32( //nolint:gosec  // Bounds checked.
  92  				min(n, math.MaxUint32),
  93  			)
  94  		}
  95  		return
  96  	}
  97  
  98  	m := make(map[string]int)
  99  	for i, a := range s.span.Attrs {
 100  		m[a.Key] = i
 101  	}
 102  
 103  	for _, a := range attrs {
 104  		val := convAttrValue(a.Value)
 105  		if val.Empty() {
 106  			s.span.DroppedAttrs++
 107  			continue
 108  		}
 109  
 110  		if idx, ok := m[string(a.Key)]; ok {
 111  			s.span.Attrs[idx] = telemetry.Attr{
 112  				Key:   string(a.Key),
 113  				Value: val,
 114  			}
 115  		} else if limit < 0 || len(s.span.Attrs) < limit {
 116  			s.span.Attrs = append(s.span.Attrs, telemetry.Attr{
 117  				Key:   string(a.Key),
 118  				Value: val,
 119  			})
 120  			m[string(a.Key)] = len(s.span.Attrs) - 1
 121  		} else {
 122  			s.span.DroppedAttrs++
 123  		}
 124  	}
 125  }
 126  
 127  // convCappedAttrs converts up to limit attrs into a []telemetry.Attr. The
 128  // number of dropped attributes is also returned.
 129  func convCappedAttrs(limit int, attrs []attribute.KeyValue) ([]telemetry.Attr, uint32) {
 130  	n := len(attrs)
 131  	if limit == 0 {
 132  		var out uint32
 133  		if n > 0 {
 134  			out = uint32(min(int64(n), math.MaxUint32)) //nolint:gosec  // Bounds checked.
 135  		}
 136  		return nil, out
 137  	}
 138  
 139  	if limit < 0 {
 140  		// Unlimited.
 141  		return convAttrs(attrs), 0
 142  	}
 143  
 144  	if n < 0 {
 145  		n = 0
 146  	}
 147  
 148  	limit = min(n, limit)
 149  	return convAttrs(attrs[:limit]), uint32(n - limit) //nolint:gosec  // Bounds checked.
 150  }
 151  
 152  func convAttrs(attrs []attribute.KeyValue) []telemetry.Attr {
 153  	if len(attrs) == 0 {
 154  		// Avoid allocations if not necessary.
 155  		return nil
 156  	}
 157  
 158  	out := make([]telemetry.Attr, 0, len(attrs))
 159  	for _, attr := range attrs {
 160  		key := string(attr.Key)
 161  		val := convAttrValue(attr.Value)
 162  		if val.Empty() {
 163  			continue
 164  		}
 165  		out = append(out, telemetry.Attr{Key: key, Value: val})
 166  	}
 167  	return out
 168  }
 169  
 170  func convAttrValue(value attribute.Value) telemetry.Value {
 171  	switch value.Type() {
 172  	case attribute.BOOL:
 173  		return telemetry.BoolValue(value.AsBool())
 174  	case attribute.INT64:
 175  		return telemetry.Int64Value(value.AsInt64())
 176  	case attribute.FLOAT64:
 177  		return telemetry.Float64Value(value.AsFloat64())
 178  	case attribute.STRING:
 179  		v := truncate(maxSpan.AttrValueLen, value.AsString())
 180  		return telemetry.StringValue(v)
 181  	case attribute.BOOLSLICE:
 182  		slice := value.AsBoolSlice()
 183  		out := make([]telemetry.Value, 0, len(slice))
 184  		for _, v := range slice {
 185  			out = append(out, telemetry.BoolValue(v))
 186  		}
 187  		return telemetry.SliceValue(out...)
 188  	case attribute.INT64SLICE:
 189  		slice := value.AsInt64Slice()
 190  		out := make([]telemetry.Value, 0, len(slice))
 191  		for _, v := range slice {
 192  			out = append(out, telemetry.Int64Value(v))
 193  		}
 194  		return telemetry.SliceValue(out...)
 195  	case attribute.FLOAT64SLICE:
 196  		slice := value.AsFloat64Slice()
 197  		out := make([]telemetry.Value, 0, len(slice))
 198  		for _, v := range slice {
 199  			out = append(out, telemetry.Float64Value(v))
 200  		}
 201  		return telemetry.SliceValue(out...)
 202  	case attribute.STRINGSLICE:
 203  		slice := value.AsStringSlice()
 204  		out := make([]telemetry.Value, 0, len(slice))
 205  		for _, v := range slice {
 206  			v = truncate(maxSpan.AttrValueLen, v)
 207  			out = append(out, telemetry.StringValue(v))
 208  		}
 209  		return telemetry.SliceValue(out...)
 210  	}
 211  	return telemetry.Value{}
 212  }
 213  
 214  // truncate returns a truncated version of s such that it contains less than
 215  // the limit number of characters. Truncation is applied by returning the limit
 216  // number of valid characters contained in s.
 217  //
 218  // If limit is negative, it returns the original string.
 219  //
 220  // UTF-8 is supported. When truncating, all invalid characters are dropped
 221  // before applying truncation.
 222  //
 223  // If s already contains less than the limit number of bytes, it is returned
 224  // unchanged. No invalid characters are removed.
 225  func truncate(limit int, s string) string {
 226  	// This prioritize performance in the following order based on the most
 227  	// common expected use-cases.
 228  	//
 229  	//  - Short values less than the default limit (128).
 230  	//  - Strings with valid encodings that exceed the limit.
 231  	//  - No limit.
 232  	//  - Strings with invalid encodings that exceed the limit.
 233  	if limit < 0 || len(s) <= limit {
 234  		return s
 235  	}
 236  
 237  	// Optimistically, assume all valid UTF-8.
 238  	var b strings.Builder
 239  	count := 0
 240  	for i, c := range s {
 241  		if c != utf8.RuneError {
 242  			count++
 243  			if count > limit {
 244  				return s[:i]
 245  			}
 246  			continue
 247  		}
 248  
 249  		_, size := utf8.DecodeRuneInString(s[i:])
 250  		if size == 1 {
 251  			// Invalid encoding.
 252  			b.Grow(len(s) - 1)
 253  			_, _ = b.WriteString(s[:i])
 254  			s = s[i:]
 255  			break
 256  		}
 257  	}
 258  
 259  	// Fast-path, no invalid input.
 260  	if b.Cap() == 0 {
 261  		return s
 262  	}
 263  
 264  	// Truncate while validating UTF-8.
 265  	for i := 0; i < len(s) && count < limit; {
 266  		c := s[i]
 267  		if c < utf8.RuneSelf {
 268  			// Optimization for single byte runes (common case).
 269  			_ = b.WriteByte(c)
 270  			i++
 271  			count++
 272  			continue
 273  		}
 274  
 275  		_, size := utf8.DecodeRuneInString(s[i:])
 276  		if size == 1 {
 277  			// We checked for all 1-byte runes above, this is a RuneError.
 278  			i++
 279  			continue
 280  		}
 281  
 282  		_, _ = b.WriteString(s[i : i+size])
 283  		i += size
 284  		count++
 285  	}
 286  
 287  	return b.String()
 288  }
 289  
 290  func (s *span) End(opts ...trace.SpanEndOption) {
 291  	if s == nil || !s.sampled.Swap(false) {
 292  		return
 293  	}
 294  
 295  	// s.end exists so the lock (s.mu) is not held while s.ended is called.
 296  	s.ended(s.end(opts))
 297  }
 298  
 299  func (s *span) end(opts []trace.SpanEndOption) []byte {
 300  	s.mu.Lock()
 301  	defer s.mu.Unlock()
 302  
 303  	cfg := trace.NewSpanEndConfig(opts...)
 304  	if t := cfg.Timestamp(); !t.IsZero() {
 305  		s.span.EndTime = cfg.Timestamp()
 306  	} else {
 307  		s.span.EndTime = time.Now()
 308  	}
 309  
 310  	b, _ := json.Marshal(s.traces) // TODO: do not ignore this error.
 311  	return b
 312  }
 313  
 314  // Expected to be implemented in eBPF.
 315  //
 316  //go:noinline
 317  func (*span) ended(buf []byte) { ended(buf) }
 318  
 319  // ended is used for testing.
 320  var ended = func([]byte) {}
 321  
 322  func (s *span) RecordError(err error, opts ...trace.EventOption) {
 323  	if s == nil || err == nil || !s.sampled.Load() {
 324  		return
 325  	}
 326  
 327  	cfg := trace.NewEventConfig(opts...)
 328  
 329  	attrs := cfg.Attributes()
 330  	attrs = append(attrs,
 331  		semconv.ExceptionType(typeStr(err)),
 332  		semconv.ExceptionMessage(err.Error()),
 333  	)
 334  	if cfg.StackTrace() {
 335  		buf := make([]byte, 2048)
 336  		n := runtime.Stack(buf, false)
 337  		attrs = append(attrs, semconv.ExceptionStacktrace(string(buf[0:n])))
 338  	}
 339  
 340  	s.mu.Lock()
 341  	defer s.mu.Unlock()
 342  
 343  	s.addEvent(semconv.ExceptionEventName, cfg.Timestamp(), attrs)
 344  }
 345  
 346  func typeStr(i any) string {
 347  	t := reflect.TypeOf(i)
 348  	if t.PkgPath() == "" && t.Name() == "" {
 349  		// Likely a builtin type.
 350  		return t.String()
 351  	}
 352  	return fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
 353  }
 354  
 355  func (s *span) AddEvent(name string, opts ...trace.EventOption) {
 356  	if s == nil || !s.sampled.Load() {
 357  		return
 358  	}
 359  
 360  	cfg := trace.NewEventConfig(opts...)
 361  
 362  	s.mu.Lock()
 363  	defer s.mu.Unlock()
 364  
 365  	s.addEvent(name, cfg.Timestamp(), cfg.Attributes())
 366  }
 367  
 368  // addEvent adds an event with name and attrs at tStamp to the span. The span
 369  // lock (s.mu) needs to be held by the caller.
 370  func (s *span) addEvent(name string, tStamp time.Time, attrs []attribute.KeyValue) {
 371  	limit := maxSpan.Events
 372  
 373  	if limit == 0 {
 374  		s.span.DroppedEvents++
 375  		return
 376  	}
 377  
 378  	if limit > 0 && len(s.span.Events) == limit {
 379  		// Drop head while avoiding allocation of more capacity.
 380  		copy(s.span.Events[:limit-1], s.span.Events[1:])
 381  		s.span.Events = s.span.Events[:limit-1]
 382  		s.span.DroppedEvents++
 383  	}
 384  
 385  	e := &telemetry.SpanEvent{Time: tStamp, Name: name}
 386  	e.Attrs, e.DroppedAttrs = convCappedAttrs(maxSpan.EventAttrs, attrs)
 387  
 388  	s.span.Events = append(s.span.Events, e)
 389  }
 390  
 391  func (s *span) AddLink(link trace.Link) {
 392  	if s == nil || !s.sampled.Load() {
 393  		return
 394  	}
 395  
 396  	l := maxSpan.Links
 397  
 398  	s.mu.Lock()
 399  	defer s.mu.Unlock()
 400  
 401  	if l == 0 {
 402  		s.span.DroppedLinks++
 403  		return
 404  	}
 405  
 406  	if l > 0 && len(s.span.Links) == l {
 407  		// Drop head while avoiding allocation of more capacity.
 408  		copy(s.span.Links[:l-1], s.span.Links[1:])
 409  		s.span.Links = s.span.Links[:l-1]
 410  		s.span.DroppedLinks++
 411  	}
 412  
 413  	s.span.Links = append(s.span.Links, convLink(link))
 414  }
 415  
 416  func convLinks(links []trace.Link) []*telemetry.SpanLink {
 417  	out := make([]*telemetry.SpanLink, 0, len(links))
 418  	for _, link := range links {
 419  		out = append(out, convLink(link))
 420  	}
 421  	return out
 422  }
 423  
 424  func convLink(link trace.Link) *telemetry.SpanLink {
 425  	l := &telemetry.SpanLink{
 426  		TraceID:    telemetry.TraceID(link.SpanContext.TraceID()),
 427  		SpanID:     telemetry.SpanID(link.SpanContext.SpanID()),
 428  		TraceState: link.SpanContext.TraceState().String(),
 429  		Flags:      uint32(link.SpanContext.TraceFlags()),
 430  	}
 431  	l.Attrs, l.DroppedAttrs = convCappedAttrs(maxSpan.LinkAttrs, link.Attributes)
 432  
 433  	return l
 434  }
 435  
 436  func (s *span) SetName(name string) {
 437  	if s == nil || !s.sampled.Load() {
 438  		return
 439  	}
 440  
 441  	s.mu.Lock()
 442  	defer s.mu.Unlock()
 443  
 444  	s.span.Name = name
 445  }
 446  
 447  func (*span) TracerProvider() trace.TracerProvider { return TracerProvider() }
 448