diff --git a/CHANGELOG.md b/CHANGELOG.md index fa47f485260..2690a140215 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - The `ExportSpans` method of the`SpanExporter` interface type was updated to accept `ReadOnlySpan`s instead of the removed `SpanSnapshot`. This brings the export interface into compliance with the specification in that it now accepts an explicitly immutable type instead of just an implied one. (#1873) - Unembed `SpanContext` in `Link`. (#1877) +- Spans created by the global `Tracer` obtained from `go.opentelemetry.io/otel`, prior to a functioning `TracerProvider` being set, now propagate the span context from their parent if one exists. (#1901) ### Deprecated diff --git a/internal/global/trace.go b/internal/global/trace.go index d7f71dc06c6..aedf1168804 100644 --- a/internal/global/trace.go +++ b/internal/global/trace.go @@ -36,7 +36,8 @@ import ( "sync" "sync/atomic" - "go.opentelemetry.io/otel/internal/trace/noop" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" ) @@ -143,5 +144,44 @@ func (t *tracer) Start(ctx context.Context, name string, opts ...trace.SpanOptio if delegate != nil { return delegate.(trace.Tracer).Start(ctx, name, opts...) } - return noop.Tracer.Start(ctx, name, opts...) + + s := nonRecordingSpan{sc: trace.SpanContextFromContext(ctx)} + ctx = trace.ContextWithSpan(ctx, s) + return ctx, s +} + +// nonRecordingSpan is a minimal implementation of a Span that wraps a +// SpanContext. It performs no operations other than to return the wrapped +// SpanContext. +type nonRecordingSpan struct { + sc trace.SpanContext } + +var _ trace.Span = nonRecordingSpan{} + +// SpanContext returns the wrapped SpanContext. +func (s nonRecordingSpan) SpanContext() trace.SpanContext { return s.sc } + +// IsRecording always returns false. +func (nonRecordingSpan) IsRecording() bool { return false } + +// SetStatus does nothing. +func (nonRecordingSpan) SetStatus(codes.Code, string) {} + +// SetError does nothing. +func (nonRecordingSpan) SetError(bool) {} + +// SetAttributes does nothing. +func (nonRecordingSpan) SetAttributes(...attribute.KeyValue) {} + +// End does nothing. +func (nonRecordingSpan) End(...trace.SpanOption) {} + +// RecordError does nothing. +func (nonRecordingSpan) RecordError(error, ...trace.EventOption) {} + +// AddEvent does nothing. +func (nonRecordingSpan) AddEvent(string, ...trace.EventOption) {} + +// SetName does nothing. +func (nonRecordingSpan) SetName(string) {} diff --git a/internal/global/trace_test.go b/internal/global/trace_test.go index a8fdfb44d9d..4ca53c35984 100644 --- a/internal/global/trace_test.go +++ b/internal/global/trace_test.go @@ -213,3 +213,19 @@ func TestTraceProviderDelegatesSameInstance(t *testing.T) { assert.NotSame(t, tracer, gtp.Tracer("abc", trace.WithInstrumentationVersion("xyz"))) } + +func TestSpanContextPropagatedWithNonRecordingSpan(t *testing.T) { + global.ResetForTest() + + sc := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: [16]byte{0x01}, + SpanID: [8]byte{0x01}, + TraceFlags: trace.FlagsSampled, + Remote: true, + }) + ctx := trace.ContextWithSpanContext(context.Background(), sc) + _, span := otel.Tracer("test").Start(ctx, "test") + + assert.Equal(t, sc, span.SpanContext()) + assert.False(t, span.IsRecording()) +}