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..7a94b9e633a 100644 --- a/sdk/trace/trace_test.go +++ b/sdk/trace/trace_test.go @@ -208,6 +208,46 @@ 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, + } + + // 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) { tp, _ := NewProvider() tr := tp.Tracer("SpanWith ChildOf")