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 {}
}
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 {
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 ++
}
}
}
func convCappedAttrs(limit int , attrs []attribute .KeyValue ) ([]telemetry .Attr , uint32 ) {
if limit == 0 {
return nil , uint32 (len (attrs ))
}
if limit < 0 {
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 {
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 {}
}
func truncate(limit int , s string ) string {
if limit < 0 || len (s ) <= limit {
return s
}
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 {
b .Grow (len (s ) - 1 )
_, _ = b .WriteString (s [:i ])
s = s [i :]
break
}
}
if b .Cap () == 0 {
return s
}
for i := 0 ; i < len (s ) && count < limit ; {
c := s [i ]
if c < utf8 .RuneSelf {
_ = b .WriteByte (c )
i ++
count ++
continue
}
_ , size := utf8 .DecodeRuneInString (s [i :])
if size == 1 {
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 .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 )
return b
}
func (*span ) ended (buf []byte ) { ended (buf ) }
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 () == "" {
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 ())
}
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 {
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 {
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 () }
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .