Skip to content

Commit a1410cd

Browse files
committed
feat(#90): adds support for finished span handler.
1 parent 55065cd commit a1410cd

File tree

4 files changed

+131
-8
lines changed

4 files changed

+131
-8
lines changed

span_implementation.go

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ import (
2525
type spanImpl struct {
2626
mtx sync.RWMutex
2727
model.SpanModel
28-
tracer *Tracer
29-
mustCollect int32 // used as atomic bool (1 = true, 0 = false)
30-
flushOnFinish bool
28+
tracer *Tracer
29+
mustCollect int32 // used as atomic bool (1 = true, 0 = false)
30+
flushOnFinish bool
31+
finishedSpanHandler func(*model.SpanModel) bool
3132
}
3233

3334
func (s *spanImpl) Context() model.SpanContext {
@@ -77,21 +78,83 @@ func (s *spanImpl) Tag(key, value string) {
7778
}
7879

7980
func (s *spanImpl) Finish() {
81+
d := time.Since(s.Timestamp)
8082
if atomic.CompareAndSwapInt32(&s.mustCollect, 1, 0) {
81-
s.Duration = time.Since(s.Timestamp)
82-
if s.flushOnFinish {
83+
s.mtx.Lock()
84+
s.Duration = d
85+
s.mtx.Unlock()
86+
87+
shouldRecord := true
88+
if s.finishedSpanHandler != nil {
89+
shouldRecord = s.finishedSpanHandler(&s.SpanModel)
90+
}
91+
92+
if shouldRecord && s.flushOnFinish {
8393
s.tracer.reporter.Send(s.SpanModel)
8494
}
95+
return
8596
}
97+
98+
var hasDuration bool
99+
s.mtx.Lock()
100+
hasDuration = s.Duration == 0
101+
s.mtx.Unlock()
102+
103+
if hasDuration {
104+
// it was not meant to be recorded because the CompareAndSwap
105+
// did not happen (meaning that s.mustCollect is 0 at this moment)
106+
// and duration is still zero value.
107+
s.mtx.Lock()
108+
s.Duration = d
109+
s.mtx.Unlock()
110+
111+
shouldRecord := false
112+
if s.finishedSpanHandler != nil {
113+
shouldRecord = s.finishedSpanHandler(&s.SpanModel)
114+
}
115+
116+
if shouldRecord && s.flushOnFinish {
117+
s.tracer.reporter.Send(s.SpanModel)
118+
}
119+
} // else the span is being finished concurrently by another goroutine
86120
}
87121

88122
func (s *spanImpl) FinishedWithDuration(d time.Duration) {
89123
if atomic.CompareAndSwapInt32(&s.mustCollect, 1, 0) {
124+
s.mtx.Lock()
90125
s.Duration = d
91-
if s.flushOnFinish {
126+
s.mtx.Unlock()
127+
128+
shouldRecord := true
129+
if s.finishedSpanHandler != nil {
130+
shouldRecord = s.finishedSpanHandler(&s.SpanModel)
131+
}
132+
133+
if shouldRecord && s.flushOnFinish {
92134
s.tracer.reporter.Send(s.SpanModel)
93135
}
136+
return
94137
}
138+
139+
var hasDuration bool
140+
s.mtx.Lock()
141+
hasDuration = s.Duration == 0
142+
s.mtx.Unlock()
143+
144+
if hasDuration {
145+
// it was not meant to be recorded because the CompareAndSwap
146+
// did not happen (meaning that s.mustCollect is 0 at this moment)
147+
// and duration is still zero value.
148+
s.Duration = d
149+
shouldRecord := false
150+
if s.finishedSpanHandler != nil {
151+
shouldRecord = s.finishedSpanHandler(&s.SpanModel)
152+
}
153+
154+
if shouldRecord && s.flushOnFinish {
155+
s.tracer.reporter.Send(s.SpanModel)
156+
}
157+
} // else the span is being finished concurrently by another goroutine
95158
}
96159

97160
func (s *spanImpl) Flush() {

tracer.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type Tracer struct {
3737
noop int32 // used as atomic bool (1 = true, 0 = false)
3838
sharedSpans bool
3939
unsampledNoop bool
40+
finishedSpanHandler func(*model.SpanModel) bool
4041
}
4142

4243
// NewTracer returns a new Zipkin Tracer.
@@ -93,8 +94,9 @@ func (t *Tracer) StartSpan(name string, options ...SpanOption) Span {
9394
Annotations: make([]model.Annotation, 0),
9495
Tags: make(map[string]string),
9596
},
96-
flushOnFinish: true,
97-
tracer: t,
97+
flushOnFinish: true,
98+
tracer: t,
99+
finishedSpanHandler: t.finishedSpanHandler,
98100
}
99101

100102
// add default tracer tags to span

tracer_options.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,13 @@ func WithNoopTracer(tracerNoop bool) TracerOption {
136136
return nil
137137
}
138138
}
139+
140+
// WithFinishedSpanHandler if set, can mutate all span data and decide if a span
141+
// should be recorded or not. E.g. user could decide to sample all requests having
142+
// an error tag set or duration over certain value.
143+
func WithFinishedSpanHandler(handler func(*model.SpanModel) bool) TracerOption {
144+
return func(o *Tracer) error {
145+
o.finishedSpanHandler = handler
146+
return nil
147+
}
148+
}

tracer_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"testing"
2222
"time"
2323

24+
"github.com/openzipkin/zipkin-go/reporter/recorder"
25+
2426
"github.com/openzipkin/zipkin-go/idgenerator"
2527
"github.com/openzipkin/zipkin-go/model"
2628
"github.com/openzipkin/zipkin-go/reporter"
@@ -808,3 +810,49 @@ func TestLocalEndpoint(t *testing.T) {
808810
t.Errorf("IPv6 endpoint want %+v, have %+v", want.IPv6, have.IPv6)
809811
}
810812
}
813+
814+
func TestFinishedSpanHandlerAvoidsReporting(t *testing.T) {
815+
rep := recorder.NewReporter()
816+
defer rep.Close()
817+
818+
tracer, _ := NewTracer(
819+
rep,
820+
WithNoopSpan(false),
821+
WithSampler(AlwaysSample),
822+
WithFinishedSpanHandler(func(s *model.SpanModel) bool {
823+
return false
824+
}),
825+
)
826+
sp := tracer.StartSpan("test")
827+
sp.Finish()
828+
829+
if want, have := 0, len(rep.Flush()); want != have {
830+
t.Errorf("unexpected number of spans, want: %d, have: %d", want, have)
831+
}
832+
}
833+
834+
func TestFinishedSpanAddsTagsToSpan(t *testing.T) {
835+
rep := recorder.NewReporter()
836+
defer rep.Close()
837+
838+
tracer, _ := NewTracer(
839+
rep,
840+
WithNoopSpan(false),
841+
WithSampler(AlwaysSample),
842+
WithFinishedSpanHandler(func(s *model.SpanModel) bool {
843+
s.Tags["my_key"] = "my_value"
844+
return true
845+
}),
846+
)
847+
sp := tracer.StartSpan("test")
848+
sp.Finish()
849+
850+
repSans := rep.Flush()
851+
if want, have := 1, len(repSans); want != have {
852+
t.Errorf("unexpected number of spans, want: %d, have: %d", want, have)
853+
}
854+
855+
if want, have := "my_value", repSans[0].Tags["my_key"]; want != have {
856+
t.Errorf("unexpected number of spans, want: %q, have: %q", want, have)
857+
}
858+
}

0 commit comments

Comments
 (0)