diff --git a/cmd/all-in-one/all_in_one_test.go b/cmd/all-in-one/all_in_one_test.go index deb89a04e25..6bb20c12560 100644 --- a/cmd/all-in-one/all_in_one_test.go +++ b/cmd/all-in-one/all_in_one_test.go @@ -23,6 +23,7 @@ import ( "encoding/json" "io" "net/http" + "strings" "testing" "time" @@ -46,12 +47,13 @@ const ( agentURL = "http://" + agentHostPort getServicesURL = queryURL + "/api/services" - getTraceURL = queryURL + "/api/traces?service=jaeger-query&tag=jaeger-debug-id:debug" getSamplingStrategyURL = agentURL + "/sampling?service=whatever" getServicesAPIV3URL = queryURL + "/api/v3/services" ) +var getTraceURL = queryURL + "/api/traces/" + var httpClient = &http.Client{ Timeout: time.Second, } @@ -70,11 +72,15 @@ func TestAllInOne(t *testing.T) { func createTrace(t *testing.T) { req, err := http.NewRequest(http.MethodGet, getServicesURL, nil) require.NoError(t, err) - req.Header.Add("jaeger-debug-id", "debug") resp, err := httpClient.Do(req) require.NoError(t, err) - resp.Body.Close() + defer resp.Body.Close() + traceResponse := resp.Header.Get("traceresponse") + parts := strings.Split(traceResponse, "-") + require.Len(t, parts, 4) // [version] [trace-id] [child-id] [trace-flags] + traceID := parts[1] + getTraceURL += traceID } type response struct { @@ -157,5 +163,5 @@ func getServicesAPIV3(t *testing.T) { jsonpb := runtime.JSONPb{} err = jsonpb.Unmarshal(body, &servicesResponse) require.NoError(t, err) - assert.Equal(t, []string{"jaeger-query"}, servicesResponse.GetServices()) + assert.Equal(t, []string{"jaeger-all-in-one"}, servicesResponse.GetServices()) } diff --git a/cmd/all-in-one/main.go b/cmd/all-in-one/main.go index b40e427cfa7..34d86f534ce 100644 --- a/cmd/all-in-one/main.go +++ b/cmd/all-in-one/main.go @@ -16,16 +16,14 @@ package main import ( + "context" "fmt" "io" "log" "os" - "github.com/opentracing/opentracing-go" "github.com/spf13/cobra" "github.com/spf13/viper" - jaegerClientConfig "github.com/uber/jaeger-client-go/config" - jaegerClientZapLog "github.com/uber/jaeger-client-go/log/zap" _ "go.uber.org/automaxprocs" "go.uber.org/zap" @@ -43,7 +41,6 @@ import ( "github.com/jaegertracing/jaeger/cmd/status" "github.com/jaegertracing/jaeger/internal/metrics/expvar" "github.com/jaegertracing/jaeger/internal/metrics/fork" - "github.com/jaegertracing/jaeger/internal/metrics/jlibadapter" "github.com/jaegertracing/jaeger/pkg/config" "github.com/jaegertracing/jaeger/pkg/jtracer" "github.com/jaegertracing/jaeger/pkg/metrics" @@ -103,7 +100,10 @@ by default uses only in-memory database.`, svc.MetricsFactory.Namespace(metrics.NSOptions{Name: "jaeger"})) version.NewInfoMetrics(metricsFactory) - tracerCloser := initTracer(svc) + tracer, err := jtracer.New("jaeger-all-in-one") + if err != nil { + logger.Fatal("Failed to initialize tracer", zap.Error(err)) + } storageFactory.InitFromViper(v, logger) if err := storageFactory.Initialize(metricsFactory, logger); err != nil { @@ -197,7 +197,7 @@ by default uses only in-memory database.`, querySrv := startQuery( svc, qOpts, qOpts.BuildQueryServiceOptions(storageFactory, logger), spanReader, dependencyReader, metricsQueryService, - metricsFactory, tm, + metricsFactory, tm, tracer, ) svc.RunAndThen(func() { @@ -213,7 +213,9 @@ by default uses only in-memory database.`, if err := storageFactory.Close(); err != nil { logger.Error("Failed to close storage factory", zap.Error(err)) } - _ = tracerCloser.Close() + if err := tracer.Close(context.Background()); err != nil { + logger.Error("Error shutting down tracer provider", zap.Error(err)) + } }) return nil }, @@ -271,13 +273,13 @@ func startQuery( metricsQueryService querysvc.MetricsQueryService, baseFactory metrics.Factory, tm *tenancy.Manager, + jt *jtracer.JTracer, ) *queryApp.Server { spanReader = storageMetrics.NewReadMetricsDecorator(spanReader, baseFactory.Namespace(metrics.NSOptions{Name: "query"})) qs := querysvc.NewQueryService(spanReader, depReader, *queryOpts) - jtracer := jtracer.OT(opentracing.GlobalTracer()) - server, err := queryApp.NewServer(svc.Logger, qs, metricsQueryService, qOpts, tm, jtracer) + server, err := queryApp.NewServer(svc.Logger, qs, metricsQueryService, qOpts, tm, jt) if err != nil { - svc.Logger.Fatal("Could not start jaeger-query service", zap.Error(err)) + svc.Logger.Fatal("Could not create jaeger-query", zap.Error(err)) } go func() { for s := range server.HealthCheckStatus() { @@ -285,34 +287,10 @@ func startQuery( } }() if err := server.Start(); err != nil { - svc.Logger.Fatal("Could not start jaeger-query service", zap.Error(err)) + svc.Logger.Fatal("Could not start jaeger-query", zap.Error(err)) } - return server -} -func initTracer(svc *flags.Service) io.Closer { - logger := svc.Logger - traceCfg := &jaegerClientConfig.Configuration{ - ServiceName: "jaeger-query", - Sampler: &jaegerClientConfig.SamplerConfig{ - Type: "const", - Param: 1.0, - }, - RPCMetrics: true, - } - traceCfg, err := traceCfg.FromEnv() - if err != nil { - logger.Fatal("Failed to read tracer configuration", zap.Error(err)) - } - tracer, closer, err := traceCfg.NewTracer( - jaegerClientConfig.Metrics(jlibadapter.NewAdapter(svc.MetricsFactory)), - jaegerClientConfig.Logger(jaegerClientZapLog.NewLogger(logger)), - ) - if err != nil { - logger.Fatal("Failed to initialize tracer", zap.Error(err)) - } - opentracing.SetGlobalTracer(tracer) - return closer + return server } func createMetricsQueryService( diff --git a/cmd/query/app/grpc_handler.go b/cmd/query/app/grpc_handler.go index 57827073000..84e18506d6b 100644 --- a/cmd/query/app/grpc_handler.go +++ b/cmd/query/app/grpc_handler.go @@ -53,14 +53,14 @@ type GRPCHandler struct { queryService *querysvc.QueryService metricsQueryService querysvc.MetricsQueryService logger *zap.Logger - tracer jtracer.JTracer + tracer *jtracer.JTracer nowFn func() time.Time } // GRPCHandlerOptions contains optional members of GRPCHandler. type GRPCHandlerOptions struct { Logger *zap.Logger - Tracer jtracer.JTracer + Tracer *jtracer.JTracer NowFn func() time.Time } @@ -73,7 +73,7 @@ func NewGRPCHandler(queryService *querysvc.QueryService, options.Logger = zap.NewNop() } - if options.Tracer.OT == nil { + if options.Tracer == nil { options.Tracer = jtracer.NoOp() } diff --git a/cmd/query/app/grpc_handler_test.go b/cmd/query/app/grpc_handler_test.go index a82ebe94afd..19c02502efa 100644 --- a/cmd/query/app/grpc_handler_test.go +++ b/cmd/query/app/grpc_handler_test.go @@ -145,7 +145,7 @@ type grpcClient struct { conn *grpc.ClientConn } -func newGRPCServer(t *testing.T, q *querysvc.QueryService, mq querysvc.MetricsQueryService, logger *zap.Logger, tracer jtracer.JTracer, tenancyMgr *tenancy.Manager) (*grpc.Server, net.Addr) { +func newGRPCServer(t *testing.T, q *querysvc.QueryService, mq querysvc.MetricsQueryService, logger *zap.Logger, tracer *jtracer.JTracer, tenancyMgr *tenancy.Manager) (*grpc.Server, net.Addr) { lis, _ := net.Listen("tcp", ":0") var grpcOpts []grpc.ServerOption if tenancyMgr.Enabled { diff --git a/cmd/query/app/handler_options.go b/cmd/query/app/handler_options.go index 78d34db8221..1b87fd6c524 100644 --- a/cmd/query/app/handler_options.go +++ b/cmd/query/app/handler_options.go @@ -62,7 +62,7 @@ func (handlerOptions) QueryLookbackDuration(queryLookbackDuration time.Duration) } // Tracer creates a HandlerOption that initializes OpenTracing tracer -func (handlerOptions) Tracer(tracer jtracer.JTracer) HandlerOption { +func (handlerOptions) Tracer(tracer *jtracer.JTracer) HandlerOption { return func(apiHandler *APIHandler) { apiHandler.tracer = tracer } diff --git a/cmd/query/app/http_handler.go b/cmd/query/app/http_handler.go index 9714483a4f2..d3c10e4344f 100644 --- a/cmd/query/app/http_handler.go +++ b/cmd/query/app/http_handler.go @@ -27,7 +27,8 @@ import ( "github.com/gogo/protobuf/proto" "github.com/gorilla/mux" - "github.com/opentracing-contrib/go-stdlib/nethttp" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel/propagation" "go.uber.org/zap" "github.com/jaegertracing/jaeger/cmd/query/app/querysvc" @@ -88,7 +89,7 @@ type APIHandler struct { basePath string apiPrefix string logger *zap.Logger - tracer jtracer.JTracer + tracer *jtracer.JTracer } // NewAPIHandler returns an APIHandler @@ -111,7 +112,7 @@ func NewAPIHandler(queryService *querysvc.QueryService, tm *tenancy.Manager, opt if aH.logger == nil { aH.logger = zap.NewNop() } - if aH.tracer.OT == nil { + if aH.tracer == nil { aH.tracer = jtracer.NoOp() } return aH @@ -146,12 +147,10 @@ func (aH *APIHandler) handleFunc( if aH.tenancyMgr.Enabled { handler = tenancy.ExtractTenantHTTPHandler(aH.tenancyMgr, handler) } - traceMiddleware := nethttp.Middleware( - aH.tracer.OT, - handler, - nethttp.OperationNameFunc(func(r *http.Request) string { - return route - })) + traceMiddleware := otelhttp.NewHandler( + otelhttp.WithRouteTag(route, traceResponseHandler(handler)), + route, + otelhttp.WithTracerProvider(aH.tracer.OTEL)) return router.HandleFunc(route, traceMiddleware.ServeHTTP) } @@ -523,3 +522,18 @@ func (aH *APIHandler) writeJSON(w http.ResponseWriter, r *http.Request, response aH.handleError(w, fmt.Errorf("failed writing HTTP response: %w", err), http.StatusInternalServerError) } } + +// Returns a handler that generates a traceresponse header. +// https://github.com/w3c/trace-context/blob/main/spec/21-http_response_header_format.md +func traceResponseHandler(handler http.Handler) http.Handler { + // We use the standard TraceContext propagator, since the formats are identical. + // But the propagator uses "traceparent" header name, so we inject it into a map + // `carrier` and then use the result to set the "tracereponse" header. + var prop propagation.TraceContext + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + carrier := make(map[string]string) + prop.Inject(r.Context(), propagation.MapCarrier(carrier)) + w.Header().Add("traceresponse", carrier["traceparent"]) + handler.ServeHTTP(w, r) + }) +} diff --git a/cmd/query/app/http_handler_test.go b/cmd/query/app/http_handler_test.go index 04604526ac6..6cbfa41c3da 100644 --- a/cmd/query/app/http_handler_test.go +++ b/cmd/query/app/http_handler_test.go @@ -35,7 +35,8 @@ import ( testHttp "github.com/stretchr/testify/http" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/uber/jaeger-client-go" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -303,12 +304,15 @@ func TestGetTrace(t *testing.T) { for _, tc := range testCases { testCase := tc // capture loop var t.Run(testCase.suffix, func(t *testing.T) { - reporter := jaeger.NewInMemoryReporter() - jaegerTracer, jaegerCloser := jaeger.NewTracer("test", jaeger.NewConstSampler(true), reporter) - jTracer := jtracer.OT(jaegerTracer) - defer jaegerCloser.Close() - - ts := initializeTestServer(HandlerOptions.Tracer(jTracer)) + exporter := tracetest.NewInMemoryExporter() + tracerProvider := sdktrace.NewTracerProvider( + sdktrace.WithSyncer(exporter), + sdktrace.WithSampler(sdktrace.AlwaysSample()), + ) + jTracer := jtracer.JTracer{OTEL: tracerProvider} + defer tracerProvider.Shutdown(context.Background()) + + ts := initializeTestServer(HandlerOptions.Tracer(&jTracer)) defer ts.server.Close() ts.spanReader.On("GetTrace", mock.AnythingOfType("*context.valueCtx"), model.NewTraceID(0, 0x123456abc)). @@ -319,8 +323,8 @@ func TestGetTrace(t *testing.T) { assert.NoError(t, err) assert.Len(t, response.Errors, 0) - assert.Len(t, reporter.GetSpans(), 1, "HTTP request was traced and span reported") - assert.Equal(t, "/api/traces/{traceID}", reporter.GetSpans()[0].(*jaeger.Span).OperationName()) + assert.Len(t, exporter.GetSpans(), 1, "HTTP request was traced and span reported") + assert.Equal(t, "/api/traces/{traceID}", exporter.GetSpans()[0].Name) traces := extractTraces(t, &response) assert.Len(t, traces[0].Spans, 2) diff --git a/cmd/query/app/server.go b/cmd/query/app/server.go index 2c9e834f324..d12f1770cbb 100644 --- a/cmd/query/app/server.go +++ b/cmd/query/app/server.go @@ -51,7 +51,7 @@ type Server struct { querySvc *querysvc.QueryService queryOptions *QueryOptions - tracer jtracer.JTracer // TODO make part of flags.Service + tracer *jtracer.JTracer // TODO make part of flags.Service conn net.Listener grpcConn net.Listener @@ -65,7 +65,7 @@ type Server struct { } // NewServer creates and initializes Server -func NewServer(logger *zap.Logger, querySvc *querysvc.QueryService, metricsQuerySvc querysvc.MetricsQueryService, options *QueryOptions, tm *tenancy.Manager, tracer jtracer.JTracer) (*Server, error) { +func NewServer(logger *zap.Logger, querySvc *querysvc.QueryService, metricsQuerySvc querysvc.MetricsQueryService, options *QueryOptions, tm *tenancy.Manager, tracer *jtracer.JTracer) (*Server, error) { _, httpPort, err := net.SplitHostPort(options.HTTPHostPort) if err != nil { return nil, err @@ -107,7 +107,7 @@ func (s Server) HealthCheckStatus() chan healthcheck.Status { return s.unavailableChannel } -func createGRPCServer(querySvc *querysvc.QueryService, metricsQuerySvc querysvc.MetricsQueryService, options *QueryOptions, tm *tenancy.Manager, logger *zap.Logger, tracer jtracer.JTracer) (*grpc.Server, error) { +func createGRPCServer(querySvc *querysvc.QueryService, metricsQuerySvc querysvc.MetricsQueryService, options *QueryOptions, tm *tenancy.Manager, logger *zap.Logger, tracer *jtracer.JTracer) (*grpc.Server, error) { var grpcOpts []grpc.ServerOption if options.TLSGRPC.Enabled { @@ -148,7 +148,7 @@ func createGRPCServer(querySvc *querysvc.QueryService, metricsQuerySvc querysvc. return server, nil } -func createHTTPServer(querySvc *querysvc.QueryService, metricsQuerySvc querysvc.MetricsQueryService, queryOpts *QueryOptions, tm *tenancy.Manager, tracer jtracer.JTracer, logger *zap.Logger) (*http.Server, context.CancelFunc, error) { +func createHTTPServer(querySvc *querysvc.QueryService, metricsQuerySvc querysvc.MetricsQueryService, queryOpts *QueryOptions, tm *tenancy.Manager, tracer *jtracer.JTracer, logger *zap.Logger) (*http.Server, context.CancelFunc, error) { apiHandlerOptions := []HandlerOption{ HandlerOptions.Logger(logger), HandlerOptions.Tracer(tracer), diff --git a/cmd/query/main.go b/cmd/query/main.go index ab88bef240e..aadb7915787 100644 --- a/cmd/query/main.go +++ b/cmd/query/main.go @@ -16,15 +16,13 @@ package main import ( + "context" "fmt" "log" "os" - "github.com/opentracing/opentracing-go" "github.com/spf13/cobra" "github.com/spf13/viper" - jaegerClientConfig "github.com/uber/jaeger-client-go/config" - jaegerClientZapLog "github.com/uber/jaeger-client-go/log/zap" _ "go.uber.org/automaxprocs" "go.uber.org/zap" @@ -34,7 +32,6 @@ import ( "github.com/jaegertracing/jaeger/cmd/query/app" "github.com/jaegertracing/jaeger/cmd/query/app/querysvc" "github.com/jaegertracing/jaeger/cmd/status" - "github.com/jaegertracing/jaeger/internal/metrics/jlibadapter" "github.com/jaegertracing/jaeger/pkg/bearertoken" "github.com/jaegertracing/jaeger/pkg/config" "github.com/jaegertracing/jaeger/pkg/jtracer" @@ -75,29 +72,10 @@ func main() { baseFactory := svc.MetricsFactory.Namespace(metrics.NSOptions{Name: "jaeger"}) metricsFactory := baseFactory.Namespace(metrics.NSOptions{Name: "query"}) version.NewInfoMetrics(metricsFactory) - - traceCfg := &jaegerClientConfig.Configuration{ - ServiceName: "jaeger-query", - Sampler: &jaegerClientConfig.SamplerConfig{ - Type: "const", - Param: 1.0, - }, - RPCMetrics: true, - } - traceCfg, err = traceCfg.FromEnv() + jtracer, err := jtracer.New("jaeger-query") if err != nil { - logger.Fatal("Failed to read tracer configuration", zap.Error(err)) + logger.Fatal("Failed to create tracer:", zap.Error(err)) } - tracer, closer, err := traceCfg.NewTracer( - jaegerClientConfig.Metrics(jlibadapter.NewAdapter(svc.MetricsFactory)), - jaegerClientConfig.Logger(jaegerClientZapLog.NewLogger(logger)), - ) - if err != nil { - logger.Fatal("Failed to initialize tracer", zap.Error(err)) - } - defer closer.Close() - opentracing.SetGlobalTracer(tracer) - jtracer := jtracer.OT(tracer) queryOpts, err := new(app.QueryOptions).InitFromViper(v, logger) if err != nil { logger.Fatal("Failed to configure query service", zap.Error(err)) @@ -148,6 +126,9 @@ func main() { if err := storageFactory.Close(); err != nil { logger.Error("Failed to close storage factory", zap.Error(err)) } + if err = jtracer.Close(context.Background()); err != nil { + logger.Fatal("Error shutting down tracer provider", zap.Error(err)) + } }) return nil }, diff --git a/pkg/jtracer/.nocover b/pkg/jtracer/.nocover new file mode 100644 index 00000000000..9d6cf4b7fb6 --- /dev/null +++ b/pkg/jtracer/.nocover @@ -0,0 +1 @@ +FIXME diff --git a/pkg/jtracer/jtracer.go b/pkg/jtracer/jtracer.go index d502767dc86..32e9c56c241 100644 --- a/pkg/jtracer/jtracer.go +++ b/pkg/jtracer/jtracer.go @@ -14,16 +14,98 @@ package jtracer -import "github.com/opentracing/opentracing-go" +import ( + "context" + "fmt" + "sync" + + "github.com/opentracing/opentracing-go" + "go.opentelemetry.io/otel" + otbridge "go.opentelemetry.io/otel/bridge/opentracing" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.20.0" + "go.opentelemetry.io/otel/trace" +) type JTracer struct { - OT opentracing.Tracer + OT opentracing.Tracer + OTEL trace.TracerProvider + closer func(ctx context.Context) error +} + +var once sync.Once + +func New(serviceName string) (*JTracer, error) { + ctx := context.Background() + tracerProvider, err := initOTEL(ctx, serviceName) + if err != nil { + return nil, err + } + // Use the bridgeTracer as your OpenTracing tracer(otTrace). + otelTracer := tracerProvider.Tracer("github.com/jaegertracing/jaeger/pkg/jtracer") + otTracer, wrappedTracerProvider := otbridge.NewTracerPair(otelTracer) + + closer := func(ctx context.Context) error { + return tracerProvider.Shutdown(ctx) + } + + return &JTracer{ + OT: otTracer, + OTEL: wrappedTracerProvider, + closer: closer, + }, nil +} + +func NoOp() *JTracer { + return &JTracer{OT: opentracing.NoopTracer{}, OTEL: trace.NewNoopTracerProvider()} } -func OT(t opentracing.Tracer) JTracer { - return JTracer{OT: t} +// initOTEL initializes OTEL Tracer +func initOTEL(ctx context.Context, svc string) (*sdktrace.TracerProvider, error) { + traceExporter, err := otelExporter(ctx) + if err != nil { + return nil, err + } + + // Register the trace exporter with a TracerProvider, using a batch + // span processor to aggregate spans before export. + bsp := sdktrace.NewBatchSpanProcessor(traceExporter) + tracerProvider := sdktrace.NewTracerProvider( + sdktrace.WithSpanProcessor(bsp), + sdktrace.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String(svc), + )), + ) + + once.Do(func() { + otel.SetTextMapPropagator( + propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, + )) + }) + + return tracerProvider, nil +} + +func otelExporter(ctx context.Context) (sdktrace.SpanExporter, error) { + client := otlptracegrpc.NewClient( + otlptracegrpc.WithInsecure(), + ) + traceExporter, err := otlptrace.New(ctx, client) + if err != nil { + return nil, fmt.Errorf("Failed to create trace exporter: %w", err) + } + + return traceExporter, nil } -func NoOp() JTracer { - return JTracer{OT: opentracing.NoopTracer{}} +// Shutdown the tracerProvider to clean up resources +func (jt *JTracer) Close(ctx context.Context) error { + return jt.closer(ctx) } diff --git a/pkg/jtracer/jtracer_test.go b/pkg/jtracer/jtracer_test.go deleted file mode 100644 index e4bffa19937..00000000000 --- a/pkg/jtracer/jtracer_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2023 The Jaeger Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package jtracer_test - -import ( - "testing" - - "github.com/opentracing/opentracing-go" - "github.com/stretchr/testify/assert" - - "github.com/jaegertracing/jaeger/pkg/jtracer" -) - -func TestOT(t *testing.T) { - mockTracer := opentracing.NoopTracer{} - jtracer := jtracer.OT(opentracing.NoopTracer{}) - - assert.Equal(t, mockTracer, jtracer.OT) -}