From fe211cd3587c29d42ce74245fe7a175b863aa494 Mon Sep 17 00:00:00 2001 From: John Levey Date: Wed, 28 Jun 2023 15:00:15 +0200 Subject: [PATCH] feat: add otel support for ExtractSampledTraceID (#46) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Juraci Paixão Kröhling Co-authored-by: Juraci Paixão Kröhling --- go.mod | 7 +- go.sum | 18 ++++ pkg/server/middleware/tracer.go | 33 +++---- pkg/server/middleware/tracer_test.go | 129 +++++++++++++++++++++++++++ 4 files changed, 169 insertions(+), 18 deletions(-) create mode 100644 pkg/server/middleware/tracer_test.go diff --git a/go.mod b/go.mod index c30b8bf..7a175dc 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/prometheus/common v0.42.0 github.com/prometheus/prometheus v1.8.2-0.20220620125440-d7e7b8e04b5e github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.3 github.com/uber/jaeger-client-go v2.30.0+incompatible github.com/uber/jaeger-lib v2.4.1+incompatible github.com/weaveworks/common v0.0.0-20230119144549-0aaa5abd1e63 @@ -55,7 +55,7 @@ require ( github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect github.com/fatih/color v1.14.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.21.4 // indirect github.com/go-openapi/errors v0.20.3 // indirect @@ -129,7 +129,10 @@ require ( go.opentelemetry.io/collector/semconv v0.73.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 // indirect go.opentelemetry.io/otel v1.14.0 // indirect + go.opentelemetry.io/otel/bridge/opentracing v1.14.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0 // indirect go.opentelemetry.io/otel/metric v0.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.14.0 // indirect go.opentelemetry.io/otel/trace v1.14.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/goleak v1.2.1 // indirect diff --git a/go.sum b/go.sum index 9e7556d..b3e451c 100644 --- a/go.sum +++ b/go.sum @@ -193,6 +193,8 @@ github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= @@ -666,6 +668,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/thanos-io/objstore v0.0.0-20230201072718-11ffbc490204 h1:W4w5Iph7j32Sf1QFWLJDCqvO0WgZS0jHGID+qnq3wV0= github.com/thanos-io/objstore v0.0.0-20230201072718-11ffbc490204/go.mod h1:STSgpY8M6EKF2G/raUFdbIMf2U9GgYlEjAEHJxjvpAo= github.com/themihai/gomemcache v0.0.0-20180902122335-24332e2d58ab h1:7ZR3hmisBWw77ZpO1/o86g+JV3VKlk3d48jopJxzTjU= @@ -728,10 +732,24 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 h1:lE9EJyw go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0/go.mod h1:pcQ3MM3SWvrA71U4GDqv9UFDJ3HQsW7y5ZO3tDTlUdI= go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= +go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= +go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/bridge/opentracing v1.14.0 h1:IHlyjkJCOJQdX70C4r7PXm4LCMmGfGVsU/54KDVCtVI= +go.opentelemetry.io/otel/bridge/opentracing v1.14.0/go.mod h1:9cMHS7NzQ0vKwnrhN2CDXqLKI57TUyl9qLUiGBR/JmU= +go.opentelemetry.io/otel/bridge/opentracing v1.16.0 h1:Bgwi7P5NCV3bv2T13bwG0WfsxaT4SjQ1rDdmFc5P7do= +go.opentelemetry.io/otel/bridge/opentracing v1.16.0/go.mod h1:X2Y6v3RnoiBGtVFd4KoHy/ftHiCJKJXzlv6W2gPsN1Q= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0 h1:sEL90JjOO/4yhquXl5zTAkLLsZ5+MycAgX99SDsxGc8= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0/go.mod h1:oCslUcizYdpKYyS9e8srZEqM6BB8fq41VJBjLAE6z1w= go.opentelemetry.io/otel/metric v0.37.0 h1:pHDQuLQOZwYD+Km0eb657A25NaRzy0a+eLyKfDXedEs= go.opentelemetry.io/otel/metric v0.37.0/go.mod h1:DmdaHfGt54iV6UKxsV9slj2bBRJcKC1B1uvDLIioc1s= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY= +go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM= go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= +go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= +go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= diff --git a/pkg/server/middleware/tracer.go b/pkg/server/middleware/tracer.go index f19b210..4dc08f6 100644 --- a/pkg/server/middleware/tracer.go +++ b/pkg/server/middleware/tracer.go @@ -5,6 +5,7 @@ import ( "net/http" opentracing "github.com/opentracing/opentracing-go" + "go.opentelemetry.io/otel/trace" "github.com/opentracing-contrib/go-stdlib/nethttp" jaeger "github.com/uber/jaeger-client-go" @@ -43,29 +44,29 @@ func (t Tracer) Wrap(next http.Handler) http.Handler { // ExtractTraceID extracts the trace id, if any from the context. func ExtractTraceID(ctx context.Context) (string, bool) { - sp := opentracing.SpanFromContext(ctx) - if sp == nil { - return "", false - } - sctx, ok := sp.Context().(jaeger.SpanContext) - if !ok { - return "", false - } - - return sctx.TraceID().String(), true + traceID, _ := ExtractSampledTraceID(ctx) + return traceID, traceID != "" } // ExtractSampledTraceID works like ExtractTraceID but the returned bool is only // true if the returned trace id is sampled. func ExtractSampledTraceID(ctx context.Context) (string, bool) { + // the most common case, where jaeger and opentracing is used sp := opentracing.SpanFromContext(ctx) - if sp == nil { - return "", false + if sp != nil { + sctx, ok := sp.Context().(jaeger.SpanContext) + if ok { + return sctx.TraceID().String(), sctx.IsSampled() + } } - sctx, ok := sp.Context().(jaeger.SpanContext) - if !ok { - return "", false + + // opentelemetry with and without the bridge + otelSp := trace.SpanFromContext(ctx) + traceID, sampled := otelSp.SpanContext().TraceID(), otelSp.SpanContext().IsSampled() + if traceID.IsValid() { // when noop span is used, the traceID is not valid + return traceID.String(), sampled } - return sctx.TraceID().String(), sctx.IsSampled() + // when nothing is in the context + return "", false } diff --git a/pkg/server/middleware/tracer_test.go b/pkg/server/middleware/tracer_test.go new file mode 100644 index 0000000..8a2c8c2 --- /dev/null +++ b/pkg/server/middleware/tracer_test.go @@ -0,0 +1,129 @@ +package middleware + +import ( + "context" + "go.opentelemetry.io/otel/trace" + "testing" + + opentracing "github.com/opentracing/opentracing-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/uber/jaeger-client-go" + "github.com/uber/jaeger-client-go/config" + "go.opentelemetry.io/otel" + bridge "go.opentelemetry.io/otel/bridge/opentracing" + sdktrace "go.opentelemetry.io/otel/sdk/trace" +) + +func TestExtractSampledTraceID(t *testing.T) { + testCases := []struct { + desc string + ctx func(*testing.T) (context.Context, func()) + empty bool + }{ + { + desc: "OpenTracing with Jaeger", + ctx: getContextWithOpenTracing, + }, + { + desc: "OpenTelemetry", + ctx: getContextWithOpenTelemetry, + }, + { + desc: "OpenTelemetry with the OpentTracing bridge", + ctx: getContextWithOpenTelemetryWithBridge, + }, + { + desc: "No tracer", + ctx: func(_ *testing.T) (context.Context, func()) { + return context.Background(), func() {} + }, + empty: true, + }, + { + desc: "OpenTelemetry with the noop", + ctx: getContextWithOpenTelemetryNoop, + empty: true, + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + ctx, closer := tC.ctx(t) + defer closer() + // test contract of ExtractSampledTraceID + sampledTraceID, sampled := ExtractSampledTraceID(ctx) + traceID, ok := ExtractTraceID(ctx) + + assert.Equal(t, sampledTraceID, traceID, "Expected sampledTraceID to equal traceID") + if tC.empty { + assert.Empty(t, traceID, "Expected traceID to be empty") + assert.False(t, sampled, "Expected sampled to be false") + assert.False(t, ok, "Expected ok to be false") + } else { + assert.NotEmpty(t, traceID, "Expected traceID to be non-empty") + assert.True(t, sampled, "Expected sampled to be true") + assert.True(t, ok, "Expected ok to be true") + } + }) + } +} + +func getContextWithOpenTracing(t *testing.T) (context.Context, func()) { + jCfg, err := config.FromEnv() + require.NoError(t, err) + + jCfg.ServiceName = "test" + jCfg.Sampler.Options = append(jCfg.Sampler.Options, jaeger.SamplerOptions.InitialSampler(jaeger.NewConstSampler(true))) + + tracer, closer, err := jCfg.NewTracer() + require.NoError(t, err) + + opentracing.SetGlobalTracer(tracer) + + sp := opentracing.GlobalTracer().StartSpan("test") + return opentracing.ContextWithSpan(context.Background(), sp), func() { + sp.Finish() + closer.Close() + } +} + +func getContextWithOpenTelemetryWithBridge(t *testing.T) (context.Context, func()) { + previous := otel.GetTracerProvider() + tp := sdktrace.NewTracerProvider() + otel.SetTracerProvider(tp) + + tr := tp.Tracer("test") + + otTracer, _ := bridge.NewTracerPair(tr) + opentracing.SetGlobalTracer(otTracer) + + sp := opentracing.GlobalTracer().StartSpan("test") + return opentracing.ContextWithSpan(context.Background(), sp), func() { + sp.Finish() + otel.SetTracerProvider(previous) + } +} + +func getContextWithOpenTelemetry(t *testing.T) (context.Context, func()) { + previous := otel.GetTracerProvider() + tp := sdktrace.NewTracerProvider() + otel.SetTracerProvider(tp) + + tr := tp.Tracer("test") + ctx, sp := tr.Start(context.Background(), "test") + return ctx, func() { + sp.End() + otel.SetTracerProvider(previous) + } +} + +func getContextWithOpenTelemetryNoop(t *testing.T) (context.Context, func()) { + ctx, sp := trace.NewNoopTracerProvider().Tracer("test").Start(context.Background(), "test") + + // sanity check + require.False(t, sp.SpanContext().TraceID().IsValid()) + + return ctx, func() { + sp.End() + } +}