forked from ethersphere/bee
/
tracing.go
260 lines (221 loc) · 7.25 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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tracing
import (
"bufio"
"bytes"
"context"
"errors"
"io"
"net/http"
"time"
"github.com/ethersphere/bee/pkg/log"
"github.com/ethersphere/bee/pkg/p2p"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/config"
)
var (
// ErrContextNotFound is returned when tracing context is not present
// in p2p Headers or context.
ErrContextNotFound = errors.New("tracing context not found")
// noopTracer is the tracer that does nothing to handle a nil Tracer usage.
noopTracer = &Tracer{tracer: new(opentracing.NoopTracer)}
)
// contextKey is used to reference a tracing context span as context value.
type contextKey struct{}
// LogField is the key in log message field that holds tracing id value.
const LogField = "traceID"
const (
// TraceContextHeaderName is the http header name used to propagate tracing context.
TraceContextHeaderName = "swarm-trace-id"
// TraceBaggageHeaderPrefix is the prefix for http headers used to propagate baggage.
TraceBaggageHeaderPrefix = "swarmctx-"
)
// Tracer connect to a tracing server and handles tracing spans and contexts
// by using opentracing Tracer.
type Tracer struct {
tracer opentracing.Tracer
}
// Options are optional parameters for Tracer constructor.
type Options struct {
Enabled bool
Endpoint string
ServiceName string
}
// NewTracer creates a new Tracer and returns a closer which needs to be closed
// when the Tracer is no longer used to flush remaining traces.
func NewTracer(o *Options) (*Tracer, io.Closer, error) {
if o == nil {
o = new(Options)
}
cfg := config.Configuration{
Disabled: !o.Enabled,
ServiceName: o.ServiceName,
Sampler: &config.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &config.ReporterConfig{
LogSpans: true,
BufferFlushInterval: 1 * time.Second,
LocalAgentHostPort: o.Endpoint,
},
Headers: &jaeger.HeadersConfig{
TraceContextHeaderName: TraceContextHeaderName,
TraceBaggageHeaderPrefix: TraceBaggageHeaderPrefix,
},
}
t, closer, err := cfg.NewTracer()
if err != nil {
return nil, nil, err
}
return &Tracer{tracer: t}, closer, nil
}
// StartSpanFromContext starts a new tracing span that is either a root one or a
// child of existing one from the provided Context. If logger is provided, a new
// log Entry will be returned with "traceID" log field.
func (t *Tracer) StartSpanFromContext(ctx context.Context, operationName string, l log.Logger, opts ...opentracing.StartSpanOption) (opentracing.Span, log.Logger, context.Context) {
if t == nil {
t = noopTracer
}
var span opentracing.Span
if parentContext := FromContext(ctx); parentContext != nil {
opts = append(opts, opentracing.ChildOf(parentContext))
span = t.tracer.StartSpan(operationName, opts...)
} else {
span = t.tracer.StartSpan(operationName, opts...)
}
sc := span.Context()
return span, loggerWithTraceID(sc, l), WithContext(ctx, sc)
}
// AddContextHeader adds a tracing span context to provided p2p Headers from
// the go context. If the tracing span context is not present in go context,
// ErrContextNotFound is returned.
func (t *Tracer) AddContextHeader(ctx context.Context, headers p2p.Headers) error {
if t == nil {
t = noopTracer
}
c := FromContext(ctx)
if c == nil {
return ErrContextNotFound
}
var b bytes.Buffer
w := bufio.NewWriter(&b)
if err := t.tracer.Inject(c, opentracing.Binary, w); err != nil {
return err
}
if err := w.Flush(); err != nil {
return err
}
headers[p2p.HeaderNameTracingSpanContext] = b.Bytes()
return nil
}
// FromHeaders returns tracing span context from p2p Headers. If the tracing
// span context is not present in go context, ErrContextNotFound is returned.
func (t *Tracer) FromHeaders(headers p2p.Headers) (opentracing.SpanContext, error) {
if t == nil {
t = noopTracer
}
v := headers[p2p.HeaderNameTracingSpanContext]
if v == nil {
return nil, ErrContextNotFound
}
c, err := t.tracer.Extract(opentracing.Binary, bytes.NewReader(v))
if err != nil {
if errors.Is(err, opentracing.ErrSpanContextNotFound) {
return nil, ErrContextNotFound
}
return nil, err
}
return c, nil
}
// WithContextFromHeaders returns a new context with injected tracing span
// context if they are found in p2p Headers. If the tracing span context is not
// present in go context, ErrContextNotFound is returned.
func (t *Tracer) WithContextFromHeaders(ctx context.Context, headers p2p.Headers) (context.Context, error) {
if t == nil {
t = noopTracer
}
c, err := t.FromHeaders(headers)
if err != nil {
return ctx, err
}
return WithContext(ctx, c), nil
}
// AddContextHTTPHeader adds a tracing span context to provided HTTP headers
// from the go context. If the tracing span context is not present in
// go context, ErrContextNotFound is returned.
func (t *Tracer) AddContextHTTPHeader(ctx context.Context, headers http.Header) error {
if t == nil {
t = noopTracer
}
c := FromContext(ctx)
if c == nil {
return ErrContextNotFound
}
carrier := opentracing.HTTPHeadersCarrier(headers)
return t.tracer.Inject(c, opentracing.HTTPHeaders, carrier)
}
// FromHTTPHeaders returns tracing span context from HTTP headers. If the tracing
// span context is not present in go context, ErrContextNotFound is returned.
func (t *Tracer) FromHTTPHeaders(headers http.Header) (opentracing.SpanContext, error) {
if t == nil {
t = noopTracer
}
carrier := opentracing.HTTPHeadersCarrier(headers)
c, err := t.tracer.Extract(opentracing.HTTPHeaders, carrier)
if err != nil {
if errors.Is(err, opentracing.ErrSpanContextNotFound) {
return nil, ErrContextNotFound
}
return nil, err
}
return c, nil
}
// WithContextFromHTTPHeaders returns a new context with injected tracing span
// context if they are found in HTTP headers. If the tracing span context is not
// present in go context, ErrContextNotFound is returned.
func (t *Tracer) WithContextFromHTTPHeaders(ctx context.Context, headers http.Header) (context.Context, error) {
if t == nil {
t = noopTracer
}
c, err := t.FromHTTPHeaders(headers)
if err != nil {
return ctx, err
}
return WithContext(ctx, c), nil
}
// WithContext adds tracing span context to go context.
func WithContext(ctx context.Context, c opentracing.SpanContext) context.Context {
return context.WithValue(ctx, contextKey{}, c)
}
// FromContext return tracing span context from go context. If the tracing span
// context is not present in go context, nil is returned.
func FromContext(ctx context.Context) opentracing.SpanContext {
c, ok := ctx.Value(contextKey{}).(opentracing.SpanContext)
if !ok {
return nil
}
return c
}
// NewLoggerWithTraceID creates a new log Entry with "traceID" field added if it
// exists in tracing span context stored from go context.
func NewLoggerWithTraceID(ctx context.Context, l log.Logger) log.Logger {
return loggerWithTraceID(FromContext(ctx), l)
}
func loggerWithTraceID(sc opentracing.SpanContext, l log.Logger) log.Logger {
if l == nil {
return nil
}
jsc, ok := sc.(jaeger.SpanContext)
if !ok {
return l
}
traceID := jsc.TraceID()
if !traceID.IsValid() {
return l
}
return l.WithValues(LogField, traceID).Build()
}