fork of https://github.com/sourcegraph/zoekt
1package tracer
2
3import (
4 "context"
5 "fmt"
6 "regexp"
7
8 "github.com/opentracing/opentracing-go"
9 "github.com/pkg/errors"
10 sglog "github.com/sourcegraph/log"
11 jaegerpropagator "go.opentelemetry.io/contrib/propagators/jaeger"
12 otpropagator "go.opentelemetry.io/contrib/propagators/ot"
13 "go.opentelemetry.io/otel"
14 otelbridge "go.opentelemetry.io/otel/bridge/opentracing"
15 "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
16 "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
17 "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
18 w3cpropagator "go.opentelemetry.io/otel/propagation"
19 otelresource "go.opentelemetry.io/otel/sdk/resource"
20 oteltracesdk "go.opentelemetry.io/otel/sdk/trace"
21 semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
22
23 "github.com/sourcegraph/zoekt/internal/otlpenv"
24)
25
26// configureOpenTelemetry creates an opentracing.Tracer that exports all OpenTracing traces
27// as OpenTelemetry traces to an OpenTelemetry collector (effectively "bridging" the two
28// APIs). This enables us to continue leveraging the OpenTracing API (which is a predecessor
29// to OpenTelemetry tracing) without making changes to existing tracing code.
30//
31// All configuration is sourced directly from the environment using the specification
32// laid out in https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md
33//
34// This setup is based on the one done in sourcegraph/sourcegraph - when making changes,
35// be wary of divergences from the source: https://github.com/sourcegraph/sourcegraph/blob/main/internal/tracer/otel.go
36func configureOpenTelemetry(resource sglog.Resource) (opentracing.Tracer, error) {
37 // Ensure propagation between services continues to work. This is also done by another
38 // project that uses the OpenTracing bridge:
39 // https://sourcegraph.com/github.com/thanos-io/thanos/-/blob/pkg/tracing/migration/bridge.go?L62
40 compositePropagator := w3cpropagator.NewCompositeTextMapPropagator(
41 jaegerpropagator.Jaeger{},
42 otpropagator.OT{},
43 w3cpropagator.TraceContext{},
44 w3cpropagator.Baggage{},
45 )
46 otel.SetTextMapPropagator(compositePropagator)
47
48 // Initialize OpenTelemetry processor and tracer provider
49 processor, err := newOTelCollectorExporter(context.Background(), otlpenv.GetEndpoint())
50 if err != nil {
51 return nil, fmt.Errorf("new exporter: %w", err)
52 }
53 provider := oteltracesdk.NewTracerProvider(
54 oteltracesdk.WithResource(otelresource.NewWithAttributes(
55 semconv.SchemaURL,
56 semconv.ServiceNameKey.String(resource.Name),
57 semconv.ServiceInstanceIDKey.String(resource.InstanceID),
58 semconv.ServiceVersionKey.String(resource.Version))),
59 oteltracesdk.WithSampler(oteltracesdk.ParentBased(oteltracesdk.NeverSample())),
60 oteltracesdk.WithSpanProcessor(processor),
61 )
62
63 // Set up bridge for converting opentracing API calls to OpenTelemetry.
64 bridge, otelTracerProvider := otelbridge.NewTracerPair(provider.Tracer("tracer.global"))
65 bridge.SetTextMapPropagator(compositePropagator)
66 otel.SetTracerProvider(otelTracerProvider)
67 otelLogger := sglog.Scoped("otel")
68 otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
69 otelLogger.Debug("error encountered", sglog.Error(err))
70 }))
71
72 // Done
73 return bridge, nil
74}
75
76// newOTelCollectorExporter creates a processor that exports spans to an OpenTelemetry
77// collector.
78func newOTelCollectorExporter(ctx context.Context, endpoint string) (oteltracesdk.SpanProcessor, error) {
79 // Set up client to otel-collector - we replicate some of the logic used internally in
80 // https://github.com/open-telemetry/opentelemetry-go/blob/21c1641831ca19e3acf341cc11459c87b9791f2f/exporters/otlp/internal/otlpconfig/envconfig.go
81 // based on our own inferred endpoint.
82 var (
83 client otlptrace.Client
84 protocol = otlpenv.GetProtocol()
85 trimmedEndpoint = trimSchema(endpoint)
86 insecure = otlpenv.IsInsecure(endpoint)
87 )
88
89 // Work with different protocols
90 switch protocol {
91 case otlpenv.ProtocolGRPC:
92 opts := []otlptracegrpc.Option{
93 otlptracegrpc.WithEndpoint(trimmedEndpoint),
94 }
95 if insecure {
96 opts = append(opts, otlptracegrpc.WithInsecure())
97 }
98 client = otlptracegrpc.NewClient(opts...)
99
100 case otlpenv.ProtocolHTTPJSON:
101 opts := []otlptracehttp.Option{
102 otlptracehttp.WithEndpoint(trimmedEndpoint),
103 }
104 if insecure {
105 opts = append(opts, otlptracehttp.WithInsecure())
106 }
107 client = otlptracehttp.NewClient(opts...)
108 }
109
110 // Initialize the exporter
111 traceExporter, err := otlptrace.New(ctx, client)
112 if err != nil {
113 return nil, errors.Wrap(err, "failed to create trace exporter")
114 }
115
116 return oteltracesdk.NewBatchSpanProcessor(traceExporter), nil
117}
118
119var httpSchemeRegexp = regexp.MustCompile(`(?i)^http://|https://`)
120
121func trimSchema(endpoint string) string {
122 return httpSchemeRegexp.ReplaceAllString(endpoint, "")
123}