fork of https://github.com/sourcegraph/zoekt
1package tracer
2
3import (
4 "context"
5 "fmt"
6 "log"
7 "regexp"
8
9 "github.com/opentracing/opentracing-go"
10 "github.com/pkg/errors"
11 sglog "github.com/sourcegraph/log"
12 jaegerpropagator "go.opentelemetry.io/contrib/propagators/jaeger"
13 otpropagator "go.opentelemetry.io/contrib/propagators/ot"
14 "go.opentelemetry.io/otel"
15 otelbridge "go.opentelemetry.io/otel/bridge/opentracing"
16 "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
17 "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
18 "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
19 w3cpropagator "go.opentelemetry.io/otel/propagation"
20 otelresource "go.opentelemetry.io/otel/sdk/resource"
21 oteltracesdk "go.opentelemetry.io/otel/sdk/trace"
22 semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
23
24 "github.com/sourcegraph/zoekt/internal/otlpenv"
25)
26
27// configureOpenTelemetry creates an opentracing.Tracer that exports all OpenTracing traces
28// as OpenTelemetry traces to an OpenTelemetry collector (effectively "bridging" the two
29// APIs). This enables us to continue leveraging the OpenTracing API (which is a predecessor
30// to OpenTelemetry tracing) without making changes to existing tracing code.
31//
32// All configuration is sourced directly from the environment using the specification
33// laid out in https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md
34//
35// This setup is based on the one done in sourcegraph/sourcegraph - when making changes,
36// be wary of divergences from the source: https://github.com/sourcegraph/sourcegraph/blob/main/internal/tracer/otel.go
37func configureOpenTelemetry(resource sglog.Resource) (opentracing.Tracer, error) {
38 // Ensure propagation between services continues to work. This is also done by another
39 // project that uses the OpenTracing bridge:
40 // https://sourcegraph.com/github.com/thanos-io/thanos/-/blob/pkg/tracing/migration/bridge.go?L62
41 compositePropagator := w3cpropagator.NewCompositeTextMapPropagator(
42 jaegerpropagator.Jaeger{},
43 otpropagator.OT{},
44 w3cpropagator.TraceContext{},
45 w3cpropagator.Baggage{},
46 )
47 otel.SetTextMapPropagator(compositePropagator)
48
49 // Initialize OpenTelemetry processor and tracer provider
50 processor, err := newOTelCollectorExporter(context.Background(), otlpenv.GetEndpoint())
51 if err != nil {
52 return nil, fmt.Errorf("new exporter: %w", err)
53 }
54 provider := oteltracesdk.NewTracerProvider(
55 oteltracesdk.WithResource(otelresource.NewWithAttributes(
56 semconv.SchemaURL,
57 semconv.ServiceNameKey.String(resource.Name),
58 semconv.ServiceInstanceIDKey.String(resource.InstanceID),
59 semconv.ServiceVersionKey.String(resource.Version))),
60 oteltracesdk.WithSampler(oteltracesdk.ParentBased(oteltracesdk.NeverSample())),
61 oteltracesdk.WithSpanProcessor(processor),
62 )
63
64 // Set up bridge for converting opentracing API calls to OpenTelemetry.
65 bridge, otelTracerProvider := otelbridge.NewTracerPair(provider.Tracer("tracer.global"))
66 bridge.SetTextMapPropagator(compositePropagator)
67 otel.SetTracerProvider(otelTracerProvider)
68 otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
69 log.Println("opentelemetry: ", err.Error())
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}