go.undefinedlabs.com/scopeagent@v0.4.2/tracer/span.go (about) 1 package tracer 2 3 import ( 4 "sync" 5 "time" 6 7 "github.com/go-errors/errors" 8 9 "github.com/opentracing/opentracing-go" 10 "github.com/opentracing/opentracing-go/ext" 11 "github.com/opentracing/opentracing-go/log" 12 13 "go.undefinedlabs.com/scopeagent/instrumentation" 14 scopetags "go.undefinedlabs.com/scopeagent/tags" 15 ) 16 17 // Span provides access to the essential details of the span, for use 18 // by basictracer consumers. These methods may only be called prior 19 // to (*opentracing.Span).Finish(). 20 type Span interface { 21 opentracing.Span 22 23 // Operation names the work done by this span instance 24 Operation() string 25 26 // Start indicates when the span began 27 Start() time.Time 28 29 // Sets the start time 30 SetStart(start time.Time) opentracing.Span 31 32 // Log fields with timestamp 33 LogFieldsWithTimestamp(t time.Time, fields ...log.Field) 34 35 // Set tag without validation 36 UnsafeSetTag(key string, value interface{}) opentracing.Span 37 } 38 39 // Implements the `Span` interface. Created via tracerImpl (see 40 // `basictracer.New()`). 41 type spanImpl struct { 42 tracer *tracerImpl 43 event func(SpanEvent) 44 sync.Mutex // protects the fields below 45 raw RawSpan 46 // The number of logs dropped because of MaxLogsPerSpan. 47 numDroppedLogs int 48 } 49 50 var spanPool = &sync.Pool{New: func() interface{} { 51 return &spanImpl{} 52 }} 53 54 func (s *spanImpl) reset() { 55 s.tracer, s.event = nil, nil 56 // Note: Would like to do the following, but then the consumer of RawSpan 57 // (the recorder) needs to make sure that they're not holding on to the 58 // baggage or logs when they return (i.e. they need to copy if they care): 59 // 60 // logs, baggage := s.raw.Logs[:0], s.raw.Baggage 61 // for k := range baggage { 62 // delete(baggage, k) 63 // } 64 // s.raw.Logs, s.raw.Baggage = logs, baggage 65 // 66 // That's likely too much to ask for. But there is some magic we should 67 // be able to do with `runtime.SetFinalizer` to reclaim that memory into 68 // a buffer pool when GC considers them unreachable, which should ease 69 // some of the load. Hard to say how quickly that would be in practice 70 // though. 71 s.raw = RawSpan{ 72 Context: SpanContext{}, 73 } 74 } 75 76 func (s *spanImpl) SetOperationName(operationName string) opentracing.Span { 77 s.Lock() 78 defer s.Unlock() 79 s.raw.Operation = operationName 80 return s 81 } 82 83 func (s *spanImpl) trim() bool { 84 return !s.raw.Context.Sampled && s.tracer.options.TrimUnsampledSpans 85 } 86 87 func (s *spanImpl) SetStart(start time.Time) opentracing.Span { 88 s.Lock() 89 defer s.Unlock() 90 s.raw.Start = start 91 return s 92 } 93 94 func (s *spanImpl) UnsafeSetTag(key string, value interface{}) opentracing.Span { 95 defer s.onTag(key, value) 96 s.Lock() 97 defer s.Unlock() 98 if key == string(ext.SamplingPriority) { 99 if v, ok := value.(uint16); ok { 100 s.raw.Context.Sampled = v != 0 101 return s 102 } 103 } 104 if s.trim() { 105 return s 106 } 107 108 if s.raw.Tags == nil { 109 s.raw.Tags = opentracing.Tags{} 110 } 111 s.raw.Tags[key] = value 112 return s 113 } 114 115 func (s *spanImpl) SetTag(key string, value interface{}) opentracing.Span { 116 cValue, c := scopetags.GetValidValue(value) 117 if c { 118 instrumentation.Logger().Printf("SetTag-ConvertedValue: %v", cValue) 119 } 120 return s.UnsafeSetTag(key, cValue) 121 } 122 123 func (s *spanImpl) LogKV(keyValues ...interface{}) { 124 fields, err := log.InterleavedKVToFields(keyValues...) 125 if err != nil { 126 s.LogFields(log.Error(err), log.String("function", "LogKV")) 127 return 128 } 129 s.LogFields(fields...) 130 } 131 132 func (s *spanImpl) appendLog(lr opentracing.LogRecord) { 133 maxLogs := s.tracer.options.MaxLogsPerSpan 134 if maxLogs == 0 || len(s.raw.Logs) < maxLogs { 135 s.raw.Logs = append(s.raw.Logs, lr) 136 return 137 } 138 139 // We have too many logs. We don't touch the first numOld logs; we treat the 140 // rest as a circular buffer and overwrite the oldest log among those. 141 numOld := (maxLogs - 1) / 2 142 numNew := maxLogs - numOld 143 s.raw.Logs[numOld+s.numDroppedLogs%numNew] = lr 144 s.numDroppedLogs++ 145 } 146 147 func (s *spanImpl) LogFields(fields ...log.Field) { 148 lr := opentracing.LogRecord{ 149 Fields: fields, 150 } 151 defer s.onLogFields(lr) 152 s.Lock() 153 defer s.Unlock() 154 if s.trim() || s.tracer.options.DropAllLogs { 155 return 156 } 157 if lr.Timestamp.IsZero() { 158 lr.Timestamp = time.Now() 159 } 160 s.appendLog(lr) 161 } 162 163 func (s *spanImpl) LogFieldsWithTimestamp(t time.Time, fields ...log.Field) { 164 lr := opentracing.LogRecord{ 165 Timestamp: t, 166 Fields: fields, 167 } 168 defer s.onLogFields(lr) 169 s.Lock() 170 defer s.Unlock() 171 if s.trim() || s.tracer.options.DropAllLogs { 172 return 173 } 174 if lr.Timestamp.IsZero() { 175 lr.Timestamp = time.Now() 176 } 177 s.appendLog(lr) 178 } 179 180 func (s *spanImpl) LogEvent(event string) { 181 s.Log(opentracing.LogData{ 182 Event: event, 183 }) 184 } 185 186 func (s *spanImpl) LogEventWithPayload(event string, payload interface{}) { 187 s.Log(opentracing.LogData{ 188 Event: event, 189 Payload: payload, 190 }) 191 } 192 193 func (s *spanImpl) Log(ld opentracing.LogData) { 194 defer s.onLog(ld) 195 s.Lock() 196 defer s.Unlock() 197 if s.trim() || s.tracer.options.DropAllLogs { 198 return 199 } 200 201 if ld.Timestamp.IsZero() { 202 ld.Timestamp = time.Now() 203 } 204 205 s.appendLog(ld.ToLogRecord()) 206 } 207 208 func (s *spanImpl) Finish() { 209 var currentError *errors.Error 210 if s.tracer != nil && s.tracer.options.OnSpanFinishPanic != nil && s.raw.ParentSpanID != 0 { 211 if r := recover(); r != nil { 212 currentError = errors.Wrap(r, 1) 213 s.callFinishPanic(¤tError) 214 } 215 } 216 217 s.FinishWithOptions(opentracing.FinishOptions{}) 218 219 if currentError != nil { 220 panic(currentError) 221 } 222 } 223 224 // rotateLogBuffer rotates the records in the buffer: records 0 to pos-1 move at 225 // the end (i.e. pos circular left shifts). 226 func rotateLogBuffer(buf []opentracing.LogRecord, pos int) { 227 // This algorithm is described in: 228 // http://www.cplusplus.com/reference/algorithm/rotate 229 for first, middle, next := 0, pos, pos; first != middle; { 230 buf[first], buf[next] = buf[next], buf[first] 231 first++ 232 next++ 233 if next == len(buf) { 234 next = middle 235 } else if first == middle { 236 middle = next 237 } 238 } 239 } 240 241 func (s *spanImpl) callFinishPanic(err **errors.Error) { 242 s.Lock() 243 defer s.Unlock() 244 s.tracer.options.OnSpanFinishPanic(&s.raw, err) 245 } 246 247 func (s *spanImpl) FinishWithOptions(opts opentracing.FinishOptions) { 248 var currentError *errors.Error 249 if s.tracer != nil && s.tracer.options.OnSpanFinishPanic != nil && s.raw.ParentSpanID != 0 { 250 if r := recover(); r != nil { 251 currentError = errors.Wrap(r, 1) 252 s.callFinishPanic(¤tError) 253 } 254 } 255 256 finishTime := opts.FinishTime 257 if finishTime.IsZero() { 258 finishTime = time.Now() 259 } 260 duration := finishTime.Sub(s.raw.Start) 261 262 s.Lock() 263 defer s.Unlock() 264 265 for _, lr := range opts.LogRecords { 266 s.appendLog(lr) 267 } 268 for _, ld := range opts.BulkLogData { 269 s.appendLog(ld.ToLogRecord()) 270 } 271 272 if s.numDroppedLogs > 0 { 273 // We dropped some log events, which means that we used part of Logs as a 274 // circular buffer (see appendLog). De-circularize it. 275 numOld := (len(s.raw.Logs) - 1) / 2 276 numNew := len(s.raw.Logs) - numOld 277 rotateLogBuffer(s.raw.Logs[numOld:], s.numDroppedLogs%numNew) 278 279 // Replace the log in the middle (the oldest "new" log) with information 280 // about the dropped logs. This means that we are effectively dropping one 281 // more "new" log. 282 numDropped := s.numDroppedLogs + 1 283 s.raw.Logs[numOld] = opentracing.LogRecord{ 284 // Keep the timestamp of the last dropped event. 285 Timestamp: s.raw.Logs[numOld].Timestamp, 286 Fields: []log.Field{ 287 log.String("event", "dropped Span logs"), 288 log.Int("dropped_log_count", numDropped), 289 log.String("component", "basictracer"), 290 }, 291 } 292 } 293 294 s.raw.Duration = duration 295 296 s.onFinish(s.raw) 297 s.tracer.options.Recorder.RecordSpan(s.raw) 298 299 // Last chance to get options before the span is possibly reset. 300 poolEnabled := s.tracer.options.EnableSpanPool 301 if s.tracer.options.DebugAssertUseAfterFinish { 302 // This makes it much more likely to catch a panic on any subsequent 303 // operation since s.tracer is accessed on every call to `Lock`. 304 // We don't call `reset()` here to preserve the logs in the Span 305 // which are printed when the assertion triggers. 306 s.tracer = nil 307 } 308 309 if poolEnabled { 310 spanPool.Put(s) 311 } 312 313 if currentError != nil { 314 panic(currentError) 315 } 316 } 317 318 func (s *spanImpl) Tracer() opentracing.Tracer { 319 return s.tracer 320 } 321 322 func (s *spanImpl) Context() opentracing.SpanContext { 323 return s.raw.Context 324 } 325 326 func (s *spanImpl) SetBaggageItem(key, val string) opentracing.Span { 327 s.onBaggage(key, val) 328 if s.trim() { 329 return s 330 } 331 332 s.Lock() 333 defer s.Unlock() 334 s.raw.Context = s.raw.Context.WithBaggageItem(key, val) 335 return s 336 } 337 338 func (s *spanImpl) BaggageItem(key string) string { 339 s.Lock() 340 defer s.Unlock() 341 return s.raw.Context.Baggage[key] 342 } 343 344 func (s *spanImpl) Operation() string { 345 return s.raw.Operation 346 } 347 348 func (s *spanImpl) Start() time.Time { 349 return s.raw.Start 350 }