Skip to content

Commit

Permalink
opentracing: allow customizing propagator headers (#155)
Browse files Browse the repository at this point in the history
  • Loading branch information
gbbr authored and Emanuele Palazzetti committed Jan 19, 2018
1 parent 26f7d18 commit 2761701
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 73 deletions.
18 changes: 11 additions & 7 deletions opentracing/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ type Configuration struct {
// GlobalTags holds a set of tags that will be automatically applied to
// all spans.
GlobalTags map[string]interface{}

// TextMapPropagator is an injector used for Context propagation.
TextMapPropagator Propagator
}

// NewConfiguration creates a `Configuration` object with default values.
Expand All @@ -39,13 +42,14 @@ func NewConfiguration() *Configuration {

// Configuration struct with default values
return &Configuration{
Enabled: true,
Debug: false,
ServiceName: binaryName,
SampleRate: 1,
AgentHostname: "localhost",
AgentPort: "8126",
GlobalTags: make(map[string]interface{}),
Enabled: true,
Debug: false,
ServiceName: binaryName,
SampleRate: 1,
AgentHostname: "localhost",
AgentPort: "8126",
GlobalTags: make(map[string]interface{}),
TextMapPropagator: NewTextMapPropagator("", "", ""),
}
}

Expand Down
69 changes: 52 additions & 17 deletions opentracing/propagators.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,55 @@ import (
ot "github.com/opentracing/opentracing-go"
)

type textMapPropagator struct{}
// Propagator implementations should be able to inject and extract
// SpanContexts into an implementation specific carrier.
type Propagator interface {
// Inject takes the SpanContext and injects it into the carrier using
// an implementation specific method.
Inject(context ot.SpanContext, carrier interface{}) error

const (
prefixBaggage = "ot-baggage-"
prefixTracerState = "x-datadog-"
// Extract returns the SpanContext from the given carrier using an
// implementation specific method.
Extract(carrier interface{}) (ot.SpanContext, error)
}

fieldNameTraceID = prefixTracerState + "trace-id"
fieldNameParentID = prefixTracerState + "parent-id"
const (
defaultBaggageHeaderPrefix = "ot-baggage-"
defaultTraceIDHeader = "x-datadog-trace-id"
defaultParentIDHeader = "x-datadog-parent-id"
)

// Inject defines the textMapPropagator to propagate SpanContext data
// NewTextMapPropagator returns a new propagator which uses opentracing.TextMap
// to inject and extract values. The parameters specify the prefix that will
// be used to prefix baggage header keys along with the trace and parent header.
// Empty strings may be provided to use the defaults, which are: "ot-baggage-" as
// prefix for baggage headers, "x-datadog-trace-id" and "x-datadog-parent-id" for
// trace and parent ID headers.
func NewTextMapPropagator(baggagePrefix, traceHeader, parentHeader string) *TextMapPropagator {
if baggagePrefix == "" {
baggagePrefix = defaultBaggageHeaderPrefix
}
if traceHeader == "" {
traceHeader = defaultTraceIDHeader
}
if parentHeader == "" {
parentHeader = defaultParentIDHeader
}
return &TextMapPropagator{baggagePrefix, traceHeader, parentHeader}
}

// TextMapPropagator implements a propagator which uses opentracing.TextMap
// internally.
type TextMapPropagator struct {
baggagePrefix string
traceHeader string
parentHeader string
}

// Inject defines the TextMapPropagator to propagate SpanContext data
// out of the current process. The implementation propagates the
// TraceID and the current active SpanID, as well as the Span baggage.
func (p *textMapPropagator) Inject(context ot.SpanContext, carrier interface{}) error {
func (p *TextMapPropagator) Inject(context ot.SpanContext, carrier interface{}) error {
ctx, ok := context.(SpanContext)
if !ok {
return ot.ErrInvalidSpanContext
Expand All @@ -31,18 +66,18 @@ func (p *textMapPropagator) Inject(context ot.SpanContext, carrier interface{})
}

// propagate the TraceID and the current active SpanID
writer.Set(fieldNameTraceID, strconv.FormatUint(ctx.traceID, 10))
writer.Set(fieldNameParentID, strconv.FormatUint(ctx.spanID, 10))
writer.Set(p.traceHeader, strconv.FormatUint(ctx.traceID, 10))
writer.Set(p.parentHeader, strconv.FormatUint(ctx.spanID, 10))

// propagate OpenTracing baggage
for k, v := range ctx.baggage {
writer.Set(prefixBaggage+k, v)
writer.Set(p.baggagePrefix+k, v)
}
return nil
}

// Extract does
func (p *textMapPropagator) Extract(carrier interface{}) (ot.SpanContext, error) {
// Extract implements Propagator.
func (p *TextMapPropagator) Extract(carrier interface{}) (ot.SpanContext, error) {
reader, ok := carrier.(ot.TextMapReader)
if !ok {
return nil, ot.ErrInvalidCarrier
Expand All @@ -54,20 +89,20 @@ func (p *textMapPropagator) Extract(carrier interface{}) (ot.SpanContext, error)
// extract SpanContext fields
err = reader.ForeachKey(func(k, v string) error {
switch strings.ToLower(k) {
case fieldNameTraceID:
case p.traceHeader:
traceID, err = strconv.ParseUint(v, 10, 64)
if err != nil {
return ot.ErrSpanContextCorrupted
}
case fieldNameParentID:
case p.parentHeader:
parentID, err = strconv.ParseUint(v, 10, 64)
if err != nil {
return ot.ErrSpanContextCorrupted
}
default:
lowercaseK := strings.ToLower(k)
if strings.HasPrefix(lowercaseK, prefixBaggage) {
decodedBaggage[strings.TrimPrefix(lowercaseK, prefixBaggage)] = v
if strings.HasPrefix(lowercaseK, p.baggagePrefix) {
decodedBaggage[strings.TrimPrefix(lowercaseK, p.baggagePrefix)] = v
}
}

Expand Down
81 changes: 81 additions & 0 deletions opentracing/propagators_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package opentracing

import (
"net/http"
"strconv"
"testing"

opentracing "github.com/opentracing/opentracing-go"
"github.com/stretchr/testify/assert"
)

func TestTracerPropagationDefaults(t *testing.T) {
assert := assert.New(t)

config := NewConfiguration()
tracer, _, _ := NewTracer(config)

root := tracer.StartSpan("web.request")
ctx := root.Context()
headers := http.Header{}

// inject the SpanContext
carrier := opentracing.HTTPHeadersCarrier(headers)
err := tracer.Inject(ctx, opentracing.HTTPHeaders, carrier)
assert.Nil(err)

// retrieve the SpanContext
propagated, err := tracer.Extract(opentracing.HTTPHeaders, carrier)
assert.Nil(err)

tCtx, ok := ctx.(SpanContext)
assert.True(ok)
tPropagated, ok := propagated.(SpanContext)
assert.True(ok)

// compare if there is a Context match
assert.Equal(tCtx.traceID, tPropagated.traceID)
assert.Equal(tCtx.spanID, tPropagated.spanID)

// ensure a child can be created
child := tracer.StartSpan("db.query", opentracing.ChildOf(propagated))
tRoot, ok := root.(*Span)
assert.True(ok)
tChild, ok := child.(*Span)
assert.True(ok)

assert.NotEqual(uint64(0), tChild.Span.TraceID)
assert.NotEqual(uint64(0), tChild.Span.SpanID)
assert.Equal(tRoot.Span.SpanID, tChild.Span.ParentID)
assert.Equal(tRoot.Span.TraceID, tChild.Span.ParentID)

tid := strconv.FormatUint(tRoot.Span.TraceID, 10)
pid := strconv.FormatUint(tRoot.Span.SpanID, 10)

// hardcode header names to fail test if defaults are changed
assert.Equal(headers.Get("x-datadog-trace-id"), tid)
assert.Equal(headers.Get("x-datadog-parent-id"), pid)
}

func TestTracerTextMapPropagationHeader(t *testing.T) {
assert := assert.New(t)

config := NewConfiguration()
config.TextMapPropagator = NewTextMapPropagator("bg-", "tid", "pid")
tracer, _, _ := NewTracer(config)

root := tracer.StartSpan("web.request").SetBaggageItem("item", "x").(*Span)
ctx := root.Context()
headers := http.Header{}

carrier := opentracing.HTTPHeadersCarrier(headers)
err := tracer.Inject(ctx, opentracing.HTTPHeaders, carrier)
assert.Nil(err)

tid := strconv.FormatUint(root.Span.TraceID, 10)
pid := strconv.FormatUint(root.Span.SpanID, 10)

assert.Equal(headers.Get("tid"), tid)
assert.Equal(headers.Get("pid"), pid)
assert.Equal(headers.Get("bg-item"), "x")
}
9 changes: 2 additions & 7 deletions opentracing/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ type Tracer struct {

// config holds the Configuration used to create the Tracer.
config *Configuration

// textPropagator is an injector used for Context propagation.
textPropagator *textMapPropagator
}

// StartSpan creates, starts, and returns a new Span with the given `operationName`
Expand Down Expand Up @@ -125,19 +122,17 @@ func (t *Tracer) startSpanWithOptions(operationName string, options ot.StartSpan
func (t *Tracer) Inject(ctx ot.SpanContext, format interface{}, carrier interface{}) error {
switch format {
case ot.TextMap, ot.HTTPHeaders:
return t.textPropagator.Inject(ctx, carrier)
return t.config.TextMapPropagator.Inject(ctx, carrier)
}

return ot.ErrUnsupportedFormat
}

// Extract returns a SpanContext instance given `format` and `carrier`.
func (t *Tracer) Extract(format interface{}, carrier interface{}) (ot.SpanContext, error) {
switch format {
case ot.TextMap, ot.HTTPHeaders:
return t.textPropagator.Extract(carrier)
return t.config.TextMapPropagator.Extract(carrier)
}

return nil, ot.ErrUnsupportedFormat
}

Expand Down
42 changes: 0 additions & 42 deletions opentracing/tracer_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package opentracing

import (
"net/http"
"testing"
"time"

Expand Down Expand Up @@ -130,44 +129,3 @@ func TestTracerSpanStartTime(t *testing.T) {

assert.Equal(startTime.UnixNano(), span.Span.Start)
}

func TestTracerPropagation(t *testing.T) {
assert := assert.New(t)

config := NewConfiguration()
tracer, _, _ := NewTracer(config)

root := tracer.StartSpan("web.request")
ctx := root.Context()
headers := http.Header{}

// inject the SpanContext
carrier := opentracing.HTTPHeadersCarrier(headers)
err := tracer.Inject(ctx, opentracing.HTTPHeaders, carrier)
assert.Nil(err)

// retrieve the SpanContext
propagated, err := tracer.Extract(opentracing.HTTPHeaders, carrier)
assert.Nil(err)

tCtx, ok := ctx.(SpanContext)
assert.True(ok)
tPropagated, ok := propagated.(SpanContext)
assert.True(ok)

// compare if there is a Context match
assert.Equal(tCtx.traceID, tPropagated.traceID)
assert.Equal(tCtx.spanID, tPropagated.spanID)

// ensure a child can be created
child := tracer.StartSpan("db.query", opentracing.ChildOf(propagated))
tRoot, ok := root.(*Span)
assert.True(ok)
tChild, ok := child.(*Span)
assert.True(ok)

assert.NotEqual(uint64(0), tChild.Span.TraceID)
assert.NotEqual(uint64(0), tChild.Span.SpanID)
assert.Equal(tRoot.Span.SpanID, tChild.Span.ParentID)
assert.Equal(tRoot.Span.TraceID, tChild.Span.ParentID)
}

0 comments on commit 2761701

Please sign in to comment.