-
Notifications
You must be signed in to change notification settings - Fork 0
/
tracing.go
165 lines (137 loc) · 4.88 KB
/
tracing.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package main
import (
"context"
"os"
"sync"
"time"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/uptrace/opentelemetry-go-extra/otellogrus"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/propagation"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)
// Context Values key for embedding `Tracer` object.
type tracerKey struct{}
var logLevels = []logrus.Level{
logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel,
logrus.WarnLevel, logrus.InfoLevel, logrus.DebugLevel,
logrus.TraceLevel,
}
var globalMutex sync.RWMutex
var defaultTracer trace.Tracer
// InitTracing initializes a global OpenTelemetry tracer provider singleton.
// Call once before using functions in this package.
// Embeds `Tracer` object in returned context.
// Instruments logrus to mirror to active trace. Must use `WithContext()`
// method.
// Call after initializing logrus.
// libraryName is typically the application's module name.
func InitTracing(ctx context.Context, libraryName string, opts ...sdktrace.TracerProviderOption) (context.Context, trace.Tracer, error) {
exp, err := makeJaegerExporter()
if err != nil {
return ctx, nil, errors.Wrap(err, "error in makeJaegerExporter")
}
opts2 := []sdktrace.TracerProviderOption{
sdktrace.WithBatcher(exp),
}
opts2 = append(opts2, opts...)
tp := sdktrace.NewTracerProvider(opts2...)
otel.SetTracerProvider(tp)
// Setup logrus instrumentation.
// Using logrus.WithContext() will mirror log to embedded span.
// Using WithFields() also converts to log attributes.
logLevel := logrus.GetLevel()
useLevels := []logrus.Level{}
for _, l := range logLevels {
if l <= logLevel {
useLevels = append(useLevels, l)
}
}
logrus.AddHook(otellogrus.NewHook(
otellogrus.WithLevels(useLevels...),
))
tracerCtx, tracer, err := NewTracer(ctx, libraryName)
if err != nil {
return ctx, nil, errors.Wrap(err, "error in NewTracer")
}
if GetDefaultTracer() == nil {
SetDefaultTracer(tracer)
}
// Required for trace propagation between services.
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
return tracerCtx, tracer, err
}
// NewTracer instantiates a new `Tracer` object with a custom library name.
// Must call `InitTracing()` first.
// Library name is set in span attribute `otel.library.name`.
// This is typically the relevant package name.
func NewTracer(ctx context.Context, libraryName string) (context.Context, trace.Tracer, error) {
tp, ok := otel.GetTracerProvider().(*sdktrace.TracerProvider)
if !ok {
return nil, nil, errors.New("OpenTelemetry global tracer provider has not been initialized")
}
tracer := tp.Tracer(libraryName)
ctx = ContextWithTracer(ctx, tracer)
return ctx, tracer, nil
}
// GetDefaultTracer gets the global tracer used as a default by this package.
func GetDefaultTracer() trace.Tracer {
globalMutex.RLock()
defer globalMutex.RUnlock()
return defaultTracer
}
// SetDefaultTracer sets the global tracer used as a default by this package.
func SetDefaultTracer(tracer trace.Tracer) {
globalMutex.Lock()
defer globalMutex.Unlock()
defaultTracer = tracer
}
// CloseTracing closes the global OpenTelemetry tracer provider.
// This allows queued up traces to be flushed.
func CloseTracing(ctx context.Context) error {
tp, ok := otel.GetTracerProvider().(*sdktrace.TracerProvider)
if !ok {
return errors.New("OpenTelemetry global tracer provider has not been initialized")
}
SetDefaultTracer(nil)
ctx, cancel := context.WithTimeout(ctx, 5 * time.Second)
defer cancel()
err := tp.Shutdown(ctx)
if err != nil {
return errors.Wrap(err, "error in tp.Shutdown")
}
return nil
}
// ContextWithTracer creates a context with a tracer object embedded.
// This value is used by scope functions or use TracerFromContext() to retrieve
// it.
func ContextWithTracer(ctx context.Context, tracer trace.Tracer) context.Context {
return context.WithValue(ctx, tracerKey{}, tracer)
}
// TracerFromContext gets embedded `Tracer` from context.
// Returns nil if not found.
func TracerFromContext(ctx context.Context) trace.Tracer {
tracer, _ := ctx.Value(tracerKey{}).(trace.Tracer)
return tracer
}
func makeJaegerExporter() (*jaeger.Exporter, error) {
var agentEndpointOpts []jaeger.AgentEndpointOption
agentHost := os.Getenv("OTEL_EXPORTER_JAEGER_AGENT_HOST")
if agentHost != "" && agentHost != "localhost" && agentHost != "127.0.0.1" {
// Default MaxPacketSize=65000, which only works with Jaeger agent on
// localhost (loopback interface).
// For tracing over network, packets must fit in MTU 1500, which has a
// payload size of 1472.
agentEndpointOpts = append(agentEndpointOpts, jaeger.WithMaxPacketSize(1472))
}
exp, err := jaeger.New(
jaeger.WithAgentEndpoint(agentEndpointOpts...),
)
if err != nil {
return nil, errors.Wrap(err, "error in jaeger.New")
}
return exp, nil
}