Skip to content
This repository has been archived by the owner on Aug 17, 2020. It is now read-only.

Trace context support in propagation Inject/Extract #105

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ func NewAgent(options ...Option) (*Agent, error) {

agent.tracer = tracer.NewWithOptions(tracer.Options{
Recorder: recorder,
ShouldSample: func(traceID uint64) bool {
ShouldSample: func(traceID uuid.UUID) bool {
return true
},
MaxLogsPerSpan: 10000,
Expand Down
2 changes: 1 addition & 1 deletion agent/recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ func (r *SpanRecorder) getPayloadComponents(span tracer.RawSpan) (PayloadSpan, [
}
events = append(events, PayloadEvent{
"context": map[string]interface{}{
"trace_id": fmt.Sprintf("%x", span.Context.TraceID),
"trace_id": span.Context.TraceID.String(),
"span_id": fmt.Sprintf("%x", span.Context.SpanID),
"event_id": eventId.String(),
},
Expand Down
3 changes: 2 additions & 1 deletion tracer/api_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tracer

import (
"github.com/google/uuid"
"testing"

ot "github.com/opentracing/opentracing-go"
Expand All @@ -11,7 +12,7 @@ import (
func newTracer() (tracer ot.Tracer, closer func()) {
tracer = NewWithOptions(Options{
Recorder: NewInMemoryRecorder(),
ShouldSample: func(traceID uint64) bool { return true }, // always sample
ShouldSample: func(traceID uuid.UUID) bool { return true }, // always sample
})
return tracer, nil
}
Expand Down
3 changes: 2 additions & 1 deletion tracer/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tracer
import (
"bytes"
"fmt"
"github.com/google/uuid"
"net/http"
"testing"

Expand Down Expand Up @@ -80,7 +81,7 @@ func BenchmarkTrimmedSpan_100Events_100Tags_100BaggageItems(b *testing.B) {
var r CountingRecorder
opts := DefaultOptions()
opts.TrimUnsampledSpans = true
opts.ShouldSample = func(_ uint64) bool { return false }
opts.ShouldSample = func(_ uuid.UUID) bool { return false }
opts.Recorder = &r
t := NewWithOptions(opts)
benchmarkWithOpsAndCB(b, func() opentracing.Span {
Expand Down
4 changes: 3 additions & 1 deletion tracer/context.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package tracer

import "github.com/google/uuid"

// SpanContext holds the basic Span metadata.
type SpanContext struct {
// A probabilistically unique identifier for a [multi-span] trace.
TraceID uint64
TraceID uuid.UUID

// A probabilistically unique identifier for a span.
SpanID uint64
Expand Down
9 changes: 6 additions & 3 deletions tracer/propagation.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package tracer

import opentracing "github.com/opentracing/opentracing-go"
import (
"github.com/google/uuid"
opentracing "github.com/opentracing/opentracing-go"
)

type accessorPropagator struct {
tracer *tracerImpl
Expand All @@ -10,8 +13,8 @@ type accessorPropagator struct {
// by types which have a means of storing the trace metadata and already know
// how to serialize themselves (for example, protocol buffers).
type DelegatingCarrier interface {
SetState(traceID, spanID uint64, sampled bool)
State() (traceID, spanID uint64, sampled bool)
SetState(traceID uuid.UUID, spanID uint64, sampled bool)
State() (traceID uuid.UUID, spanID uint64, sampled bool)
SetBaggageItem(key, value string)
GetBaggage(func(key, value string))
}
Expand Down
8 changes: 5 additions & 3 deletions tracer/propagation_env_var.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package tracer

import (
"fmt"
"github.com/google/uuid"
"github.com/opentracing/opentracing-go"
"os"
"strconv"
Expand Down Expand Up @@ -30,7 +31,7 @@ func (p *envVarPropagator) Inject(
if carrier == nil {
return opentracing.ErrInvalidCarrier
}
carrier.Set(fieldNameTraceID, strconv.FormatUint(sc.TraceID, 16))
carrier.Set(fieldNameTraceID, sc.TraceID.String())
carrier.Set(fieldNameSpanID, strconv.FormatUint(sc.SpanID, 16))
carrier.Set(fieldNameSampled, strconv.FormatBool(sc.Sampled))
for k, v := range sc.Baggage {
Expand All @@ -50,14 +51,15 @@ func (p *envVarPropagator) Extract(
return nil, opentracing.ErrInvalidCarrier
}
requiredFieldCount := 0
var traceID, spanID uint64
var traceID uuid.UUID
var spanID uint64
var sampled bool
var err error
decodedBaggage := make(map[string]string)
err = carrier.ForeachKey(func(k, v string) error {
switch strings.ToLower(k) {
case fieldNameTraceID:
traceID, err = strconv.ParseUint(v, 16, 64)
traceID, err = uuid.Parse(v)
if err != nil {
return opentracing.ErrSpanContextCorrupted
}
Expand Down
69 changes: 64 additions & 5 deletions tracer/propagation_ot.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package tracer

import (
"encoding/binary"
"fmt"
"github.com/google/uuid"
"io"
"strconv"
"strings"
Expand All @@ -26,6 +28,8 @@ const (
fieldNameTraceID = prefixTracerState + "traceid"
fieldNameSpanID = prefixTracerState + "spanid"
fieldNameSampled = prefixTracerState + "sampled"

traceParentKey = "traceparent"
)

func (p *textMapPropagator) Inject(
Expand All @@ -40,13 +44,29 @@ func (p *textMapPropagator) Inject(
if !ok {
return opentracing.ErrInvalidCarrier
}
carrier.Set(fieldNameTraceID, strconv.FormatUint(sc.TraceID, 16))

traceId := strings.Replace(sc.TraceID.String(), "-", "", -1)

carrier.Set(fieldNameTraceID, traceId)
carrier.Set(fieldNameSpanID, strconv.FormatUint(sc.SpanID, 16))
carrier.Set(fieldNameSampled, strconv.FormatBool(sc.Sampled))

tpSampled := "00"
if sc.Sampled {
tpSampled = "01"
}
traceParentValue := fmt.Sprintf("%v-%v-%016x-%v",
"00", // Version 0
traceId, // 16bytes TraceId
sc.SpanID, // 8bytes SpanId
tpSampled, // 00 for not sampled, 01 for sampled
)
carrier.Set(traceParentKey, traceParentValue)

for k, v := range sc.Baggage {
carrier.Set(prefixBaggage+k, v)
}
Comment on lines 66 to 68
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot locate the tracestate header for the baggage in OTel format.


return nil
}

Expand All @@ -58,14 +78,46 @@ func (p *textMapPropagator) Extract(
return nil, opentracing.ErrInvalidCarrier
}
requiredFieldCount := 0
var traceID, spanID uint64
var traceID uuid.UUID
var spanID uint64
var sampled bool
var err error
decodedBaggage := make(map[string]string)

err = carrier.ForeachKey(func(k, v string) error {
switch strings.ToLower(k) {
case traceParentKey:
if len(v) < 55 {
return opentracing.ErrSpanContextCorrupted
}
traceParentArray := strings.Split(v, "-")
if len(traceParentArray) < 4 || traceParentArray[0] != "00" || len(traceParentArray[1]) != 32 || len(traceParentArray[2]) != 16 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if len(traceParentArray) < 4 || traceParentArray[0] != "00" || len(traceParentArray[1]) != 32 || len(traceParentArray[2]) != 16 {
if len(traceParentArray) != 4 || traceParentArray[0] != "00" || len(traceParentArray[1]) != 32 || len(traceParentArray[2]) != 16 {

return opentracing.ErrSpanContextCorrupted
}

traceID, err = uuid.Parse(traceParentArray[1])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Inject method, it is being removed the - characters, and here it is being parse without them. Canuuid.Parse function parse UUIDs without -?

if err != nil {
return opentracing.ErrSpanContextCorrupted
}
spanID, err = strconv.ParseUint(traceParentArray[2], 16, 64)
if err != nil {
return opentracing.ErrSpanContextCorrupted
}
if traceParentArray[3] == "01" {
sampled = true
}
requiredFieldCount = requiredFieldCount + 3
default:
// Balance off the requiredFieldCount++ just below...
requiredFieldCount--
}
requiredFieldCount++
return nil
})
err = carrier.ForeachKey(func(k, v string) error {
switch strings.ToLower(k) {
case fieldNameTraceID:
traceID, err = strconv.ParseUint(v, 16, 64)
traceID, err = uuid.Parse(v)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change implies all agents need to send UUID, isn't?

if err != nil {
return opentracing.ErrSpanContextCorrupted
}
Expand Down Expand Up @@ -93,6 +145,7 @@ func (p *textMapPropagator) Extract(
if err != nil {
return nil, err
}

if requiredFieldCount < tracerStateFieldCount {
if requiredFieldCount == 0 {
return nil, opentracing.ErrSpanContextNotFound
Expand Down Expand Up @@ -122,7 +175,9 @@ func (p *binaryPropagator) Inject(
}

state := wire.TracerState{}
Copy link
Contributor

@drodriguezhdez drodriguezhdez Mar 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curiosity, are we using the binary protocol? Is there any standard for that?

state.TraceId = sc.TraceID
bytes, _ := sc.TraceID.MarshalBinary()
state.TraceIdHi = binary.BigEndian.Uint64(bytes[:8])
state.TraceIdLo = binary.BigEndian.Uint64(bytes[8:])
state.SpanId = sc.SpanID
state.Sampled = sc.Sampled
state.BaggageItems = sc.Baggage
Expand Down Expand Up @@ -171,8 +226,12 @@ func (p *binaryPropagator) Extract(
return nil, opentracing.ErrSpanContextCorrupted
}

traceIdBytes := make([]byte, 16)
binary.BigEndian.PutUint64(traceIdBytes[:8], ctx.TraceIdHi)
binary.BigEndian.PutUint64(traceIdBytes[8:], ctx.TraceIdLo)
traceID, _ := uuid.FromBytes(traceIdBytes)
return SpanContext{
TraceID: ctx.TraceId,
TraceID: traceID,
SpanID: ctx.SpanId,
Sampled: ctx.Sampled,
Baggage: ctx.BaggageItems,
Expand Down
5 changes: 3 additions & 2 deletions tracer/propagation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package tracer_test

import (
"bytes"
"github.com/google/uuid"
"net/http"
"reflect"
"testing"
Expand Down Expand Up @@ -29,11 +30,11 @@ func (vc *verbatimCarrier) GetBaggage(f func(string, string)) {
}
}

func (vc *verbatimCarrier) SetState(tID, sID uint64, sampled bool) {
func (vc *verbatimCarrier) SetState(tID uuid.UUID, sID uint64, sampled bool) {
vc.SpanContext = tracer.SpanContext{TraceID: tID, SpanID: sID, Sampled: sampled}
}

func (vc *verbatimCarrier) State() (traceID, spanID uint64, sampled bool) {
func (vc *verbatimCarrier) State() (traceID uuid.UUID, spanID uint64, sampled bool) {
return vc.SpanContext.TraceID, vc.SpanContext.SpanID, vc.SpanContext.Sampled
}

Expand Down
17 changes: 9 additions & 8 deletions tracer/span_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tracer

import (
"github.com/google/uuid"
"reflect"
"strconv"
"testing"
Expand All @@ -15,7 +16,7 @@ func TestSpan_Baggage(t *testing.T) {
recorder := NewInMemoryRecorder()
tracer := NewWithOptions(Options{
Recorder: recorder,
ShouldSample: func(traceID uint64) bool { return true }, // always sample
ShouldSample: func(traceID uuid.UUID) bool { return true }, // always sample
})
span := tracer.StartSpan("x")
span.SetBaggageItem("x", "y")
Expand Down Expand Up @@ -52,7 +53,7 @@ func TestSpan_Sampling(t *testing.T) {
recorder := NewInMemoryRecorder()
tracer := NewWithOptions(Options{
Recorder: recorder,
ShouldSample: func(traceID uint64) bool { return true },
ShouldSample: func(traceID uuid.UUID) bool { return true },
})
span := tracer.StartSpan("x")
span.Finish()
Expand All @@ -66,7 +67,7 @@ func TestSpan_Sampling(t *testing.T) {

tracer = NewWithOptions(Options{
Recorder: recorder,
ShouldSample: func(traceID uint64) bool { return false },
ShouldSample: func(traceID uuid.UUID) bool { return false },
})

recorder.Reset()
Expand All @@ -85,7 +86,7 @@ func TestSpan_SingleLoggedTaggedSpan(t *testing.T) {
recorder := NewInMemoryRecorder()
tracer := NewWithOptions(Options{
Recorder: recorder,
ShouldSample: func(traceID uint64) bool { return true }, // always sample
ShouldSample: func(traceID uuid.UUID) bool { return true }, // always sample
})
span := tracer.StartSpan("x")
span.LogEventWithPayload("event", "payload")
Expand All @@ -112,7 +113,7 @@ func TestSpan_TrimUnsampledSpans(t *testing.T) {
// Tracer that trims only unsampled but always samples
tracer := NewWithOptions(Options{
Recorder: recorder,
ShouldSample: func(traceID uint64) bool { return true }, // always sample
ShouldSample: func(traceID uuid.UUID) bool { return true }, // always sample
TrimUnsampledSpans: true,
})

Expand All @@ -133,7 +134,7 @@ func TestSpan_TrimUnsampledSpans(t *testing.T) {
// Tracer that trims only unsampled and never samples
tracer = NewWithOptions(Options{
Recorder: recorder,
ShouldSample: func(traceID uint64) bool { return false }, // never sample
ShouldSample: func(traceID uuid.UUID) bool { return false }, // never sample
TrimUnsampledSpans: true,
})

Expand All @@ -152,7 +153,7 @@ func TestSpan_DropAllLogs(t *testing.T) {
// Tracer that drops logs
tracer := NewWithOptions(Options{
Recorder: recorder,
ShouldSample: func(traceID uint64) bool { return true }, // always sample
ShouldSample: func(traceID uuid.UUID) bool { return true }, // always sample
DropAllLogs: true,
})

Expand All @@ -175,7 +176,7 @@ func TestSpan_MaxLogSperSpan(t *testing.T) {
// Tracer that only retains the last <limit> logs.
tracer := NewWithOptions(Options{
Recorder: recorder,
ShouldSample: func(traceID uint64) bool { return true }, // always sample
ShouldSample: func(traceID uuid.UUID) bool { return true }, // always sample
MaxLogsPerSpan: limit,
})

Expand Down
14 changes: 11 additions & 3 deletions tracer/tracer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package tracer

import (
"encoding/binary"
"github.com/google/uuid"
"time"

"github.com/go-errors/errors"
Expand All @@ -27,7 +29,7 @@ type Options struct {
// func(traceID uint64) { return traceID % 64 == 0 }
//
// samples every 64th trace on average.
ShouldSample func(traceID uint64) bool
ShouldSample func(traceID uuid.UUID) bool
// TrimUnsampledSpans turns potentially expensive operations on unsampled
// Spans into no-ops. More precisely, tags and log events are silently
// discarded. If NewSpanEventListener is set, the callbacks will still fire.
Expand Down Expand Up @@ -100,7 +102,13 @@ type Options struct {
// returned object with a Tracer.
func DefaultOptions() Options {
return Options{
ShouldSample: func(traceID uint64) bool { return traceID%64 == 0 },
ShouldSample: func(traceID uuid.UUID) bool {
bytes, err := traceID.MarshalBinary()
if err != nil {
return false
}
return binary.LittleEndian.Uint64(bytes[8:])%64 == 0
},
MaxLogsPerSpan: 100,
}
}
Expand Down Expand Up @@ -195,7 +203,7 @@ ReferencesLoop:
break ReferencesLoop
}
}
if sp.raw.Context.TraceID == 0 {
if sp.raw.Context.TraceID == uuid.Nil {
// No parent Span found; allocate new trace and span ids and determine
// the Sampled status.
sp.raw.Context.TraceID, sp.raw.Context.SpanID = randomID2()
Expand Down
Loading