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")
 	}