@@ -29,6 +29,8 @@ type spanImpl struct {
2929 event func (SpanEvent )
3030 sync.Mutex // protects the fields below
3131 raw RawSpan
32+ // The number of logs dropped because of MaxLogsPerSpan.
33+ numDroppedLogs int
3234}
3335
3436var spanPool = & sync.Pool {New : func () interface {} {
@@ -98,6 +100,21 @@ func (s *spanImpl) LogKV(keyValues ...interface{}) {
98100 s .LogFields (fields ... )
99101}
100102
103+ func (s * spanImpl ) appendLog (lr opentracing.LogRecord ) {
104+ maxLogs := s .tracer .options .MaxLogsPerSpan
105+ if maxLogs == 0 || len (s .raw .Logs ) < maxLogs {
106+ s .raw .Logs = append (s .raw .Logs , lr )
107+ return
108+ }
109+
110+ // We have too many logs. We don't touch the first numOld logs; we treat the
111+ // rest as a circular buffer and overwrite the oldest log among those.
112+ numOld := (maxLogs - 1 ) / 2
113+ numNew := maxLogs - numOld
114+ s .raw .Logs [numOld + s .numDroppedLogs % numNew ] = lr
115+ s .numDroppedLogs ++
116+ }
117+
101118func (s * spanImpl ) LogFields (fields ... log.Field ) {
102119 lr := opentracing.LogRecord {
103120 Fields : fields ,
@@ -111,7 +128,7 @@ func (s *spanImpl) LogFields(fields ...log.Field) {
111128 if lr .Timestamp .IsZero () {
112129 lr .Timestamp = time .Now ()
113130 }
114- s .raw . Logs = append ( s . raw . Logs , lr )
131+ s .appendLog ( lr )
115132}
116133
117134func (s * spanImpl ) LogEvent (event string ) {
@@ -139,13 +156,30 @@ func (s *spanImpl) Log(ld opentracing.LogData) {
139156 ld .Timestamp = time .Now ()
140157 }
141158
142- s .raw . Logs = append ( s . raw . Logs , ld .ToLogRecord ())
159+ s .appendLog ( ld .ToLogRecord ())
143160}
144161
145162func (s * spanImpl ) Finish () {
146163 s .FinishWithOptions (opentracing.FinishOptions {})
147164}
148165
166+ // rotateLogBuffer rotates the records in the buffer: records 0 to pos-1 move at
167+ // the end (i.e. pos circular left shifts).
168+ func rotateLogBuffer (buf []opentracing.LogRecord , pos int ) {
169+ // This algorithm is described in:
170+ // http://www.cplusplus.com/reference/algorithm/rotate
171+ for first , middle , next := 0 , pos , pos ; first != middle ; {
172+ buf [first ], buf [next ] = buf [next ], buf [first ]
173+ first ++
174+ next ++
175+ if next == len (buf ) {
176+ next = middle
177+ } else if first == middle {
178+ middle = next
179+ }
180+ }
181+ }
182+
149183func (s * spanImpl ) FinishWithOptions (opts opentracing.FinishOptions ) {
150184 finishTime := opts .FinishTime
151185 if finishTime .IsZero () {
@@ -155,18 +189,42 @@ func (s *spanImpl) FinishWithOptions(opts opentracing.FinishOptions) {
155189
156190 s .Lock ()
157191 defer s .Unlock ()
158- if opts .LogRecords != nil {
159- s .raw .Logs = append (s .raw .Logs , opts .LogRecords ... )
192+
193+ for _ , lr := range opts .LogRecords {
194+ s .appendLog (lr )
160195 }
161196 for _ , ld := range opts .BulkLogData {
162- s .raw . Logs = append ( s . raw . Logs , ld .ToLogRecord ())
197+ s .appendLog ( ld .ToLogRecord ())
163198 }
199+
200+ if s .numDroppedLogs > 0 {
201+ // We dropped some log events, which means that we used part of Logs as a
202+ // circular buffer (see appendLog). De-circularize it.
203+ numOld := (len (s .raw .Logs ) - 1 ) / 2
204+ numNew := len (s .raw .Logs ) - numOld
205+ rotateLogBuffer (s .raw .Logs [numOld :], s .numDroppedLogs % numNew )
206+
207+ // Replace the log in the middle (the oldest "new" log) with information
208+ // about the dropped logs. This means that we are effectively dropping one
209+ // more "new" log.
210+ numDropped := s .numDroppedLogs + 1
211+ s .raw .Logs [numOld ] = opentracing.LogRecord {
212+ // Keep the timestamp of the last dropped event.
213+ Timestamp : s .raw .Logs [numOld ].Timestamp ,
214+ Fields : []log.Field {
215+ log .String ("event" , "dropped Span logs" ),
216+ log .Int ("dropped_log_count" , numDropped ),
217+ log .String ("component" , "basictracer" ),
218+ },
219+ }
220+ }
221+
164222 s .raw .Duration = duration
165223
166224 s .onFinish (s .raw )
167225 s .tracer .options .Recorder .RecordSpan (s .raw )
168226
169- // Last chance to get options before the span is possbily reset.
227+ // Last chance to get options before the span is possibly reset.
170228 poolEnabled := s .tracer .options .EnableSpanPool
171229 if s .tracer .options .DebugAssertUseAfterFinish {
172230 // This makes it much more likely to catch a panic on any subsequent
0 commit comments