diff --git a/bridge/opentracing/wrapper.go b/bridge/opentracing/wrapper.go index 5137414ef9a..5f10819809f 100644 --- a/bridge/opentracing/wrapper.go +++ b/bridge/opentracing/wrapper.go @@ -16,6 +16,7 @@ package opentracing // import "go.opentelemetry.io/otel/bridge/opentracing" import ( "context" + "sync" "go.opentelemetry.io/otel/bridge/opentracing/migration" "go.opentelemetry.io/otel/trace" @@ -46,12 +47,37 @@ func NewWrappedTracerProvider(bridge *BridgeTracer, tracer trace.Tracer) *Wrappe } } +type wrappedTracerKey struct { + name string + version string +} + // NewDynamicWrappedTracerProvider creates a new trace provider that creates new // instances of WrapperTracer that wraps OpenTelemetry tracer for each call to Tracer(). func NewDynamicWrappedTracerProvider(bridge *BridgeTracer, provider trace.TracerProvider) *WrapperTracerProvider { + var ( + mtx sync.Mutex + tracers = make(map[wrappedTracerKey]*WrapperTracer) + ) + return &WrapperTracerProvider{ getWrappedTracer: func(name string, opts ...trace.TracerOption) *WrapperTracer { - return NewWrapperTracer(bridge, provider.Tracer(name, opts...)) + mtx.Lock() + defer mtx.Unlock() + + c := trace.NewTracerConfig(opts...) + key := wrappedTracerKey{ + name: name, + version: c.InstrumentationVersion(), + } + + if t, ok := tracers[key]; ok { + return t + } + + wrapper := NewWrapperTracer(bridge, provider.Tracer(name, opts...)) + tracers[key] = wrapper + return wrapper }, } } diff --git a/bridge/opentracing/wrapper_test.go b/bridge/opentracing/wrapper_test.go new file mode 100644 index 00000000000..f98277fc2e2 --- /dev/null +++ b/bridge/opentracing/wrapper_test.go @@ -0,0 +1,71 @@ +package opentracing + +import ( + "testing" + + "go.opentelemetry.io/otel/bridge/opentracing/internal" + "go.opentelemetry.io/otel/trace" +) + +type namedMockTracer struct { + name string + *internal.MockTracer +} + +type namedMockTracerProvider struct{} + +var _ trace.TracerProvider = (*namedMockTracerProvider)(nil) + +// Tracer returns the WrapperTracer associated with the WrapperTracerProvider. +func (p *namedMockTracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer { + return &namedMockTracer{ + name: name, + MockTracer: internal.NewMockTracer(), + } +} + +func TestDynamicWrappedTracerProvider(t *testing.T) { + // assertMockTracerName casts tracer into a named mock tracer provided by + // namedMockTracerProvider, and asserts against its name + assertMockTracerName := func(t *testing.T, tracer trace.Tracer, name string) { + // Unwrap the tracer + wrapped := tracer.(*WrapperTracer) + tracer = wrapped.tracer + + // Cast into the underlying type and assert + if mock, ok := tracer.(*namedMockTracer); ok { + if name != mock.name { + t.Errorf("expected name %q, got %q", name, mock.name) + } + } else if !ok { + t.Errorf("expected *namedMockTracer, got %T", mock) + } + } + + var ( + foobar = "foobar" + bazbar = "bazbar" + provider = NewDynamicWrappedTracerProvider(nil, &namedMockTracerProvider{}) + ) + + t.Run("Tracers should be created with foobar from provider", func(t *testing.T) { + tracer := provider.Tracer(foobar) + assertMockTracerName(t, tracer, foobar) + }) + + t.Run("Repeated requests to create a tracer should provide the existing tracer", func(t *testing.T) { + tracer1 := provider.Tracer(foobar) + assertMockTracerName(t, tracer1, foobar) + tracer2 := provider.Tracer(foobar) + assertMockTracerName(t, tracer2, foobar) + tracer3 := provider.Tracer(bazbar) + assertMockTracerName(t, tracer3, bazbar) + + if tracer1 != tracer2 { + t.Errorf("expected the same tracer, got different tracers") + } + if tracer1 == tracer3 || tracer2 == tracer3 { + t.Errorf("expected different tracers, got the same tracer") + } + }) +}