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 }