github.com/ethersphere/bee/v2@v2.2.0/pkg/tracing/tracing.go (about) 1 // Copyright 2020 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package tracing 6 7 import ( 8 "bufio" 9 "bytes" 10 "context" 11 "errors" 12 "io" 13 "net/http" 14 "time" 15 16 "github.com/ethersphere/bee/v2/pkg/log" 17 "github.com/ethersphere/bee/v2/pkg/p2p" 18 "github.com/opentracing/opentracing-go" 19 "github.com/uber/jaeger-client-go" 20 "github.com/uber/jaeger-client-go/config" 21 ) 22 23 var ( 24 // ErrContextNotFound is returned when tracing context is not present 25 // in p2p Headers or context. 26 ErrContextNotFound = errors.New("tracing context not found") 27 28 // noopTracer is the tracer that does nothing to handle a nil Tracer usage. 29 noopTracer = &Tracer{tracer: new(opentracing.NoopTracer)} 30 ) 31 32 // contextKey is used to reference a tracing context span as context value. 33 type contextKey struct{} 34 35 // LogField is the key in log message field that holds tracing id value. 36 const LogField = "traceID" 37 38 const ( 39 // TraceContextHeaderName is the http header name used to propagate tracing context. 40 TraceContextHeaderName = "swarm-trace-id" 41 42 // TraceBaggageHeaderPrefix is the prefix for http headers used to propagate baggage. 43 TraceBaggageHeaderPrefix = "swarmctx-" 44 ) 45 46 // Tracer connect to a tracing server and handles tracing spans and contexts 47 // by using opentracing Tracer. 48 type Tracer struct { 49 tracer opentracing.Tracer 50 } 51 52 // Options are optional parameters for Tracer constructor. 53 type Options struct { 54 Enabled bool 55 Endpoint string 56 ServiceName string 57 } 58 59 // NewTracer creates a new Tracer and returns a closer which needs to be closed 60 // when the Tracer is no longer used to flush remaining traces. 61 func NewTracer(o *Options) (*Tracer, io.Closer, error) { 62 if o == nil { 63 o = new(Options) 64 } 65 66 cfg := config.Configuration{ 67 Disabled: !o.Enabled, 68 ServiceName: o.ServiceName, 69 Sampler: &config.SamplerConfig{ 70 Type: jaeger.SamplerTypeConst, 71 Param: 1, 72 }, 73 Reporter: &config.ReporterConfig{ 74 LogSpans: true, 75 BufferFlushInterval: 1 * time.Second, 76 LocalAgentHostPort: o.Endpoint, 77 }, 78 Headers: &jaeger.HeadersConfig{ 79 TraceContextHeaderName: TraceContextHeaderName, 80 TraceBaggageHeaderPrefix: TraceBaggageHeaderPrefix, 81 }, 82 } 83 84 t, closer, err := cfg.NewTracer() 85 if err != nil { 86 return nil, nil, err 87 } 88 return &Tracer{tracer: t}, closer, nil 89 } 90 91 // StartSpanFromContext starts a new tracing span that is either a root one or a 92 // child of existing one from the provided Context. If logger is provided, a new 93 // log Entry will be returned with "traceID" log field. 94 func (t *Tracer) StartSpanFromContext(ctx context.Context, operationName string, l log.Logger, opts ...opentracing.StartSpanOption) (opentracing.Span, log.Logger, context.Context) { 95 if t == nil { 96 t = noopTracer 97 } 98 99 var span opentracing.Span 100 if parentContext := FromContext(ctx); parentContext != nil { 101 opts = append(opts, opentracing.ChildOf(parentContext)) 102 span = t.tracer.StartSpan(operationName, opts...) 103 } else { 104 span = t.tracer.StartSpan(operationName, opts...) 105 } 106 sc := span.Context() 107 return span, loggerWithTraceID(sc, l), WithContext(ctx, sc) 108 } 109 110 // FollowSpanFromContext starts a new tracing span that is either a root one or 111 // follows an existing one from the provided Context. If logger is provided, a new 112 // log Entry will be returned with "traceID" log field. 113 func (t *Tracer) FollowSpanFromContext(ctx context.Context, operationName string, l log.Logger, opts ...opentracing.StartSpanOption) (opentracing.Span, log.Logger, context.Context) { 114 if t == nil { 115 t = noopTracer 116 } 117 118 var span opentracing.Span 119 if parentContext := FromContext(ctx); parentContext != nil { 120 opts = append(opts, opentracing.FollowsFrom(parentContext)) 121 span = t.tracer.StartSpan(operationName, opts...) 122 } else { 123 span = t.tracer.StartSpan(operationName, opts...) 124 } 125 sc := span.Context() 126 return span, loggerWithTraceID(sc, l), WithContext(ctx, sc) 127 } 128 129 // AddContextHeader adds a tracing span context to provided p2p Headers from 130 // the go context. If the tracing span context is not present in go context, 131 // ErrContextNotFound is returned. 132 func (t *Tracer) AddContextHeader(ctx context.Context, headers p2p.Headers) error { 133 if t == nil { 134 t = noopTracer 135 } 136 137 c := FromContext(ctx) 138 if c == nil { 139 return ErrContextNotFound 140 } 141 142 var b bytes.Buffer 143 w := bufio.NewWriter(&b) 144 if err := t.tracer.Inject(c, opentracing.Binary, w); err != nil { 145 return err 146 } 147 if err := w.Flush(); err != nil { 148 return err 149 } 150 151 headers[p2p.HeaderNameTracingSpanContext] = b.Bytes() 152 153 return nil 154 } 155 156 // FromHeaders returns tracing span context from p2p Headers. If the tracing 157 // span context is not present in go context, ErrContextNotFound is returned. 158 func (t *Tracer) FromHeaders(headers p2p.Headers) (opentracing.SpanContext, error) { 159 if t == nil { 160 t = noopTracer 161 } 162 163 v := headers[p2p.HeaderNameTracingSpanContext] 164 if v == nil { 165 return nil, ErrContextNotFound 166 } 167 c, err := t.tracer.Extract(opentracing.Binary, bytes.NewReader(v)) 168 if err != nil { 169 if errors.Is(err, opentracing.ErrSpanContextNotFound) { 170 return nil, ErrContextNotFound 171 } 172 return nil, err 173 } 174 175 return c, nil 176 } 177 178 // WithContextFromHeaders returns a new context with injected tracing span 179 // context if they are found in p2p Headers. If the tracing span context is not 180 // present in go context, ErrContextNotFound is returned. 181 func (t *Tracer) WithContextFromHeaders(ctx context.Context, headers p2p.Headers) (context.Context, error) { 182 if t == nil { 183 t = noopTracer 184 } 185 186 c, err := t.FromHeaders(headers) 187 if err != nil { 188 return ctx, err 189 } 190 return WithContext(ctx, c), nil 191 } 192 193 // AddContextHTTPHeader adds a tracing span context to provided HTTP headers 194 // from the go context. If the tracing span context is not present in 195 // go context, ErrContextNotFound is returned. 196 func (t *Tracer) AddContextHTTPHeader(ctx context.Context, headers http.Header) error { 197 if t == nil { 198 t = noopTracer 199 } 200 201 c := FromContext(ctx) 202 if c == nil { 203 return ErrContextNotFound 204 } 205 206 carrier := opentracing.HTTPHeadersCarrier(headers) 207 return t.tracer.Inject(c, opentracing.HTTPHeaders, carrier) 208 } 209 210 // FromHTTPHeaders returns tracing span context from HTTP headers. If the tracing 211 // span context is not present in go context, ErrContextNotFound is returned. 212 func (t *Tracer) FromHTTPHeaders(headers http.Header) (opentracing.SpanContext, error) { 213 if t == nil { 214 t = noopTracer 215 } 216 217 carrier := opentracing.HTTPHeadersCarrier(headers) 218 c, err := t.tracer.Extract(opentracing.HTTPHeaders, carrier) 219 if err != nil { 220 if errors.Is(err, opentracing.ErrSpanContextNotFound) { 221 return nil, ErrContextNotFound 222 } 223 return nil, err 224 } 225 226 return c, nil 227 } 228 229 // WithContextFromHTTPHeaders returns a new context with injected tracing span 230 // context if they are found in HTTP headers. If the tracing span context is not 231 // present in go context, ErrContextNotFound is returned. 232 func (t *Tracer) WithContextFromHTTPHeaders(ctx context.Context, headers http.Header) (context.Context, error) { 233 if t == nil { 234 t = noopTracer 235 } 236 237 c, err := t.FromHTTPHeaders(headers) 238 if err != nil { 239 return ctx, err 240 } 241 242 return WithContext(ctx, c), nil 243 } 244 245 // WithContext adds tracing span context to go context. 246 func WithContext(ctx context.Context, c opentracing.SpanContext) context.Context { 247 return context.WithValue(ctx, contextKey{}, c) 248 } 249 250 // FromContext return tracing span context from go context. If the tracing span 251 // context is not present in go context, nil is returned. 252 func FromContext(ctx context.Context) opentracing.SpanContext { 253 c, ok := ctx.Value(contextKey{}).(opentracing.SpanContext) 254 if !ok { 255 return nil 256 } 257 return c 258 } 259 260 // NewLoggerWithTraceID creates a new log Entry with "traceID" field added if it 261 // exists in tracing span context stored from go context. 262 func NewLoggerWithTraceID(ctx context.Context, l log.Logger) log.Logger { 263 return loggerWithTraceID(FromContext(ctx), l) 264 } 265 266 func loggerWithTraceID(sc opentracing.SpanContext, l log.Logger) log.Logger { 267 if l == nil { 268 return nil 269 } 270 jsc, ok := sc.(jaeger.SpanContext) 271 if !ok { 272 return l 273 } 274 traceID := jsc.TraceID() 275 if !traceID.IsValid() { 276 return l 277 } 278 return l.WithValues(LogField, traceID).Build() 279 }