RFC: add SpanContext.LocalRootSpanID (#1029)
* RFC: add SpanContext.LocalRootSpanID
* Move LocalRootSpanID to Span/SpanData
SpanContext should only hold things that are
propagated between processes, which is not the
case for this field.
* Fix tests
diff --git a/trace/export.go b/trace/export.go
index e0d9a4b..6fc1f4c 100644
--- a/trace/export.go
+++ b/trace/export.go
@@ -73,10 +73,11 @@
// SpanData contains all the information collected by a Span.
type SpanData struct {
SpanContext
- ParentSpanID SpanID
- SpanKind int
- Name string
- StartTime time.Time
+ ParentSpanID SpanID
+ LocalRootSpanID SpanID
+ SpanKind int
+ Name string
+ StartTime time.Time
// The wall clock time of EndTime will be adjusted to always be offset
// from StartTime by the duration of the span.
EndTime time.Time
diff --git a/trace/trace.go b/trace/trace.go
index 38ead7b..549e6fb 100644
--- a/trace/trace.go
+++ b/trace/trace.go
@@ -39,9 +39,10 @@
// It will be non-nil if we are exporting the span or recording events for it.
// Otherwise, data is nil, and the Span is simply a carrier for the
// SpanContext, so that the trace ID is propagated.
- data *SpanData
- mu sync.Mutex // protects the contents of *data (but not the pointer value.)
- spanContext SpanContext
+ data *SpanData
+ mu sync.Mutex // protects the contents of *data (but not the pointer value.)
+ spanContext SpanContext
+ localRootSpanID SpanID
// lruAttributes are capped at configured limit. When the capacity is reached an oldest entry
// is removed to create room for a new entry.
@@ -169,14 +170,16 @@
func StartSpan(ctx context.Context, name string, o ...StartOption) (context.Context, *Span) {
var opts StartOptions
var parent SpanContext
+ var localRoot SpanID
if p := FromContext(ctx); p != nil {
p.addChild()
parent = p.spanContext
+ localRoot = p.localRootSpanID
}
for _, op := range o {
op(&opts)
}
- span := startSpanInternal(name, parent != SpanContext{}, parent, false, opts)
+ span := startSpanInternal(name, parent != SpanContext{}, parent, localRoot, false, opts)
ctx, end := startExecutionTracerTask(ctx, name)
span.executionTracerTaskEnd = end
@@ -195,15 +198,16 @@
for _, op := range o {
op(&opts)
}
- span := startSpanInternal(name, parent != SpanContext{}, parent, true, opts)
+ span := startSpanInternal(name, parent != SpanContext{}, parent, SpanID{}, true, opts)
ctx, end := startExecutionTracerTask(ctx, name)
span.executionTracerTaskEnd = end
return NewContext(ctx, span), span
}
-func startSpanInternal(name string, hasParent bool, parent SpanContext, remoteParent bool, o StartOptions) *Span {
+func startSpanInternal(name string, hasParent bool, parent SpanContext, localRoot SpanID, remoteParent bool, o StartOptions) *Span {
span := &Span{}
span.spanContext = parent
+ span.localRootSpanID = localRoot
cfg := config.Load().(*Config)
@@ -213,6 +217,10 @@
span.spanContext.SpanID = cfg.IDGenerator.NewSpanID()
sampler := cfg.DefaultSampler
+ if localRoot == (SpanID{}) {
+ span.localRootSpanID = span.spanContext.SpanID
+ }
+
if !hasParent || remoteParent || o.Sampler != nil {
// If this span is the child of a local span and no Sampler is set in the
// options, keep the parent's TraceOptions.
@@ -236,6 +244,7 @@
span.data = &SpanData{
SpanContext: span.spanContext,
+ LocalRootSpanID: span.localRootSpanID,
StartTime: time.Now(),
SpanKind: o.SpanKind,
Name: name,
diff --git a/trace/trace_test.go b/trace/trace_test.go
index c215136..c8f624d 100644
--- a/trace/trace_test.go
+++ b/trace/trace_test.go
@@ -230,6 +230,32 @@
}
}
+func TestLocalRootSpanID(t *testing.T) {
+ ctx, span1 := StartSpan(context.Background(), "span1")
+ if span1.localRootSpanID == (SpanID{}) {
+ t.Errorf("exporting root span: expected nonzero localRootSpanID")
+ }
+
+ _, span2 := StartSpan(ctx, "span2")
+ if err := checkChild(span1.spanContext, span2); err != nil {
+ t.Error(err)
+ }
+ if got, want := span2.localRootSpanID, span1.localRootSpanID; got != want {
+ t.Errorf("span2.localRootSpanID=%q; want %q (span1.localRootSpanID)", got, want)
+ }
+
+ _, span3 := StartSpanWithRemoteParent(context.Background(), "span3", span2.SpanContext())
+ if err := checkChild(span3.spanContext, span2); err != nil {
+ t.Error(err)
+ }
+ if span3.localRootSpanID == (SpanID{}) {
+ t.Errorf("exporting span with remote parent: expected nonzero localRootSpanID")
+ }
+ if got, want := span3.localRootSpanID, span2.localRootSpanID; got == want {
+ t.Errorf("span3.localRootSpanID=%q; expected different value to span2.localRootSpanID, got same", got)
+ }
+}
+
// startSpan returns a context with a new Span that is recording events and will be exported.
func startSpan(o StartOptions) *Span {
_, span := StartSpanWithRemoteParent(context.Background(), "span0",
@@ -274,7 +300,11 @@
if got.SpanContext.SpanID == (SpanID{}) {
return nil, fmt.Errorf("exporting span: expected nonzero SpanID")
}
+ if got.LocalRootSpanID == (SpanID{}) {
+ return nil, fmt.Errorf("exporting span: expected nonzero LocalRootSpanID")
+ }
got.SpanContext.SpanID = SpanID{}
+ got.LocalRootSpanID = SpanID{}
if !checkTime(&got.StartTime) {
return nil, fmt.Errorf("exporting span: expected nonzero StartTime")
}