-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Global trace forwarding implementation (#406)
* Global trace forwarding implementation according to open-telemetry/oteps#74
- Loading branch information
1 parent
b80b336
commit bb4f7fa
Showing
4 changed files
with
249 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package internal | ||
|
||
/* | ||
This file contains the forwarding implementation of the trace.Provider used as | ||
the default global instance. Prior to initialization of an SDK, Tracers | ||
returned by the global Provider will provide no-op functionality. This means | ||
that all Span created prior to initialization are no-op Spans. | ||
Once an SDK has been initialized, all provided no-op Tracers are swapped for | ||
Tracers provided by the SDK defined Provider. However, any Span started prior | ||
to this initialization does not change its behavior. Meaning, the Span remains | ||
a no-op Span. | ||
The implementation to track and swap Tracers locks all new Tracer creation | ||
until the swap is complete. This assumes that this operation is not | ||
performance-critical. If that assumption is incorrect, be sure to configure an | ||
SDK prior to any Tracer creation. | ||
*/ | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
|
||
"go.opentelemetry.io/otel/api/trace" | ||
) | ||
|
||
// traceProvider is a placeholder for a configured SDK Provider. | ||
// | ||
// All Provider functionality is forwarded to a delegate once configured. | ||
type traceProvider struct { | ||
mtx sync.Mutex | ||
tracers []*tracer | ||
|
||
delegate trace.Provider | ||
} | ||
|
||
// Compile-time guarantee that traceProvider implements the trace.Provider interface. | ||
var _ trace.Provider = &traceProvider{} | ||
|
||
// setDelegate configures p to delegate all Provider functionality to provider. | ||
// | ||
// All Tracers provided prior to this function call are switched out to be | ||
// Tracers provided by provider. | ||
// | ||
// Delegation only happens on the first call to this method. All subsequent | ||
// calls result in no delegation changes. | ||
func (p *traceProvider) setDelegate(provider trace.Provider) { | ||
if p.delegate != nil { | ||
return | ||
} | ||
|
||
p.mtx.Lock() | ||
defer p.mtx.Unlock() | ||
|
||
p.delegate = provider | ||
for _, t := range p.tracers { | ||
t.setDelegate(provider) | ||
} | ||
|
||
p.tracers = nil | ||
} | ||
|
||
// Tracer implements trace.Provider. | ||
func (p *traceProvider) Tracer(name string) trace.Tracer { | ||
p.mtx.Lock() | ||
defer p.mtx.Unlock() | ||
|
||
if p.delegate != nil { | ||
return p.delegate.Tracer(name) | ||
} | ||
|
||
t := &tracer{name: name} | ||
p.tracers = append(p.tracers, t) | ||
return t | ||
} | ||
|
||
// tracer is a placeholder for a trace.Tracer. | ||
// | ||
// All Tracer functionality is forwarded to a delegate once configured. | ||
// Otherwise, all functionality is forwarded to a NoopTracer. | ||
type tracer struct { | ||
once sync.Once | ||
name string | ||
|
||
delegate trace.Tracer | ||
} | ||
|
||
// Compile-time guarantee that tracer implements the trace.Tracer interface. | ||
var _ trace.Tracer = &tracer{} | ||
|
||
// setDelegate configures t to delegate all Tracer functionality to Tracers | ||
// created by provider. | ||
// | ||
// All subsequent calls to the Tracer methods will be passed to the delegate. | ||
// | ||
// Delegation only happens on the first call to this method. All subsequent | ||
// calls result in no delegation changes. | ||
func (t *tracer) setDelegate(provider trace.Provider) { | ||
t.once.Do(func() { t.delegate = provider.Tracer(t.name) }) | ||
} | ||
|
||
// WithSpan implements trace.Tracer by forwarding the call to t.delegate if | ||
// set, otherwise it forwards the call to a NoopTracer. | ||
func (t *tracer) WithSpan(ctx context.Context, name string, body func(context.Context) error) error { | ||
if t.delegate != nil { | ||
return t.delegate.WithSpan(ctx, name, body) | ||
} | ||
return trace.NoopTracer{}.WithSpan(ctx, name, body) | ||
} | ||
|
||
// Start implements trace.Tracer by forwarding the call to t.delegate if | ||
// set, otherwise it forwards the call to a NoopTracer. | ||
func (t *tracer) Start(ctx context.Context, name string, opts ...trace.StartOption) (context.Context, trace.Span) { | ||
if t.delegate != nil { | ||
return t.delegate.Start(ctx, name, opts...) | ||
} | ||
return trace.NoopTracer{}.Start(ctx, name, opts...) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package internal_test | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"go.opentelemetry.io/otel/api/global" | ||
"go.opentelemetry.io/otel/api/global/internal" | ||
export "go.opentelemetry.io/otel/sdk/export/trace" | ||
sdktrace "go.opentelemetry.io/otel/sdk/trace" | ||
) | ||
|
||
type testSpanProcesor struct { | ||
// Names of Spans started. | ||
spansStarted []string | ||
// Names of Spans ended. | ||
spansEnded []string | ||
} | ||
|
||
func (t *testSpanProcesor) OnStart(s *export.SpanData) { | ||
t.spansStarted = append(t.spansStarted, s.Name) | ||
} | ||
|
||
func (t *testSpanProcesor) OnEnd(s *export.SpanData) { | ||
t.spansEnded = append(t.spansEnded, s.Name) | ||
} | ||
|
||
func (t *testSpanProcesor) Shutdown() {} | ||
|
||
func TestTraceDefaultSDK(t *testing.T) { | ||
internal.ResetForTest() | ||
|
||
ctx := context.Background() | ||
gtp := global.TraceProvider() | ||
tracer1 := gtp.Tracer("pre") | ||
_, span1 := tracer1.Start(ctx, "span1") | ||
|
||
// This should be dropped. | ||
if err := tracer1.WithSpan(ctx, "withSpan1", func(context.Context) error { return nil }); err != nil { | ||
t.Errorf("failed to wrap function with span prior to initialization: %v", err) | ||
} | ||
|
||
tp, err := sdktrace.NewProvider(sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()})) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
tsp := &testSpanProcesor{} | ||
tp.RegisterSpanProcessor(tsp) | ||
|
||
global.SetTraceProvider(tp) | ||
|
||
// This span was started before initialization, it is expected to be dropped. | ||
span1.End() | ||
|
||
// The existing Tracer should have been configured to now use the configured SDK. | ||
_, span2 := tracer1.Start(ctx, "span2") | ||
span2.End() | ||
if err := tracer1.WithSpan(ctx, "withSpan2", func(context.Context) error { return nil }); err != nil { | ||
t.Errorf("failed to wrap function with span post initialization: %v", err) | ||
} | ||
|
||
// The global trace Provider should now create Tracers that also use the newly configured SDK. | ||
tracer2 := gtp.Tracer("post") | ||
_, span3 := tracer2.Start(ctx, "span3") | ||
span3.End() | ||
if err := tracer2.WithSpan(ctx, "withSpan3", func(context.Context) error { return nil }); err != nil { | ||
t.Errorf("failed to wrap function with span post initialization with new tracer: %v", err) | ||
} | ||
|
||
expected := []string{"pre/span2", "pre/withSpan2", "post/span3", "post/withSpan3"} | ||
require.Equal(t, tsp.spansStarted, expected) | ||
require.Equal(t, tsp.spansEnded, expected) | ||
} |