This commit updates to images v0.117.0 so that the cross-distro.sh test works again (images removed fedora-39.json in main but the uses the previous version of images that includes fedora-39 so there is a mismatch (we should look into if there is a way to get github.com/osbuild/images@latest instead of main in the cross-arch test). It also updates all the vendor stuff that got pulled via the new images release (which is giantonormous). This update requires updating the Go version to 1.22.8
432 lines
9.6 KiB
Go
432 lines
9.6 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package sdk
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/codes"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
|
"go.opentelemetry.io/otel/trace"
|
|
"go.opentelemetry.io/otel/trace/noop"
|
|
|
|
"go.opentelemetry.io/auto/sdk/internal/telemetry"
|
|
)
|
|
|
|
type span struct {
|
|
noop.Span
|
|
|
|
spanContext trace.SpanContext
|
|
sampled atomic.Bool
|
|
|
|
mu sync.Mutex
|
|
traces *telemetry.Traces
|
|
span *telemetry.Span
|
|
}
|
|
|
|
func (s *span) SpanContext() trace.SpanContext {
|
|
if s == nil {
|
|
return trace.SpanContext{}
|
|
}
|
|
// s.spanContext is immutable, do not acquire lock s.mu.
|
|
return s.spanContext
|
|
}
|
|
|
|
func (s *span) IsRecording() bool {
|
|
if s == nil {
|
|
return false
|
|
}
|
|
|
|
return s.sampled.Load()
|
|
}
|
|
|
|
func (s *span) SetStatus(c codes.Code, msg string) {
|
|
if s == nil || !s.sampled.Load() {
|
|
return
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if s.span.Status == nil {
|
|
s.span.Status = new(telemetry.Status)
|
|
}
|
|
|
|
s.span.Status.Message = msg
|
|
|
|
switch c {
|
|
case codes.Unset:
|
|
s.span.Status.Code = telemetry.StatusCodeUnset
|
|
case codes.Error:
|
|
s.span.Status.Code = telemetry.StatusCodeError
|
|
case codes.Ok:
|
|
s.span.Status.Code = telemetry.StatusCodeOK
|
|
}
|
|
}
|
|
|
|
func (s *span) SetAttributes(attrs ...attribute.KeyValue) {
|
|
if s == nil || !s.sampled.Load() {
|
|
return
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
limit := maxSpan.Attrs
|
|
if limit == 0 {
|
|
// No attributes allowed.
|
|
s.span.DroppedAttrs += uint32(len(attrs))
|
|
return
|
|
}
|
|
|
|
m := make(map[string]int)
|
|
for i, a := range s.span.Attrs {
|
|
m[a.Key] = i
|
|
}
|
|
|
|
for _, a := range attrs {
|
|
val := convAttrValue(a.Value)
|
|
if val.Empty() {
|
|
s.span.DroppedAttrs++
|
|
continue
|
|
}
|
|
|
|
if idx, ok := m[string(a.Key)]; ok {
|
|
s.span.Attrs[idx] = telemetry.Attr{
|
|
Key: string(a.Key),
|
|
Value: val,
|
|
}
|
|
} else if limit < 0 || len(s.span.Attrs) < limit {
|
|
s.span.Attrs = append(s.span.Attrs, telemetry.Attr{
|
|
Key: string(a.Key),
|
|
Value: val,
|
|
})
|
|
m[string(a.Key)] = len(s.span.Attrs) - 1
|
|
} else {
|
|
s.span.DroppedAttrs++
|
|
}
|
|
}
|
|
}
|
|
|
|
// convCappedAttrs converts up to limit attrs into a []telemetry.Attr. The
|
|
// number of dropped attributes is also returned.
|
|
func convCappedAttrs(limit int, attrs []attribute.KeyValue) ([]telemetry.Attr, uint32) {
|
|
if limit == 0 {
|
|
return nil, uint32(len(attrs))
|
|
}
|
|
|
|
if limit < 0 {
|
|
// Unlimited.
|
|
return convAttrs(attrs), 0
|
|
}
|
|
|
|
limit = min(len(attrs), limit)
|
|
return convAttrs(attrs[:limit]), uint32(len(attrs) - limit)
|
|
}
|
|
|
|
func convAttrs(attrs []attribute.KeyValue) []telemetry.Attr {
|
|
if len(attrs) == 0 {
|
|
// Avoid allocations if not necessary.
|
|
return nil
|
|
}
|
|
|
|
out := make([]telemetry.Attr, 0, len(attrs))
|
|
for _, attr := range attrs {
|
|
key := string(attr.Key)
|
|
val := convAttrValue(attr.Value)
|
|
if val.Empty() {
|
|
continue
|
|
}
|
|
out = append(out, telemetry.Attr{Key: key, Value: val})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func convAttrValue(value attribute.Value) telemetry.Value {
|
|
switch value.Type() {
|
|
case attribute.BOOL:
|
|
return telemetry.BoolValue(value.AsBool())
|
|
case attribute.INT64:
|
|
return telemetry.Int64Value(value.AsInt64())
|
|
case attribute.FLOAT64:
|
|
return telemetry.Float64Value(value.AsFloat64())
|
|
case attribute.STRING:
|
|
v := truncate(maxSpan.AttrValueLen, value.AsString())
|
|
return telemetry.StringValue(v)
|
|
case attribute.BOOLSLICE:
|
|
slice := value.AsBoolSlice()
|
|
out := make([]telemetry.Value, 0, len(slice))
|
|
for _, v := range slice {
|
|
out = append(out, telemetry.BoolValue(v))
|
|
}
|
|
return telemetry.SliceValue(out...)
|
|
case attribute.INT64SLICE:
|
|
slice := value.AsInt64Slice()
|
|
out := make([]telemetry.Value, 0, len(slice))
|
|
for _, v := range slice {
|
|
out = append(out, telemetry.Int64Value(v))
|
|
}
|
|
return telemetry.SliceValue(out...)
|
|
case attribute.FLOAT64SLICE:
|
|
slice := value.AsFloat64Slice()
|
|
out := make([]telemetry.Value, 0, len(slice))
|
|
for _, v := range slice {
|
|
out = append(out, telemetry.Float64Value(v))
|
|
}
|
|
return telemetry.SliceValue(out...)
|
|
case attribute.STRINGSLICE:
|
|
slice := value.AsStringSlice()
|
|
out := make([]telemetry.Value, 0, len(slice))
|
|
for _, v := range slice {
|
|
v = truncate(maxSpan.AttrValueLen, v)
|
|
out = append(out, telemetry.StringValue(v))
|
|
}
|
|
return telemetry.SliceValue(out...)
|
|
}
|
|
return telemetry.Value{}
|
|
}
|
|
|
|
// truncate returns a truncated version of s such that it contains less than
|
|
// the limit number of characters. Truncation is applied by returning the limit
|
|
// number of valid characters contained in s.
|
|
//
|
|
// If limit is negative, it returns the original string.
|
|
//
|
|
// UTF-8 is supported. When truncating, all invalid characters are dropped
|
|
// before applying truncation.
|
|
//
|
|
// If s already contains less than the limit number of bytes, it is returned
|
|
// unchanged. No invalid characters are removed.
|
|
func truncate(limit int, s string) string {
|
|
// This prioritize performance in the following order based on the most
|
|
// common expected use-cases.
|
|
//
|
|
// - Short values less than the default limit (128).
|
|
// - Strings with valid encodings that exceed the limit.
|
|
// - No limit.
|
|
// - Strings with invalid encodings that exceed the limit.
|
|
if limit < 0 || len(s) <= limit {
|
|
return s
|
|
}
|
|
|
|
// Optimistically, assume all valid UTF-8.
|
|
var b strings.Builder
|
|
count := 0
|
|
for i, c := range s {
|
|
if c != utf8.RuneError {
|
|
count++
|
|
if count > limit {
|
|
return s[:i]
|
|
}
|
|
continue
|
|
}
|
|
|
|
_, size := utf8.DecodeRuneInString(s[i:])
|
|
if size == 1 {
|
|
// Invalid encoding.
|
|
b.Grow(len(s) - 1)
|
|
_, _ = b.WriteString(s[:i])
|
|
s = s[i:]
|
|
break
|
|
}
|
|
}
|
|
|
|
// Fast-path, no invalid input.
|
|
if b.Cap() == 0 {
|
|
return s
|
|
}
|
|
|
|
// Truncate while validating UTF-8.
|
|
for i := 0; i < len(s) && count < limit; {
|
|
c := s[i]
|
|
if c < utf8.RuneSelf {
|
|
// Optimization for single byte runes (common case).
|
|
_ = b.WriteByte(c)
|
|
i++
|
|
count++
|
|
continue
|
|
}
|
|
|
|
_, size := utf8.DecodeRuneInString(s[i:])
|
|
if size == 1 {
|
|
// We checked for all 1-byte runes above, this is a RuneError.
|
|
i++
|
|
continue
|
|
}
|
|
|
|
_, _ = b.WriteString(s[i : i+size])
|
|
i += size
|
|
count++
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
func (s *span) End(opts ...trace.SpanEndOption) {
|
|
if s == nil || !s.sampled.Swap(false) {
|
|
return
|
|
}
|
|
|
|
// s.end exists so the lock (s.mu) is not held while s.ended is called.
|
|
s.ended(s.end(opts))
|
|
}
|
|
|
|
func (s *span) end(opts []trace.SpanEndOption) []byte {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
cfg := trace.NewSpanEndConfig(opts...)
|
|
if t := cfg.Timestamp(); !t.IsZero() {
|
|
s.span.EndTime = cfg.Timestamp()
|
|
} else {
|
|
s.span.EndTime = time.Now()
|
|
}
|
|
|
|
b, _ := json.Marshal(s.traces) // TODO: do not ignore this error.
|
|
return b
|
|
}
|
|
|
|
// Expected to be implemented in eBPF.
|
|
//
|
|
//go:noinline
|
|
func (*span) ended(buf []byte) { ended(buf) }
|
|
|
|
// ended is used for testing.
|
|
var ended = func([]byte) {}
|
|
|
|
func (s *span) RecordError(err error, opts ...trace.EventOption) {
|
|
if s == nil || err == nil || !s.sampled.Load() {
|
|
return
|
|
}
|
|
|
|
cfg := trace.NewEventConfig(opts...)
|
|
|
|
attrs := cfg.Attributes()
|
|
attrs = append(attrs,
|
|
semconv.ExceptionType(typeStr(err)),
|
|
semconv.ExceptionMessage(err.Error()),
|
|
)
|
|
if cfg.StackTrace() {
|
|
buf := make([]byte, 2048)
|
|
n := runtime.Stack(buf, false)
|
|
attrs = append(attrs, semconv.ExceptionStacktrace(string(buf[0:n])))
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.addEvent(semconv.ExceptionEventName, cfg.Timestamp(), attrs)
|
|
}
|
|
|
|
func typeStr(i any) string {
|
|
t := reflect.TypeOf(i)
|
|
if t.PkgPath() == "" && t.Name() == "" {
|
|
// Likely a builtin type.
|
|
return t.String()
|
|
}
|
|
return fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
|
|
}
|
|
|
|
func (s *span) AddEvent(name string, opts ...trace.EventOption) {
|
|
if s == nil || !s.sampled.Load() {
|
|
return
|
|
}
|
|
|
|
cfg := trace.NewEventConfig(opts...)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.addEvent(name, cfg.Timestamp(), cfg.Attributes())
|
|
}
|
|
|
|
// addEvent adds an event with name and attrs at tStamp to the span. The span
|
|
// lock (s.mu) needs to be held by the caller.
|
|
func (s *span) addEvent(name string, tStamp time.Time, attrs []attribute.KeyValue) {
|
|
limit := maxSpan.Events
|
|
|
|
if limit == 0 {
|
|
s.span.DroppedEvents++
|
|
return
|
|
}
|
|
|
|
if limit > 0 && len(s.span.Events) == limit {
|
|
// Drop head while avoiding allocation of more capacity.
|
|
copy(s.span.Events[:limit-1], s.span.Events[1:])
|
|
s.span.Events = s.span.Events[:limit-1]
|
|
s.span.DroppedEvents++
|
|
}
|
|
|
|
e := &telemetry.SpanEvent{Time: tStamp, Name: name}
|
|
e.Attrs, e.DroppedAttrs = convCappedAttrs(maxSpan.EventAttrs, attrs)
|
|
|
|
s.span.Events = append(s.span.Events, e)
|
|
}
|
|
|
|
func (s *span) AddLink(link trace.Link) {
|
|
if s == nil || !s.sampled.Load() {
|
|
return
|
|
}
|
|
|
|
l := maxSpan.Links
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if l == 0 {
|
|
s.span.DroppedLinks++
|
|
return
|
|
}
|
|
|
|
if l > 0 && len(s.span.Links) == l {
|
|
// Drop head while avoiding allocation of more capacity.
|
|
copy(s.span.Links[:l-1], s.span.Links[1:])
|
|
s.span.Links = s.span.Links[:l-1]
|
|
s.span.DroppedLinks++
|
|
}
|
|
|
|
s.span.Links = append(s.span.Links, convLink(link))
|
|
}
|
|
|
|
func convLinks(links []trace.Link) []*telemetry.SpanLink {
|
|
out := make([]*telemetry.SpanLink, 0, len(links))
|
|
for _, link := range links {
|
|
out = append(out, convLink(link))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func convLink(link trace.Link) *telemetry.SpanLink {
|
|
l := &telemetry.SpanLink{
|
|
TraceID: telemetry.TraceID(link.SpanContext.TraceID()),
|
|
SpanID: telemetry.SpanID(link.SpanContext.SpanID()),
|
|
TraceState: link.SpanContext.TraceState().String(),
|
|
Flags: uint32(link.SpanContext.TraceFlags()),
|
|
}
|
|
l.Attrs, l.DroppedAttrs = convCappedAttrs(maxSpan.LinkAttrs, link.Attributes)
|
|
|
|
return l
|
|
}
|
|
|
|
func (s *span) SetName(name string) {
|
|
if s == nil || !s.sampled.Load() {
|
|
return
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.span.Name = name
|
|
}
|
|
|
|
func (*span) TracerProvider() trace.TracerProvider { return TracerProvider() }
|