Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add span.TracerProvider() #2009

Merged
merged 4 commits into from
Jun 17, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Creates package `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` implementing a gRPC `otlpmetric.Client` and offers convenience functions, `New` and `NewUnstarted`, to create an `otlpmetric.Exporter`.(#1991)
- Added `go.opentelemetry.io/otel/exporters/stdout/stdouttrace` exporter. (#2005)
- Added `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric` exporter. (#2005)
- Added a `TracerProvider()` method to the `"go.opentelemetry.io/otel/trace".Span` interface. This can be used to obtain a `TracerProvider` from a given span that utilizes the same trace processing pipeline. (#2009)

### Changed

Expand Down Expand Up @@ -108,6 +109,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Remove the `Tracer` method from the `Span` interface in the `go.opentelemetry.io/otel/trace` package.
Using the same tracer that created a span introduces the error where an instrumentation library's `Tracer` is used by other code instead of their own.
The `"go.opentelemetry.io/otel".Tracer` function or a `TracerProvider` should be used to acquire a library specific `Tracer` instead. (#1900)
- The `TracerProvider()` method on the `Span` interface may also be used to obtain a `TracerProvider` using the same trace processing pipeline. (#2009)
- The `http.url` attribute generated by `HTTPClientAttributesFromHTTPRequest` will no longer include username or password information. (#1919)
- The `IsEmpty` method of the `TraceState` type in the `go.opentelemetry.io/otel/trace` package is removed in favor of using the added `TraceState.Len` method. (#1931)
- The `Set`, `Value`, `ContextWithValue`, `ContextWithoutValue`, and `ContextWithEmpty` functions in the `go.opentelemetry.io/otel/baggage` package are removed.
Expand Down
2 changes: 2 additions & 0 deletions bridge/opentracing/internal/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,5 @@ func (s *MockSpan) AddEvent(name string, o ...trace.EventOption) {
func (s *MockSpan) OverrideTracer(tracer trace.Tracer) {
s.officialTracer = tracer
}

func (s *MockSpan) TracerProvider() trace.TracerProvider { return trace.NewNoopTracerProvider() }
14 changes: 9 additions & 5 deletions internal/global/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (p *tracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.T
return val
}

t := &tracer{name: name, opts: opts}
t := &tracer{name: name, opts: opts, provider: p}
p.tracers[key] = t
return t
}
Expand All @@ -118,8 +118,9 @@ type il struct {
// All Tracer functionality is forwarded to a delegate once configured.
// Otherwise, all functionality is forwarded to a NoopTracer.
type tracer struct {
name string
opts []trace.TracerOption
name string
opts []trace.TracerOption
provider *tracerProvider

delegate atomic.Value
}
Expand All @@ -145,7 +146,7 @@ func (t *tracer) Start(ctx context.Context, name string, opts ...trace.SpanStart
return delegate.(trace.Tracer).Start(ctx, name, opts...)
}

s := nonRecordingSpan{sc: trace.SpanContextFromContext(ctx)}
s := nonRecordingSpan{sc: trace.SpanContextFromContext(ctx), tracer: t}
ctx = trace.ContextWithSpan(ctx, s)
return ctx, s
}
Expand All @@ -154,7 +155,8 @@ func (t *tracer) Start(ctx context.Context, name string, opts ...trace.SpanStart
// SpanContext. It performs no operations other than to return the wrapped
// SpanContext.
type nonRecordingSpan struct {
sc trace.SpanContext
sc trace.SpanContext
tracer *tracer
}

var _ trace.Span = nonRecordingSpan{}
Expand Down Expand Up @@ -185,3 +187,5 @@ func (nonRecordingSpan) AddEvent(string, ...trace.EventOption) {}

// SetName does nothing.
func (nonRecordingSpan) SetName(string) {}

func (s nonRecordingSpan) TracerProvider() trace.TracerProvider { return s.tracer.provider }
6 changes: 5 additions & 1 deletion internal/global/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,18 @@ func TestTraceWithSDK(t *testing.T) {
_, span3 := tracer2.Start(ctx, "span3")
span3.End()

// The noop-span should still provide access to a usable TracerProvider.
_, span4 := span1.TracerProvider().Tracer("fromSpan").Start(ctx, "span4")
span4.End()

filterNames := func(spans []*oteltest.Span) []string {
names := make([]string, len(spans))
for i := range spans {
names[i] = spans[i].Name()
}
return names
}
expected := []string{"span2", "span3"}
expected := []string{"span2", "span3", "span4"}
assert.ElementsMatch(t, expected, filterNames(sr.Started()))
assert.ElementsMatch(t, expected, filterNames(sr.Completed()))
}
Expand Down
6 changes: 6 additions & 0 deletions oteltest/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,12 @@ func (h *Harness) testSpan(tracerFactory func() trace.Tracer) {

return subject
},
"Span created via span.TracerProvider()": func() trace.Span {
ctx, spanA := tracerFactory().Start(context.Background(), "span1")

_, spanB := spanA.TracerProvider().Tracer("second").Start(ctx, "span2")
return spanB
},
}

for mechanismName, mechanism := range mechanisms {
Expand Down
7 changes: 4 additions & 3 deletions oteltest/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ func (p *TracerProvider) Tracer(instName string, opts ...trace.TracerOption) tra
t, ok := p.tracers[inst]
if !ok {
t = &Tracer{
Name: instName,
Version: conf.InstrumentationVersion(),
config: &p.config,
Name: instName,
Version: conf.InstrumentationVersion(),
config: &p.config,
provider: p,
}
p.tracers[inst] = t
}
Expand Down
6 changes: 6 additions & 0 deletions oteltest/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,9 @@ func (s *Span) StatusMessage() string { return s.statusMessage }

// SpanKind returns the span kind of s.
func (s *Span) SpanKind() trace.SpanKind { return s.spanKind }

// TracerProvider returns a trace.TracerProvider that can be used to generate
// additional Spans on the same telemetry pipeline as the current Span.
func (s *Span) TracerProvider() trace.TracerProvider {
return s.tracer.provider
}
20 changes: 20 additions & 0 deletions oteltest/span_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,4 +569,24 @@ func TestSpan(t *testing.T) {
e.Expect(subject.SpanKind()).ToEqual(trace.SpanKindConsumer)
})
})

t.Run("can provide a valid TracerProvider", func(t *testing.T) {
t.Parallel()

e := matchers.NewExpecter(t)

sr := new(oteltest.SpanRecorder)
tracerA := oteltest.NewTracerProvider(oteltest.WithSpanRecorder(sr)).Tracer(t.Name())
ctx, spanA := tracerA.Start(context.Background(), "span1")

e.Expect(len(sr.Started())).ToEqual(1)

_, spanB := spanA.TracerProvider().Tracer("extracted").Start(ctx, "span2")

spans := sr.Started()

e.Expect(len(spans)).ToEqual(2)
e.Expect(spans[0]).ToEqual(spanA)
e.Expect(spans[1]).ToEqual(spanB)
})
}
3 changes: 2 additions & 1 deletion oteltest/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ type Tracer struct {
// Version is the instrumentation version.
Version string

config *config
config *config
Aneurysm9 marked this conversation as resolved.
Show resolved Hide resolved
provider *TracerProvider
}

// Start creates a span. If t is configured with a SpanRecorder its OnStart
Expand Down
6 changes: 6 additions & 0 deletions sdk/trace/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,12 @@ func (s *span) ChildSpanCount() int {
return s.childSpanCount
}

// TracerProvider returns a trace.TracerProvider that can be used to generate
// additional Spans on the same telemetry pipeline as the current Span.
func (s *span) TracerProvider() trace.TracerProvider {
return s.tracer.provider
}

// snapshot creates a read-only copy of the current state of the span.
func (s *span) snapshot() ReadOnlySpan {
var sd snapshot
Expand Down
12 changes: 10 additions & 2 deletions trace/noop.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,14 @@ type noopTracer struct{}

var _ Tracer = noopTracer{}

// Start starts a noop span.
// Start carries forward a non-recording Span, if one is present in the context, otherwise it
// creates a no-op Span.
func (t noopTracer) Start(ctx context.Context, name string, _ ...SpanStartOption) (context.Context, Span) {
span := noopSpan{}
span := SpanFromContext(ctx)
if _, ok := span.(nonRecordingSpan); !ok {
// span is likely already a noopSpan, but let's be sure
span = noopSpan{}
}
return ContextWithSpan(ctx, span), span
}

Expand Down Expand Up @@ -79,3 +84,6 @@ func (noopSpan) AddEvent(string, ...EventOption) {}

// SetName does nothing.
func (noopSpan) SetName(string) {}

// TracerProvider returns a no-op TracerProvider
func (noopSpan) TracerProvider() TracerProvider { return noopTracerProvider{} }
19 changes: 19 additions & 0 deletions trace/noop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,22 @@ func TestNoopSpan(t *testing.T) {
t.Errorf("span.IsRecording() returned %#v, want %#v", got, want)
}
}

func TestNonRecordingSpanTracerStart(t *testing.T) {
tid, err := TraceIDFromHex("01000000000000000000000000000000")
if err != nil {
t.Fatalf("failure creating TraceID: %s", err.Error())
}
sid, err := SpanIDFromHex("0200000000000000")
if err != nil {
t.Fatalf("failure creating SpanID: %s", err.Error())
}
sc := NewSpanContext(SpanContextConfig{TraceID: tid, SpanID: sid})

ctx := ContextWithSpanContext(context.Background(), sc)
_, span := NewNoopTracerProvider().Tracer("test instrumentation").Start(ctx, "span1")

if got, want := span.SpanContext(), sc; !assertSpanContextEqual(got, want) {
t.Errorf("SpanContext not carried by nonRecordingSpan. got %#v, want %#v", got, want)
}
}
4 changes: 4 additions & 0 deletions trace/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,10 @@ type Span interface {
// already exists for an attribute of the Span it will be overwritten with
// the value contained in kv.
SetAttributes(kv ...attribute.KeyValue)

// TracerProvider returns a TracerProvider that can be used to generate
// additional Spans on the same telemetry pipeline as the current Span.
TracerProvider() TracerProvider
}

// Link is the relationship between two Spans. The relationship can be within
Expand Down