mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-18 00:47:48 +08:00
492 lines
15 KiB
Go
492 lines
15 KiB
Go
package otelutil
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/sdk/instrumentation"
|
|
"go.opentelemetry.io/otel/sdk/resource"
|
|
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
|
"go.opentelemetry.io/otel/trace"
|
|
)
|
|
|
|
// Span is a type similar to otel's SpanStub, but with the correct types needed
|
|
// for handle marshaling and unmarshalling.
|
|
type Span struct {
|
|
// Name is the name of a specific span
|
|
Name string
|
|
// SpanContext is the unique SpanContext that identifies the span
|
|
SpanContext trace.SpanContext
|
|
// Parten is the unique SpanContext that identifies the parent of the span.
|
|
// If the span has no parent, this span context will be invalid.
|
|
Parent trace.SpanContext
|
|
// SpanKind is the role the span plays in a Trace
|
|
SpanKind trace.SpanKind
|
|
// StartTime is the time the span started recording
|
|
StartTime time.Time
|
|
// EndTime returns the time the span stopped recording
|
|
EndTime time.Time
|
|
// Attributes are the defining attributes of a span
|
|
Attributes []attribute.KeyValue
|
|
// Events are all the events that occurred within the span
|
|
Events []tracesdk.Event
|
|
// Links are all the links the span has to other spans
|
|
Links []tracesdk.Link
|
|
// Status is that span status
|
|
Status tracesdk.Status
|
|
// DroppedAttributes is the number of attributes dropped by the span due to
|
|
// a limit being reached
|
|
DroppedAttributes int
|
|
// DroppedEvents is the number of attributes dropped by the span due to a
|
|
// limit being reached
|
|
DroppedEvents int
|
|
// DroppedLinks is the number of links dropped by the span due to a limit
|
|
// being reached
|
|
DroppedLinks int
|
|
// ChildSpanCount is the count of spans that consider the span a direct
|
|
// parent
|
|
ChildSpanCount int
|
|
// Resource is the information about the entity that produced the span
|
|
// We have to change this type from the otel type to make this struct
|
|
// marshallable
|
|
Resource []attribute.KeyValue
|
|
// InstrumentationLibrary is information about the library that produced
|
|
// the span
|
|
//nolint:staticcheck
|
|
InstrumentationLibrary instrumentation.Library
|
|
}
|
|
|
|
type Spans []Span
|
|
|
|
// Len return the length of the Spans.
|
|
func (s Spans) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
// ReadOnlySpans return a list of tracesdk.ReadOnlySpan from span stubs.
|
|
func (s Spans) ReadOnlySpans() []tracesdk.ReadOnlySpan {
|
|
roSpans := make([]tracesdk.ReadOnlySpan, len(s))
|
|
for i := range s {
|
|
roSpans[i] = s[i].Snapshot()
|
|
}
|
|
return roSpans
|
|
}
|
|
|
|
// ParseSpanStubs parses BuildKit trace data into a list of SpanStubs.
|
|
func ParseSpanStubs(rdr io.Reader) (Spans, error) {
|
|
var spanStubs []Span
|
|
decoder := json.NewDecoder(rdr)
|
|
for {
|
|
var span Span
|
|
if err := decoder.Decode(&span); err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return nil, errors.Wrapf(err, "error decoding JSON")
|
|
}
|
|
spanStubs = append(spanStubs, span)
|
|
}
|
|
return spanStubs, nil
|
|
}
|
|
|
|
// spanData is data that we need to unmarshal in custom ways.
|
|
type spanData struct {
|
|
Name string
|
|
SpanContext spanContext
|
|
Parent spanContext
|
|
SpanKind trace.SpanKind
|
|
StartTime time.Time
|
|
EndTime time.Time
|
|
Attributes []keyValue
|
|
Events []event
|
|
Links []link
|
|
Status tracesdk.Status
|
|
DroppedAttributes int
|
|
DroppedEvents int
|
|
DroppedLinks int
|
|
ChildSpanCount int
|
|
Resource []keyValue // change this type from the otel type to make this struct marshallable
|
|
//nolint:staticcheck
|
|
InstrumentationLibrary instrumentation.Library
|
|
}
|
|
|
|
// spanContext is a custom type used to unmarshal otel SpanContext correctly.
|
|
type spanContext struct {
|
|
TraceID string
|
|
SpanID string
|
|
TraceFlags string
|
|
TraceState string // TODO: implement, currently dropped
|
|
Remote bool
|
|
}
|
|
|
|
// event is a custom type used to unmarshal otel Event correctly.
|
|
type event struct {
|
|
Name string
|
|
Attributes []keyValue
|
|
DroppedAttributeCount int
|
|
Time time.Time
|
|
}
|
|
|
|
// link is a custom type used to unmarshal otel Link correctly.
|
|
type link struct {
|
|
SpanContext spanContext
|
|
Attributes []keyValue
|
|
DroppedAttributeCount int
|
|
}
|
|
|
|
// keyValue is a custom type used to unmarshal otel KeyValue correctly.
|
|
type keyValue struct {
|
|
Key string
|
|
Value value
|
|
}
|
|
|
|
// value is a custom type used to unmarshal otel Value correctly.
|
|
type value struct {
|
|
Type string
|
|
Value any
|
|
}
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler for Span which allows correctly
|
|
// retrieving attribute.KeyValue values.
|
|
func (s *Span) UnmarshalJSON(data []byte) error {
|
|
var sd spanData
|
|
if err := json.NewDecoder(bytes.NewReader(data)).Decode(&sd); err != nil {
|
|
return errors.Wrap(err, "unable to decode to spanData")
|
|
}
|
|
|
|
s.Name = sd.Name
|
|
s.SpanKind = sd.SpanKind
|
|
s.StartTime = sd.StartTime
|
|
s.EndTime = sd.EndTime
|
|
s.Status = sd.Status
|
|
s.DroppedAttributes = sd.DroppedAttributes
|
|
s.DroppedEvents = sd.DroppedEvents
|
|
s.DroppedLinks = sd.DroppedLinks
|
|
s.ChildSpanCount = sd.ChildSpanCount
|
|
s.InstrumentationLibrary = sd.InstrumentationLibrary
|
|
|
|
spanCtx, err := sd.SpanContext.asTraceSpanContext()
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to decode spanCtx")
|
|
}
|
|
s.SpanContext = spanCtx
|
|
|
|
parent, err := sd.Parent.asTraceSpanContext()
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to decode parent")
|
|
}
|
|
s.Parent = parent
|
|
|
|
var attributes []attribute.KeyValue
|
|
for _, a := range sd.Attributes {
|
|
kv, err := a.asAttributeKeyValue()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to decode attribute (%s)", a.Key)
|
|
}
|
|
attributes = append(attributes, kv)
|
|
}
|
|
s.Attributes = attributes
|
|
|
|
var events []tracesdk.Event
|
|
for _, e := range sd.Events {
|
|
var eventAttributes []attribute.KeyValue
|
|
for _, a := range e.Attributes {
|
|
kv, err := a.asAttributeKeyValue()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to decode event attribute (%s)", a.Key)
|
|
}
|
|
eventAttributes = append(eventAttributes, kv)
|
|
}
|
|
events = append(events, tracesdk.Event{
|
|
Name: e.Name,
|
|
Attributes: eventAttributes,
|
|
DroppedAttributeCount: e.DroppedAttributeCount,
|
|
Time: e.Time,
|
|
})
|
|
}
|
|
s.Events = events
|
|
|
|
var links []tracesdk.Link
|
|
for _, l := range sd.Links {
|
|
linkSpanCtx, err := l.SpanContext.asTraceSpanContext()
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to decode linkSpanCtx")
|
|
}
|
|
var linkAttributes []attribute.KeyValue
|
|
for _, a := range l.Attributes {
|
|
kv, err := a.asAttributeKeyValue()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to decode link attribute (%s)", a.Key)
|
|
}
|
|
linkAttributes = append(linkAttributes, kv)
|
|
}
|
|
links = append(links, tracesdk.Link{
|
|
SpanContext: linkSpanCtx,
|
|
Attributes: linkAttributes,
|
|
DroppedAttributeCount: l.DroppedAttributeCount,
|
|
})
|
|
}
|
|
s.Links = links
|
|
|
|
var resources []attribute.KeyValue
|
|
for _, r := range sd.Resource {
|
|
kv, err := r.asAttributeKeyValue()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to decode resource (%s)", r.Key)
|
|
}
|
|
resources = append(resources, kv)
|
|
}
|
|
s.Resource = resources
|
|
|
|
return nil
|
|
}
|
|
|
|
// asTraceSpanContext converts the internal spanContext representation to an
|
|
// otel one.
|
|
func (sc *spanContext) asTraceSpanContext() (trace.SpanContext, error) {
|
|
traceID, err := traceIDFromHex(sc.TraceID)
|
|
if err != nil {
|
|
return trace.SpanContext{}, errors.Wrap(err, "unable to parse trace id")
|
|
}
|
|
spanID, err := spanIDFromHex(sc.SpanID)
|
|
if err != nil {
|
|
return trace.SpanContext{}, errors.Wrap(err, "unable to parse span id")
|
|
}
|
|
traceFlags := trace.TraceFlags(0x00)
|
|
if sc.TraceFlags == "01" {
|
|
traceFlags = trace.TraceFlags(0x01)
|
|
}
|
|
config := trace.SpanContextConfig{
|
|
TraceID: traceID,
|
|
SpanID: spanID,
|
|
TraceFlags: traceFlags,
|
|
Remote: sc.Remote,
|
|
}
|
|
return trace.NewSpanContext(config), nil
|
|
}
|
|
|
|
// asAttributeKeyValue converts the internal keyValue representation to an
|
|
// otel one.
|
|
func (kv *keyValue) asAttributeKeyValue() (attribute.KeyValue, error) {
|
|
// value types get encoded as string
|
|
switch kv.Value.Type {
|
|
case attribute.INVALID.String():
|
|
return attribute.KeyValue{}, errors.New("invalid value type")
|
|
case attribute.BOOL.String():
|
|
return attribute.Bool(kv.Key, kv.Value.Value.(bool)), nil
|
|
case attribute.INT64.String():
|
|
// value could be int64 or float64, so handle both cases (float64 comes
|
|
// from json unmarshal)
|
|
var v int64
|
|
switch i := kv.Value.Value.(type) {
|
|
case int64:
|
|
v = i
|
|
case float64:
|
|
v = int64(i)
|
|
}
|
|
return attribute.Int64(kv.Key, v), nil
|
|
case attribute.FLOAT64.String():
|
|
return attribute.Float64(kv.Key, kv.Value.Value.(float64)), nil
|
|
case attribute.STRING.String():
|
|
return attribute.String(kv.Key, kv.Value.Value.(string)), nil
|
|
case attribute.BOOLSLICE.String():
|
|
return attribute.BoolSlice(kv.Key, kv.Value.Value.([]bool)), nil
|
|
case attribute.INT64SLICE.String():
|
|
// handle both float64 and int64 (float64 comes from json unmarshal)
|
|
var v []int64
|
|
switch sli := kv.Value.Value.(type) {
|
|
case []int64:
|
|
v = sli
|
|
case []float64:
|
|
for i := range sli {
|
|
v = append(v, int64(sli[i]))
|
|
}
|
|
}
|
|
return attribute.Int64Slice(kv.Key, v), nil
|
|
case attribute.FLOAT64SLICE.String():
|
|
return attribute.Float64Slice(kv.Key, kv.Value.Value.([]float64)), nil
|
|
case attribute.STRINGSLICE.String():
|
|
var strSli []string
|
|
// sometimes we can get an []interface{} instead of a []string, so
|
|
// always cast to []string if that happens.
|
|
switch sli := kv.Value.Value.(type) {
|
|
case []string:
|
|
strSli = sli
|
|
case []any:
|
|
for i := range sli {
|
|
var v string
|
|
// best case we have a string, otherwise, cast it using
|
|
// fmt.Sprintf
|
|
if str, ok := sli[i].(string); ok {
|
|
v = str
|
|
} else {
|
|
v = fmt.Sprintf("%v", sli[i])
|
|
}
|
|
// add the string to the slice
|
|
strSli = append(strSli, v)
|
|
}
|
|
default:
|
|
return attribute.KeyValue{}, errors.Errorf("got unsupported type %q for %s", reflect.ValueOf(kv.Value.Value).Kind(), attribute.STRINGSLICE.String())
|
|
}
|
|
return attribute.StringSlice(kv.Key, strSli), nil
|
|
default:
|
|
return attribute.KeyValue{}, errors.Errorf("unknown value type %s", kv.Value.Type)
|
|
}
|
|
}
|
|
|
|
// traceIDFromHex returns a TraceID from a hex string if it is compliant with
|
|
// the W3C trace-context specification and removes the validity check.
|
|
// https://www.w3.org/TR/trace-context/#trace-id
|
|
func traceIDFromHex(h string) (trace.TraceID, error) {
|
|
t := trace.TraceID{}
|
|
if len(h) != 32 {
|
|
return t, errors.New("unable to parse trace id")
|
|
}
|
|
if err := decodeHex(h, t[:]); err != nil {
|
|
return t, err
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
// spanIDFromHex returns a SpanID from a hex string if it is compliant with the
|
|
// W3C trace-context specification and removes the validity check.
|
|
// https://www.w3.org/TR/trace-context/#parent-id
|
|
func spanIDFromHex(h string) (trace.SpanID, error) {
|
|
s := trace.SpanID{}
|
|
if len(h) != 16 {
|
|
return s, errors.New("unable to parse span id of length: %d")
|
|
}
|
|
if err := decodeHex(h, s[:]); err != nil {
|
|
return s, err
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// decodeHex decodes hex in a manner compliant with otel.
|
|
func decodeHex(h string, b []byte) error {
|
|
for _, r := range h {
|
|
switch {
|
|
case 'a' <= r && r <= 'f':
|
|
continue
|
|
case '0' <= r && r <= '9':
|
|
continue
|
|
default:
|
|
return errors.New("unable to parse hex id")
|
|
}
|
|
}
|
|
decoded, err := hex.DecodeString(h)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
copy(b, decoded)
|
|
return nil
|
|
}
|
|
|
|
// Snapshot turns a Span into a ReadOnlySpan which is exportable by otel.
|
|
func (s *Span) Snapshot() tracesdk.ReadOnlySpan {
|
|
return spanSnapshot{
|
|
name: s.Name,
|
|
spanContext: s.SpanContext,
|
|
parent: s.Parent,
|
|
spanKind: s.SpanKind,
|
|
startTime: s.StartTime,
|
|
endTime: s.EndTime,
|
|
attributes: s.Attributes,
|
|
events: s.Events,
|
|
links: s.Links,
|
|
status: s.Status,
|
|
droppedAttributes: s.DroppedAttributes,
|
|
droppedEvents: s.DroppedEvents,
|
|
droppedLinks: s.DroppedLinks,
|
|
childSpanCount: s.ChildSpanCount,
|
|
resource: resource.NewSchemaless(s.Resource...),
|
|
instrumentationScope: s.InstrumentationLibrary,
|
|
}
|
|
}
|
|
|
|
// spanSnapshot is a helper type for transforming a Span into a ReadOnlySpan.
|
|
type spanSnapshot struct {
|
|
// Embed the interface to implement the private method.
|
|
tracesdk.ReadOnlySpan
|
|
|
|
name string
|
|
spanContext trace.SpanContext
|
|
parent trace.SpanContext
|
|
spanKind trace.SpanKind
|
|
startTime time.Time
|
|
endTime time.Time
|
|
attributes []attribute.KeyValue
|
|
events []tracesdk.Event
|
|
links []tracesdk.Link
|
|
status tracesdk.Status
|
|
droppedAttributes int
|
|
droppedEvents int
|
|
droppedLinks int
|
|
childSpanCount int
|
|
resource *resource.Resource
|
|
instrumentationScope instrumentation.Scope
|
|
}
|
|
|
|
// Name returns the Name of the snapshot
|
|
func (s spanSnapshot) Name() string { return s.name }
|
|
|
|
// SpanContext returns the SpanContext of the snapshot
|
|
func (s spanSnapshot) SpanContext() trace.SpanContext { return s.spanContext }
|
|
|
|
// Parent returns the Parent of the snapshot
|
|
func (s spanSnapshot) Parent() trace.SpanContext { return s.parent }
|
|
|
|
// SpanKind returns the SpanKind of the snapshot
|
|
func (s spanSnapshot) SpanKind() trace.SpanKind { return s.spanKind }
|
|
|
|
// StartTime returns the StartTime of the snapshot
|
|
func (s spanSnapshot) StartTime() time.Time { return s.startTime }
|
|
|
|
// EndTime returns the EndTime of the snapshot
|
|
func (s spanSnapshot) EndTime() time.Time { return s.endTime }
|
|
|
|
// Attributes returns the Attributes of the snapshot
|
|
func (s spanSnapshot) Attributes() []attribute.KeyValue { return s.attributes }
|
|
|
|
// Links returns the Links of the snapshot
|
|
func (s spanSnapshot) Links() []tracesdk.Link { return s.links }
|
|
|
|
// Events return the Events of the snapshot
|
|
func (s spanSnapshot) Events() []tracesdk.Event { return s.events }
|
|
|
|
// Status returns the Status of the snapshot
|
|
func (s spanSnapshot) Status() tracesdk.Status { return s.status }
|
|
|
|
// DroppedAttributes returns the DroppedAttributes of the snapshot
|
|
func (s spanSnapshot) DroppedAttributes() int { return s.droppedAttributes }
|
|
|
|
// DroppedLinks returns the DroppedLinks of the snapshot
|
|
func (s spanSnapshot) DroppedLinks() int { return s.droppedLinks }
|
|
|
|
// DroppedEvents returns the DroppedEvents of the snapshot
|
|
func (s spanSnapshot) DroppedEvents() int { return s.droppedEvents }
|
|
|
|
// ChildSpanCount returns the ChildSpanCount of the snapshot
|
|
func (s spanSnapshot) ChildSpanCount() int { return s.childSpanCount }
|
|
|
|
// Resource returns the Resource of the snapshot
|
|
func (s spanSnapshot) Resource() *resource.Resource { return s.resource }
|
|
|
|
// InstrumentationScope returns the InstrumentationScope of the snapshot
|
|
func (s spanSnapshot) InstrumentationScope() instrumentation.Scope {
|
|
return s.instrumentationScope
|
|
}
|
|
|
|
// InstrumentationLibrary returns the InstrumentationLibrary of the snapshot
|
|
//
|
|
//nolint:staticcheck
|
|
func (s spanSnapshot) InstrumentationLibrary() instrumentation.Library {
|
|
return s.instrumentationScope
|
|
}
|