Permalink
Fetching contributors…
Cannot retrieve contributors at this time
263 lines (239 sloc) 8.53 KB
package basictracer
import (
"time"
opentracing "github.com/opentracing/opentracing-go"
)
// Tracer extends the opentracing.Tracer interface with methods to
// probe implementation state, for use by basictracer consumers.
type Tracer interface {
opentracing.Tracer
// Options gets the Options used in New() or NewWithOptions().
Options() Options
}
// Options allows creating a customized Tracer via NewWithOptions. The object
// must not be updated when there is an active tracer using it.
type Options struct {
// ShouldSample is a function which is called when creating a new Span and
// determines whether that Span is sampled. The randomized TraceID is supplied
// to allow deterministic sampling decisions to be made across different nodes.
// For example,
//
// func(traceID uint64) { return traceID % 64 == 0 }
//
// samples every 64th trace on average.
ShouldSample func(traceID uint64) bool
// TrimUnsampledSpans turns potentially expensive operations on unsampled
// Spans into no-ops. More precisely, tags and log events are silently
// discarded. If NewSpanEventListener is set, the callbacks will still fire.
TrimUnsampledSpans bool
// Recorder receives Spans which have been finished.
Recorder SpanRecorder
// NewSpanEventListener can be used to enhance the tracer by effectively
// attaching external code to trace events. See NetTraceIntegrator for a
// practical example, and event.go for the list of possible events.
NewSpanEventListener func() func(SpanEvent)
// DropAllLogs turns log events on all Spans into no-ops.
// If NewSpanEventListener is set, the callbacks will still fire.
DropAllLogs bool
// MaxLogsPerSpan limits the number of Logs in a span (if set to a nonzero
// value). If a span has more logs than this value, logs are dropped as
// necessary (and replaced with a log describing how many were dropped).
//
// About half of the MaxLogPerSpan logs kept are the oldest logs, and about
// half are the newest logs.
//
// If NewSpanEventListener is set, the callbacks will still fire for all log
// events. This value is ignored if DropAllLogs is true.
MaxLogsPerSpan int
// DebugAssertSingleGoroutine internally records the ID of the goroutine
// creating each Span and verifies that no operation is carried out on
// it on a different goroutine.
// Provided strictly for development purposes.
// Passing Spans between goroutine without proper synchronization often
// results in use-after-Finish() errors. For a simple example, consider the
// following pseudocode:
//
// func (s *Server) Handle(req http.Request) error {
// sp := s.StartSpan("server")
// defer sp.Finish()
// wait := s.queueProcessing(opentracing.ContextWithSpan(context.Background(), sp), req)
// select {
// case resp := <-wait:
// return resp.Error
// case <-time.After(10*time.Second):
// sp.LogEvent("timed out waiting for processing")
// return ErrTimedOut
// }
// }
//
// This looks reasonable at first, but a request which spends more than ten
// seconds in the queue is abandoned by the main goroutine and its trace
// finished, leading to use-after-finish when the request is finally
// processed. Note also that even joining on to a finished Span via
// StartSpanWithOptions constitutes an illegal operation.
//
// Code bases which do not require (or decide they do not want) Spans to
// be passed across goroutine boundaries can run with this flag enabled in
// tests to increase their chances of spotting wrong-doers.
DebugAssertSingleGoroutine bool
// DebugAssertUseAfterFinish is provided strictly for development purposes.
// When set, it attempts to exacerbate issues emanating from use of Spans
// after calling Finish by running additional assertions.
DebugAssertUseAfterFinish bool
// EnableSpanPool enables the use of a pool, so that the tracer reuses spans
// after Finish has been called on it. Adds a slight performance gain as it
// reduces allocations. However, if you have any use-after-finish race
// conditions the code may panic.
EnableSpanPool bool
}
// DefaultOptions returns an Options object with a 1 in 64 sampling rate and
// all options disabled. A Recorder needs to be set manually before using the
// returned object with a Tracer.
func DefaultOptions() Options {
return Options{
ShouldSample: func(traceID uint64) bool { return traceID%64 == 0 },
MaxLogsPerSpan: 100,
}
}
// NewWithOptions creates a customized Tracer.
func NewWithOptions(opts Options) opentracing.Tracer {
rval := &tracerImpl{options: opts}
rval.textPropagator = &textMapPropagator{rval}
rval.binaryPropagator = &binaryPropagator{rval}
rval.accessorPropagator = &accessorPropagator{rval}
return rval
}
// New creates and returns a standard Tracer which defers completed Spans to
// `recorder`.
// Spans created by this Tracer support the ext.SamplingPriority tag: Setting
// ext.SamplingPriority causes the Span to be Sampled from that point on.
func New(recorder SpanRecorder) opentracing.Tracer {
opts := DefaultOptions()
opts.Recorder = recorder
return NewWithOptions(opts)
}
// Implements the `Tracer` interface.
type tracerImpl struct {
options Options
textPropagator *textMapPropagator
binaryPropagator *binaryPropagator
accessorPropagator *accessorPropagator
}
func (t *tracerImpl) StartSpan(
operationName string,
opts ...opentracing.StartSpanOption,
) opentracing.Span {
sso := opentracing.StartSpanOptions{}
for _, o := range opts {
o.Apply(&sso)
}
return t.StartSpanWithOptions(operationName, sso)
}
func (t *tracerImpl) getSpan() *spanImpl {
if t.options.EnableSpanPool {
sp := spanPool.Get().(*spanImpl)
sp.reset()
return sp
}
return &spanImpl{}
}
func (t *tracerImpl) StartSpanWithOptions(
operationName string,
opts opentracing.StartSpanOptions,
) opentracing.Span {
// Start time.
startTime := opts.StartTime
if startTime.IsZero() {
startTime = time.Now()
}
// Tags.
tags := opts.Tags
// Build the new span. This is the only allocation: We'll return this as
// an opentracing.Span.
sp := t.getSpan()
// Look for a parent in the list of References.
//
// TODO: would be nice if basictracer did something with all
// References, not just the first one.
ReferencesLoop:
for _, ref := range opts.References {
switch ref.Type {
case opentracing.ChildOfRef,
opentracing.FollowsFromRef:
refCtx := ref.ReferencedContext.(SpanContext)
sp.raw.Context.TraceID = refCtx.TraceID
sp.raw.Context.SpanID = randomID()
sp.raw.Context.Sampled = refCtx.Sampled
sp.raw.ParentSpanID = refCtx.SpanID
if l := len(refCtx.Baggage); l > 0 {
sp.raw.Context.Baggage = make(map[string]string, l)
for k, v := range refCtx.Baggage {
sp.raw.Context.Baggage[k] = v
}
}
break ReferencesLoop
}
}
if sp.raw.Context.TraceID == 0 {
// No parent Span found; allocate new trace and span ids and determine
// the Sampled status.
sp.raw.Context.TraceID, sp.raw.Context.SpanID = randomID2()
sp.raw.Context.Sampled = t.options.ShouldSample(sp.raw.Context.TraceID)
}
return t.startSpanInternal(
sp,
operationName,
startTime,
tags,
)
}
func (t *tracerImpl) startSpanInternal(
sp *spanImpl,
operationName string,
startTime time.Time,
tags opentracing.Tags,
) opentracing.Span {
sp.tracer = t
if t.options.NewSpanEventListener != nil {
sp.event = t.options.NewSpanEventListener()
}
sp.raw.Operation = operationName
sp.raw.Start = startTime
sp.raw.Duration = -1
sp.raw.Tags = tags
if t.options.DebugAssertSingleGoroutine {
sp.SetTag(debugGoroutineIDTag, curGoroutineID())
}
defer sp.onCreate(operationName)
return sp
}
type delegatorType struct{}
// Delegator is the format to use for DelegatingCarrier.
var Delegator delegatorType
func (t *tracerImpl) Inject(sc opentracing.SpanContext, format interface{}, carrier interface{}) error {
switch format {
case opentracing.TextMap, opentracing.HTTPHeaders:
return t.textPropagator.Inject(sc, carrier)
case opentracing.Binary:
return t.binaryPropagator.Inject(sc, carrier)
}
if _, ok := format.(delegatorType); ok {
return t.accessorPropagator.Inject(sc, carrier)
}
return opentracing.ErrUnsupportedFormat
}
func (t *tracerImpl) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) {
switch format {
case opentracing.TextMap, opentracing.HTTPHeaders:
return t.textPropagator.Extract(carrier)
case opentracing.Binary:
return t.binaryPropagator.Extract(carrier)
}
if _, ok := format.(delegatorType); ok {
return t.accessorPropagator.Extract(carrier)
}
return nil, opentracing.ErrUnsupportedFormat
}
func (t *tracerImpl) Options() Options {
return t.options
}