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  }