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(&currentError)
   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(&currentError)
   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  }