mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 10:03:42 +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 interface{}
 | 
						|
}
 | 
						|
 | 
						|
// 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 []interface{}:
 | 
						|
			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
 | 
						|
}
 |