Skip to content

Commit

Permalink
api(trace): change SpanID to byte array (#241)
Browse files Browse the repository at this point in the history
* api(trace): change SpanID to byte array

* fix doc and create const errors
  • Loading branch information
paivagustavo authored and rghetia committed Oct 28, 2019
1 parent d9c4aa5 commit 4e545e2
Show file tree
Hide file tree
Showing 26 changed files with 194 additions and 212 deletions.
110 changes: 76 additions & 34 deletions api/core/span_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import (
"bytes"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
)

const (
Expand All @@ -30,91 +28,135 @@ const (
// for SpanContext TraceFlags field when a trace is sampled.
TraceFlagsSampled = traceFlagsBitMaskSampled
TraceFlagsUnused = traceFlagsBitMaskUnused

ErrInvalidHexID errorConst = "trace-id and span-id can only contain [0-9a-f] characters, all lowercase"

ErrInvalidTraceIDLength errorConst = "hex encoded trace-id must have length equals to 32"
ErrNilTraceID errorConst = "trace-id can't be all zero"

ErrInvalidSpanIDLength errorConst = "hex encoded span-id must have length equals to 16"
ErrNilSpanID errorConst = "span-id can't be all zero"
)

type errorConst string

func (e errorConst) Error() string {
return string(e)
}

type TraceID [16]byte

var nilTraceID TraceID
var _ json.Marshaler = nilTraceID

func (t TraceID) isValid() bool {
func (t TraceID) IsValid() bool {
return !bytes.Equal(t[:], nilTraceID[:])
}

// MarshalJSON implements a custom marshal function to encode TraceID as a hex string
func (t TraceID) MarshalJSON() ([]byte, error) {
return json.Marshal(hex.EncodeToString(t[:]))
}

type SpanID [8]byte

var nilSpanID SpanID
var _ json.Marshaler = nilSpanID

func (s SpanID) IsValid() bool {
return !bytes.Equal(s[:], nilSpanID[:])
}

// MarshalJSON implements a custom marshal function to encode SpanID as a hex string
func (s SpanID) MarshalJSON() ([]byte, error) {
return json.Marshal(hex.EncodeToString(s[:]))
}

// TraceIDFromHex returns a TraceID from a hex string if it is compliant
// with the w3c trace-context specification.
// See more at https://www.w3.org/TR/trace-context/#trace-id
func TraceIDFromHex(h string) (TraceID, error) {
t := TraceID{}
if len(h) != 32 {
return t, errors.New("hex encoded trace-id must have length equals to 32")
return t, ErrInvalidTraceIDLength
}

if err := decodeHex(h, t[:]); err != nil {
return t, err
}

if !t.IsValid() {
return t, ErrNilTraceID
}
return t, nil
}

// SpanIDFromHex returns a SpanID from a hex string if it is compliant
// with the w3c trace-context specification.
// See more at https://www.w3.org/TR/trace-context/#parent-id
func SpanIDFromHex(h string) (SpanID, error) {
s := SpanID{}
if len(h) != 16 {
return s, ErrInvalidSpanIDLength
}

if err := decodeHex(h, s[:]); err != nil {
return s, err
}

if !s.IsValid() {
return s, ErrNilSpanID
}
return s, nil
}

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 t, errors.New("trace-id can only contain [0-9a-f] characters, all lowercase")
return ErrInvalidHexID
}
}

b, err := hex.DecodeString(h)
decoded, err := hex.DecodeString(h)
if err != nil {
return t, err
return err
}
copy(t[:], b)

if !t.isValid() {
return t, errors.New("trace-id can't be all zero")
}
return t, nil
copy(b[:], decoded)
return nil
}

type SpanContext struct {
TraceID TraceID
SpanID uint64
SpanID SpanID
TraceFlags byte
}

var _ json.Marshaler = (*SpanContext)(nil)

// EmptySpanContext is meant for internal use to return invalid span context during error conditions.
func EmptySpanContext() SpanContext {
return SpanContext{}
}

// MarshalJSON implements a custom marshal function to encode SpanContext
// in a human readable format with hex encoded TraceID and SpanID.
func (sc SpanContext) MarshalJSON() ([]byte, error) {
type JSONSpanContext struct {
TraceID string
SpanID string
TraceFlags byte
}

return json.Marshal(JSONSpanContext{
TraceID: sc.TraceIDString(),
SpanID: sc.SpanIDString(),
TraceFlags: sc.TraceFlags,
})
}

func (sc SpanContext) IsValid() bool {
return sc.HasTraceID() && sc.HasSpanID()
}

func (sc SpanContext) HasTraceID() bool {
return sc.TraceID.isValid()
return sc.TraceID.IsValid()
}

func (sc SpanContext) HasSpanID() bool {
return sc.SpanID != 0
return sc.SpanID.IsValid()
}

func (sc SpanContext) SpanIDString() string {
return fmt.Sprintf("%.16x", sc.SpanID)
return hex.EncodeToString(sc.SpanID[:])

}

func (sc SpanContext) TraceIDString() string {
Expand Down
18 changes: 9 additions & 9 deletions api/core/span_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,28 @@ func TestIsValid(t *testing.T) {
for _, testcase := range []struct {
name string
tid core.TraceID
sid uint64
sid core.SpanID
want bool
}{
{
name: "SpanContext.IsValid() returns true if sc has both an Trace ID and Span ID",
tid: core.TraceID([16]byte{1}),
sid: uint64(42),
tid: [16]byte{1},
sid: [8]byte{42},
want: true,
}, {
name: "SpanContext.IsValid() returns false if sc has neither an Trace ID nor Span ID",
tid: core.TraceID([16]byte{}),
sid: uint64(0),
sid: [8]byte{},
want: false,
}, {
name: "SpanContext.IsValid() returns false if sc has a Span ID but not a Trace ID",
tid: core.TraceID([16]byte{}),
sid: uint64(42),
sid: [8]byte{42},
want: false,
}, {
name: "SpanContext.IsValid() returns false if sc has a Trace ID but not a Span ID",
tid: core.TraceID([16]byte{1}),
sid: uint64(0),
sid: [8]byte{},
want: false,
},
} {
Expand Down Expand Up @@ -141,7 +141,7 @@ func TestHasSpanID(t *testing.T) {
}{
{
name: "SpanContext.HasSpanID() returns true if self.SpanID != 0",
sc: core.SpanContext{SpanID: uint64(42)},
sc: core.SpanContext{SpanID: [8]byte{42}},
want: true,
}, {
name: "SpanContext.HasSpanID() returns false if self.SpanID == 0",
Expand All @@ -167,8 +167,8 @@ func TestSpanIDString(t *testing.T) {
}{
{
name: "SpanContext.SpanIDString returns string representation of self.TraceID values > 0",
sc: core.SpanContext{SpanID: uint64(42)},
want: `000000000000002a`,
sc: core.SpanContext{SpanID: [8]byte{42}},
want: `2a00000000000000`,
}, {
name: "SpanContext.SpanIDString returns string representation of self.TraceID values == 0",
sc: core.SpanContext{},
Expand Down
16 changes: 2 additions & 14 deletions experimental/bridge/opentracing/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"sync"

Expand Down Expand Up @@ -512,7 +511,7 @@ func (t *BridgeTracer) Inject(sm ot.SpanContext, format interface{}, carrier int
return ot.ErrInvalidCarrier
}
hhcarrier.Set(traceIDHeader, bridgeSC.otelSpanContext.TraceIDString())
hhcarrier.Set(spanIDHeader, spanIDToString(bridgeSC.otelSpanContext.SpanID))
hhcarrier.Set(spanIDHeader, bridgeSC.otelSpanContext.SpanIDString())
hhcarrier.Set(traceFlagsHeader, traceFlagsToString(bridgeSC.otelSpanContext.TraceFlags))
bridgeSC.ForeachBaggageItem(func(k, v string) bool {
// we assume that keys are already canonicalized
Expand All @@ -522,10 +521,6 @@ func (t *BridgeTracer) Inject(sm ot.SpanContext, format interface{}, carrier int
return nil
}

func spanIDToString(spanID uint64) string {
return fmt.Sprintf("%.16x", spanID)
}

func traceFlagsToString(opts byte) string {
var parts []string
if opts&otelcore.TraceFlagsSampled == otelcore.TraceFlagsSampled {
Expand Down Expand Up @@ -557,7 +552,7 @@ func (t *BridgeTracer) Extract(format interface{}, carrier interface{}) (ot.Span
}
bridgeSC.otelSpanContext.TraceID = traceID
case spanIDHeader:
spanID, err := spanIDFromString(v)
spanID, err := otelcore.SpanIDFromHex(v)
if err != nil {
return err
}
Expand All @@ -581,13 +576,6 @@ func (t *BridgeTracer) Extract(format interface{}, carrier interface{}) (ot.Span
return bridgeSC, nil
}

func spanIDFromString(s string) (uint64, error) {
if len(s) != 16 {
return 0, fmt.Errorf("invalid span ID")
}
return strconv.ParseUint(s, 16, 64)
}

func stringToTraceFlags(s string) byte {
var opts byte
for _, part := range strings.Split(s, ",") {
Expand Down
20 changes: 12 additions & 8 deletions experimental/bridge/opentracing/internal/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ type MockTracer struct {
Resources oteldctx.Map
FinishedSpans []*MockSpan
SpareTraceIDs []otelcore.TraceID
SpareSpanIDs []uint64
SpareSpanIDs []otelcore.SpanID
SpareContextKeyValues []MockContextKeyValue

randLock sync.Mutex
Expand Down Expand Up @@ -153,11 +153,11 @@ func (t *MockTracer) getTraceID(ctx context.Context, spanOpts *oteltrace.SpanOpt
return t.getRandTraceID()
}

func (t *MockTracer) getParentSpanID(ctx context.Context, spanOpts *oteltrace.SpanOptions) uint64 {
func (t *MockTracer) getParentSpanID(ctx context.Context, spanOpts *oteltrace.SpanOptions) otelcore.SpanID {
if parent := t.getParentSpanContext(ctx, spanOpts); parent.IsValid() {
return parent.SpanID
}
return 0
return otelcore.SpanID{}
}

func (t *MockTracer) getParentSpanContext(ctx context.Context, spanOpts *oteltrace.SpanOptions) otelcore.SpanContext {
Expand All @@ -171,7 +171,7 @@ func (t *MockTracer) getParentSpanContext(ctx context.Context, spanOpts *oteltra
return otelcore.EmptySpanContext()
}

func (t *MockTracer) getSpanID() uint64 {
func (t *MockTracer) getSpanID() otelcore.SpanID {
if len(t.SpareSpanIDs) > 0 {
spanID := t.SpareSpanIDs[0]
t.SpareSpanIDs = t.SpareSpanIDs[1:]
Expand All @@ -180,13 +180,17 @@ func (t *MockTracer) getSpanID() uint64 {
}
return spanID
}
return t.getRandUint64()
return t.getRandSpanID()
}

func (t *MockTracer) getRandUint64() uint64 {
func (t *MockTracer) getRandSpanID() otelcore.SpanID {
t.randLock.Lock()
defer t.randLock.Unlock()
return t.rand.Uint64()

sid := otelcore.SpanID{}
t.rand.Read(sid[:])

return sid
}

func (t *MockTracer) getRandTraceID() otelcore.TraceID {
Expand Down Expand Up @@ -220,7 +224,7 @@ type MockSpan struct {
Attributes oteldctx.Map
StartTime time.Time
EndTime time.Time
ParentSpanID uint64
ParentSpanID otelcore.SpanID
Events []MockEvent
}

Expand Down
Loading

0 comments on commit 4e545e2

Please sign in to comment.