From f42b56672c1e3f9384883993d4510416d8dc8f88 Mon Sep 17 00:00:00 2001 From: Nick Travers Date: Thu, 2 Jan 2020 11:49:51 -0800 Subject: [PATCH 1/2] Allow for setting an explicit SpanContext on Span creation In certain circumstances, information from an existing SpanContext is desirable when instantiating a new Span. For instance, when a process is accepting inbound spans in various formats before using an OpenTelemetry exporter to forward to another destination. Add a new StartOption, WithSpanContext, that allows setting an explicit SpanContext on a trace. Signed-off-by: Nick Travers --- api/trace/api.go | 20 ++++++++++++++------ sdk/trace/span.go | 22 ++++++++++++++++------ sdk/trace/trace_test.go | 22 ++++++++++++++++++++++ 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/api/trace/api.go b/api/trace/api.go index 11c7f541427..127a21916f0 100644 --- a/api/trace/api.go +++ b/api/trace/api.go @@ -97,12 +97,13 @@ type StartOption func(*StartConfig) // StartConfig provides options to set properties of span at the time of starting // a new span. type StartConfig struct { - Attributes []core.KeyValue - StartTime time.Time - Links []Link - Relation Relation - Record bool - SpanKind SpanKind + Attributes []core.KeyValue + StartTime time.Time + Links []Link + Relation Relation + Record bool + SpanKind SpanKind + SpanContext core.SpanContext } // Relation is used to establish relationship between newly created span and the @@ -249,3 +250,10 @@ func WithSpanKind(sk SpanKind) StartOption { c.SpanKind = sk } } + +// WithSpanContext specifies the SpanContext to use for a Trace. +func WithSpanContext(ctx core.SpanContext) StartOption { + return func(c *StartConfig) { + c.SpanContext = ctx + } +} diff --git a/sdk/trace/span.go b/sdk/trace/span.go index d21ff1f5d39..b31cc86f046 100644 --- a/sdk/trace/span.go +++ b/sdk/trace/span.go @@ -248,15 +248,25 @@ func (s *span) addChild() { func startSpanInternal(tr *tracer, name string, parent core.SpanContext, remoteParent bool, o apitrace.StartConfig) *span { var noParent bool span := &span{} - span.spanContext = parent - cfg := tr.provider.config.Load().(*Config) - if parent == core.EmptySpanContext() { - span.spanContext.TraceID = cfg.IDGenerator.NewTraceID() - noParent = true + if o.SpanContext.IsValid() { + // if an explicit span context was provided, use it ... + span.spanContext = o.SpanContext + } else { + // ... otherwise, use the parent ... + span.spanContext = parent + + // ... unless there was no parent, in which case generate a new TraceID ... + if parent == core.EmptySpanContext() { + noParent = true + span.spanContext.TraceID = cfg.IDGenerator.NewTraceID() + } + + // ... always generate a new SpanID + span.spanContext.SpanID = cfg.IDGenerator.NewSpanID() } - span.spanContext.SpanID = cfg.IDGenerator.NewSpanID() + data := samplingData{ noParent: noParent, remoteParent: remoteParent, diff --git a/sdk/trace/trace_test.go b/sdk/trace/trace_test.go index ca566c56c17..56382d19ad0 100644 --- a/sdk/trace/trace_test.go +++ b/sdk/trace/trace_test.go @@ -208,6 +208,28 @@ func TestSampling(t *testing.T) { } } +func TestStartSpanWithExplicitSpanContext(t *testing.T) { + tp, _ := NewProvider() + tr := tp.Tracer("WithSpanContext") + + sc1 := core.SpanContext{ + TraceID: tid, + SpanID: sid, + TraceFlags: 0x0, + } + + _, s := tr.Start(context.Background(), "span1", apitrace.WithSpanContext(sc1)) + if s.SpanContext() != sc1 { + t.Errorf("got %+v, want %+v", s.SpanContext(), sc1) + } + + sc2 := core.EmptySpanContext() + _, s = tr.Start(context.Background(), "span2", apitrace.WithSpanContext(sc2)) + if s.SpanContext() == sc2 { + t.Errorf("got %+v, wanted a non-EmptySpanContext", s.SpanContext()) + } +} + func TestStartSpanWithChildOf(t *testing.T) { tp, _ := NewProvider() tr := tp.Tracer("SpanWith ChildOf") From b75916a5e2aa990d7904ffcd34e40fbda2391fc7 Mon Sep 17 00:00:00 2001 From: Nick Travers Date: Thu, 2 Jan 2020 12:54:20 -0800 Subject: [PATCH 2/2] Add test case for explicitly invalid SpanContext Signed-off-by: Nick Travers --- sdk/trace/trace_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/sdk/trace/trace_test.go b/sdk/trace/trace_test.go index 56382d19ad0..7a94b9e633a 100644 --- a/sdk/trace/trace_test.go +++ b/sdk/trace/trace_test.go @@ -218,16 +218,34 @@ func TestStartSpanWithExplicitSpanContext(t *testing.T) { TraceFlags: 0x0, } + // providing an explicit span context _, s := tr.Start(context.Background(), "span1", apitrace.WithSpanContext(sc1)) if s.SpanContext() != sc1 { t.Errorf("got %+v, want %+v", s.SpanContext(), sc1) } + // providing an explicit empty span context sc2 := core.EmptySpanContext() _, s = tr.Start(context.Background(), "span2", apitrace.WithSpanContext(sc2)) if s.SpanContext() == sc2 { t.Errorf("got %+v, wanted a non-EmptySpanContext", s.SpanContext()) } + + // providing an explicit, but invalid, span context + sc3 := core.SpanContext{ + TraceID: [16]byte{}, + SpanID: [8]byte{}, + } + if sc3.IsValid() { + t.Errorf("got %v, wanted an invalid SpanContext", sc3) + } + _, s = tr.Start(context.Background(), "span3", apitrace.WithSpanContext(sc3)) + if s.SpanContext() == sc3 { + t.Errorf("got %+v, wanted a SpanContext different to the invalid original", s.SpanContext()) + } + if !s.SpanContext().IsValid() { + t.Errorf("got %+v, wanted a valid SpanContext", s.SpanContext()) + } } func TestStartSpanWithChildOf(t *testing.T) {