go.undefinedlabs.com/scopeagent@v0.4.2/tracer/tracer.go (about)

     1  package tracer
     2  
     3  import (
     4  	"time"
     5  
     6  	"github.com/go-errors/errors"
     7  	"github.com/google/uuid"
     8  	"github.com/opentracing/opentracing-go"
     9  )
    10  
    11  const emptyUUID = "00000000-0000-0000-0000-000000000000"
    12  
    13  // Tracer extends the opentracing.Tracer interface with methods to
    14  // probe implementation state, for use by basictracer consumers.
    15  type Tracer interface {
    16  	opentracing.Tracer
    17  
    18  	// Options gets the Options used in New() or NewWithOptions().
    19  	Options() Options
    20  }
    21  
    22  // Options allows creating a customized Tracer via NewWithOptions. The object
    23  // must not be updated when there is an active tracer using it.
    24  type Options struct {
    25  	// ShouldSample is a function which is called when creating a new Span and
    26  	// determines whether that Span is sampled. The randomized TraceID is supplied
    27  	// to allow deterministic sampling decisions to be made across different nodes.
    28  	// For example,
    29  	//
    30  	//   func(traceID uuid.UUID) { return true }
    31  	//
    32  	ShouldSample func(traceID uuid.UUID) bool
    33  	// TrimUnsampledSpans turns potentially expensive operations on unsampled
    34  	// Spans into no-ops. More precisely, tags and log events are silently
    35  	// discarded. If NewSpanEventListener is set, the callbacks will still fire.
    36  	TrimUnsampledSpans bool
    37  	// Recorder receives Spans which have been finished.
    38  	Recorder SpanRecorder
    39  	// NewSpanEventListener can be used to enhance the tracer by effectively
    40  	// attaching external code to trace events. See NetTraceIntegrator for a
    41  	// practical example, and event.go for the list of possible events.
    42  	NewSpanEventListener func() func(SpanEvent)
    43  	// DropAllLogs turns log events on all Spans into no-ops.
    44  	// If NewSpanEventListener is set, the callbacks will still fire.
    45  	DropAllLogs bool
    46  	// MaxLogsPerSpan limits the number of Logs in a span (if set to a nonzero
    47  	// value). If a span has more logs than this value, logs are dropped as
    48  	// necessary (and replaced with a log describing how many were dropped).
    49  	//
    50  	// About half of the MaxLogPerSpan logs kept are the oldest logs, and about
    51  	// half are the newest logs.
    52  	//
    53  	// If NewSpanEventListener is set, the callbacks will still fire for all log
    54  	// events. This value is ignored if DropAllLogs is true.
    55  	MaxLogsPerSpan int
    56  	// DebugAssertSingleGoroutine internally records the ID of the goroutine
    57  	// creating each Span and verifies that no operation is carried out on
    58  	// it on a different goroutine.
    59  	// Provided strictly for development purposes.
    60  	// Passing Spans between goroutine without proper synchronization often
    61  	// results in use-after-Finish() errors. For a simple example, consider the
    62  	// following pseudocode:
    63  	//
    64  	//  func (s *Server) Handle(req nethttp.Request) error {
    65  	//    sp := s.StartSpan("server")
    66  	//    defer sp.Finish()
    67  	//    wait := s.queueProcessing(opentracing.ContextWithSpan(context.Background(), sp), req)
    68  	//    select {
    69  	//    case resp := <-wait:
    70  	//      return resp.Error
    71  	//    case <-time.After(10*time.Second):
    72  	//      sp.LogEvent("timed out waiting for processing")
    73  	//      return ErrTimedOut
    74  	//    }
    75  	//  }
    76  	//
    77  	// This looks reasonable at first, but a request which spends more than ten
    78  	// seconds in the queue is abandoned by the main goroutine and its trace
    79  	// finished, leading to use-after-finish when the request is finally
    80  	// processed. Note also that even joining on to a finished Span via
    81  	// StartSpanWithOptions constitutes an illegal operation.
    82  	//
    83  	// Code bases which do not require (or decide they do not want) Spans to
    84  	// be passed across goroutine boundaries can run with this flag enabled in
    85  	// tests to increase their chances of spotting wrong-doers.
    86  	DebugAssertSingleGoroutine bool
    87  	// DebugAssertUseAfterFinish is provided strictly for development purposes.
    88  	// When set, it attempts to exacerbate issues emanating from use of Spans
    89  	// after calling Finish by running additional assertions.
    90  	DebugAssertUseAfterFinish bool
    91  	// EnableSpanPool enables the use of a pool, so that the tracer reuses spans
    92  	// after Finish has been called on it. Adds a slight performance gain as it
    93  	// reduces allocations. However, if you have any use-after-finish race
    94  	// conditions the code may panic.
    95  	EnableSpanPool bool
    96  	// Func to call when a panic has been detected when a span is finalizing
    97  	OnSpanFinishPanic func(rSpan *RawSpan, err **errors.Error)
    98  }
    99  
   100  // DefaultOptions returns an Options object with a 1 in 64 sampling rate and
   101  // all options disabled. A Recorder needs to be set manually before using the
   102  // returned object with a Tracer.
   103  func DefaultOptions() Options {
   104  	return Options{
   105  		ShouldSample:   func(traceID uuid.UUID) bool { return true },
   106  		MaxLogsPerSpan: 100,
   107  	}
   108  }
   109  
   110  // NewWithOptions creates a customized Tracer.
   111  func NewWithOptions(opts Options) opentracing.Tracer {
   112  	rval := &tracerImpl{options: opts}
   113  	rval.textPropagator = &textMapPropagator{rval}
   114  	rval.binaryPropagator = &binaryPropagator{rval}
   115  	rval.accessorPropagator = &accessorPropagator{rval}
   116  	rval.envVarPropagator = &envVarPropagator{rval}
   117  	return rval
   118  }
   119  
   120  // New creates and returns a standard Tracer which defers completed Spans to
   121  // `recorder`.
   122  // Spans created by this Tracer support the ext.SamplingPriority tag: Setting
   123  // ext.SamplingPriority causes the Span to be Sampled from that point on.
   124  func New(recorder SpanRecorder) opentracing.Tracer {
   125  	opts := DefaultOptions()
   126  	opts.Recorder = recorder
   127  	return NewWithOptions(opts)
   128  }
   129  
   130  // Implements the `Tracer` interface.
   131  type tracerImpl struct {
   132  	options            Options
   133  	textPropagator     *textMapPropagator
   134  	binaryPropagator   *binaryPropagator
   135  	accessorPropagator *accessorPropagator
   136  	envVarPropagator   *envVarPropagator
   137  }
   138  
   139  func (t *tracerImpl) StartSpan(
   140  	operationName string,
   141  	opts ...opentracing.StartSpanOption,
   142  ) opentracing.Span {
   143  	sso := opentracing.StartSpanOptions{}
   144  	for _, o := range opts {
   145  		o.Apply(&sso)
   146  	}
   147  	return t.StartSpanWithOptions(operationName, sso)
   148  }
   149  
   150  func (t *tracerImpl) getSpan() *spanImpl {
   151  	if t.options.EnableSpanPool {
   152  		sp := spanPool.Get().(*spanImpl)
   153  		sp.reset()
   154  		return sp
   155  	}
   156  	return &spanImpl{}
   157  }
   158  
   159  func (t *tracerImpl) StartSpanWithOptions(
   160  	operationName string,
   161  	opts opentracing.StartSpanOptions,
   162  ) opentracing.Span {
   163  	// Start time.
   164  	startTime := opts.StartTime
   165  	if startTime.IsZero() {
   166  		startTime = time.Now()
   167  	}
   168  
   169  	// Tags.
   170  	tags := opts.Tags
   171  
   172  	// Build the new span. This is the only allocation: We'll return this as
   173  	// an opentracing.Span.
   174  	sp := t.getSpan()
   175  	// Look for a parent in the list of References.
   176  	//
   177  	// TODO: would be nice if basictracer did something with all
   178  	// References, not just the first one.
   179  ReferencesLoop:
   180  	for _, ref := range opts.References {
   181  		switch ref.Type {
   182  		case opentracing.ChildOfRef,
   183  			opentracing.FollowsFromRef:
   184  
   185  			refCtx := ref.ReferencedContext.(SpanContext)
   186  			sp.raw.Context.TraceID = refCtx.TraceID
   187  			sp.raw.Context.SpanID = getRandomId()
   188  			sp.raw.Context.Sampled = refCtx.Sampled
   189  			sp.raw.ParentSpanID = refCtx.SpanID
   190  
   191  			if l := len(refCtx.Baggage); l > 0 {
   192  				sp.raw.Context.Baggage = make(map[string]string, l)
   193  				for k, v := range refCtx.Baggage {
   194  					sp.raw.Context.Baggage[k] = v
   195  				}
   196  			}
   197  			break ReferencesLoop
   198  		}
   199  	}
   200  	if sp.raw.Context.TraceID.String() == emptyUUID {
   201  		// No parent Span found; allocate new trace and span ids and determine
   202  		// the Sampled status.
   203  		sp.raw.Context.TraceID = uuid.New()
   204  		sp.raw.Context.SpanID = getRandomId()
   205  		sp.raw.Context.Sampled = t.options.ShouldSample(sp.raw.Context.TraceID)
   206  	}
   207  
   208  	return t.startSpanInternal(
   209  		sp,
   210  		operationName,
   211  		startTime,
   212  		tags,
   213  	)
   214  }
   215  
   216  func (t *tracerImpl) startSpanInternal(
   217  	sp *spanImpl,
   218  	operationName string,
   219  	startTime time.Time,
   220  	tags opentracing.Tags,
   221  ) opentracing.Span {
   222  	sp.tracer = t
   223  	if t.options.NewSpanEventListener != nil {
   224  		sp.event = t.options.NewSpanEventListener()
   225  	}
   226  	sp.raw.Operation = operationName
   227  	sp.raw.Start = startTime
   228  	sp.raw.Duration = -1
   229  	sp.raw.Tags = tags
   230  	if t.options.DebugAssertSingleGoroutine {
   231  		sp.SetTag(debugGoroutineIDTag, curGoroutineID())
   232  	}
   233  	defer sp.onCreate(operationName)
   234  	return sp
   235  }
   236  
   237  type delegatorType struct{}
   238  
   239  // Delegator is the format to use for DelegatingCarrier.
   240  var Delegator delegatorType
   241  
   242  func (t *tracerImpl) Inject(sc opentracing.SpanContext, format interface{}, carrier interface{}) error {
   243  	switch format {
   244  	case opentracing.TextMap, opentracing.HTTPHeaders:
   245  		return t.textPropagator.Inject(sc, carrier)
   246  	case opentracing.Binary:
   247  		return t.binaryPropagator.Inject(sc, carrier)
   248  	case EnvironmentVariableFormat:
   249  		return t.envVarPropagator.Inject(sc, carrier)
   250  	}
   251  	if _, ok := format.(delegatorType); ok {
   252  		return t.accessorPropagator.Inject(sc, carrier)
   253  	}
   254  	return opentracing.ErrUnsupportedFormat
   255  }
   256  
   257  func (t *tracerImpl) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) {
   258  	switch format {
   259  	case opentracing.TextMap, opentracing.HTTPHeaders:
   260  		return t.textPropagator.Extract(carrier)
   261  	case opentracing.Binary:
   262  		return t.binaryPropagator.Extract(carrier)
   263  	case EnvironmentVariableFormat:
   264  		return t.envVarPropagator.Extract(carrier)
   265  	}
   266  	if _, ok := format.(delegatorType); ok {
   267  		return t.accessorPropagator.Extract(carrier)
   268  	}
   269  	return nil, opentracing.ErrUnsupportedFormat
   270  }
   271  
   272  func (t *tracerImpl) Options() Options {
   273  	return t.options
   274  }