-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #151 from appoptics/add-opentracing-multitracer
Add OpenTracing multiplexing tracer
- Loading branch information
Showing
6 changed files
with
336 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package opentracing | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/appoptics/appoptics-apm-go/v1/ao/internal/reporter" | ||
mt "github.com/appoptics/appoptics-apm-go/v1/contrib/multitracer" | ||
opentracing "github.com/opentracing/opentracing-go" | ||
"github.com/opentracing/opentracing-go/harness" | ||
) | ||
|
||
// This test sets up the AO Tracer wrapped in a MultiTracer | ||
func TestMultiTracerAPICheck(t *testing.T) { | ||
_ = reporter.SetTestReporter(reporter.TestReporterDisableDefaultSetting(true)) // set up test reporter | ||
multiTracer := &mt.MultiTracer{Tracers: []opentracing.Tracer{NewTracer()}} | ||
|
||
harness.RunAPIChecks(t, func() (tracer opentracing.Tracer, closer func()) { | ||
return multiTracer, nil | ||
}, | ||
harness.CheckBaggageValues(false), | ||
harness.CheckInject(true), | ||
harness.CheckExtract(true), | ||
harness.UseProbe(&multiApiCheckProbe{ | ||
mt: multiTracer, | ||
probes: []harness.APICheckProbe{apiCheckProbe{}}, | ||
}), | ||
) | ||
} | ||
|
||
type multiApiCheckProbe struct { | ||
mt *mt.MultiTracer | ||
probes []harness.APICheckProbe | ||
} | ||
|
||
func (m *multiApiCheckProbe) SameTrace(first, second opentracing.Span) bool { | ||
sp1 := first.(*mt.MultiSpan) | ||
sp2 := second.(*mt.MultiSpan) | ||
|
||
for i := range m.mt.Tracers { | ||
if m.probes[i] == nil { | ||
continue | ||
} | ||
if !m.probes[i].SameTrace(sp1.Spans[i], sp2.Spans[i]) { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
func (m *multiApiCheckProbe) SameSpanContext(span opentracing.Span, spanCtx opentracing.SpanContext) bool { | ||
sp := span.(*mt.MultiSpan) | ||
sc := spanCtx.(*mt.MultiSpanContext) | ||
|
||
for i := range m.mt.Tracers { | ||
if m.probes[i] == nil { | ||
continue | ||
} | ||
if !m.probes[i].SameSpanContext(sp.Spans[i], sc.SpanContexts[i]) { | ||
return false | ||
} | ||
} | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// +build basictracer | ||
// | ||
// Behind a build tag avoid adding a dependency on basictracer-go | ||
|
||
package opentracing | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/appoptics/appoptics-apm-go/v1/ao/internal/reporter" | ||
mt "github.com/appoptics/appoptics-apm-go/v1/contrib/multitracer" | ||
bt "github.com/opentracing/basictracer-go" | ||
opentracing "github.com/opentracing/opentracing-go" | ||
"github.com/opentracing/opentracing-go/harness" | ||
) | ||
|
||
// This test sets up AO Tracer and the OT "BasicTracer" side by side | ||
func TestMultiTracerAOBasicTracerAPICheck(t *testing.T) { | ||
_ = reporter.SetTestReporter(reporter.TestReporterDisableDefaultSetting(true)) // set up test reporter | ||
multiTracer := &mt.MultiTracer{ | ||
Tracers: []opentracing.Tracer{ | ||
NewTracer(), | ||
bt.NewWithOptions(bt.Options{ | ||
Recorder: bt.NewInMemoryRecorder(), | ||
ShouldSample: func(traceID uint64) bool { return true }, // always sample | ||
}), | ||
}} | ||
|
||
harness.RunAPIChecks(t, func() (tracer opentracing.Tracer, closer func()) { | ||
return multiTracer, nil | ||
}, | ||
harness.CheckBaggageValues(false), | ||
harness.CheckInject(true), | ||
harness.CheckExtract(true), | ||
harness.UseProbe(&multiApiCheckProbe{ | ||
mt: multiTracer, | ||
probes: []harness.APICheckProbe{apiCheckProbe{}, nil}, | ||
}), | ||
) | ||
} | ||
|
||
// This test sets up the OT "BasicTracer" wrapped in a MultiTracer | ||
func TestMultiTracerBasicTracerAPICheck(t *testing.T) { | ||
_ = reporter.SetTestReporter(reporter.TestReporterDisableDefaultSetting(true)) // set up test reporter | ||
harness.RunAPIChecks(t, func() (tracer opentracing.Tracer, closer func()) { | ||
return &mt.MultiTracer{ | ||
Tracers: []opentracing.Tracer{ | ||
bt.NewWithOptions(bt.Options{ | ||
Recorder: bt.NewInMemoryRecorder(), | ||
ShouldSample: func(traceID uint64) bool { return true }, // always sample | ||
}), | ||
}}, nil | ||
}, | ||
harness.CheckBaggageValues(false), | ||
harness.CheckInject(true), | ||
harness.CheckExtract(true), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
// Package multitracer provides a way to run more than one OpenTracing tracers, by multiplexing calls across | ||
// multiple Tracer, Span, and SpanContext implementations. The goal is to support a user sending data to | ||
// two tracing vendors simultaneously (e.g., AppOptics and another implementation). | ||
// | ||
// Currently, baggage propagation is not supported, and the two tracers must use distinct HTTP header | ||
// names, so as not to clobber each other. | ||
package multitracer | ||
|
||
import ( | ||
ot "github.com/opentracing/opentracing-go" | ||
"github.com/opentracing/opentracing-go/log" | ||
) | ||
|
||
// MultiTracer multiplexes OpenTracing API calls across multiple Tracer implementations. | ||
type MultiTracer struct { | ||
Tracers []ot.Tracer | ||
} | ||
|
||
// MultiSpan represents a list of Spans returned from multiple Tracers, managed by a MultiTracer. | ||
type MultiSpan struct { | ||
multiTracer *MultiTracer | ||
Spans []ot.Span | ||
} | ||
|
||
// MultiSpanContext represents a list of SpanContext returned from multiple Spans, managed by a MultiTracer. | ||
type MultiSpanContext struct { | ||
multiTracer *MultiTracer | ||
SpanContexts []ot.SpanContext | ||
} | ||
|
||
func (m *MultiTracer) StartSpan(operationName string, opts ...ot.StartSpanOption) ot.Span { | ||
ret := &MultiSpan{ | ||
multiTracer: m, | ||
Spans: make([]ot.Span, len(m.Tracers)), | ||
} | ||
for i, t := range m.Tracers { | ||
// look for ot.StartSpanOptions with a MultiSpanContext in an ot.SpanReference | ||
tracerSpecificOpts := []ot.StartSpanOption{} | ||
for _, opt := range opts { | ||
switch o := opt.(type) { | ||
case ot.SpanReference: | ||
// pull out tracer-specific SpanContext from MultiSpanContext | ||
refCtx := o.ReferencedContext.(*MultiSpanContext) | ||
tracerSpecificReference := ot.SpanReference{ | ||
Type: o.Type, | ||
ReferencedContext: refCtx.SpanContexts[i], | ||
} | ||
tracerSpecificOpts = append(tracerSpecificOpts, tracerSpecificReference) | ||
case ot.StartTime, ot.Tags, ot.Tag: | ||
tracerSpecificOpts = append(tracerSpecificOpts, opt) | ||
} | ||
} | ||
ret.Spans[i] = t.StartSpan(operationName, tracerSpecificOpts...) | ||
} | ||
return ret | ||
} | ||
|
||
// Inject propagates context using multiple tracers. Errors from individual tracers are dropped. | ||
func (m *MultiTracer) Inject(sm ot.SpanContext, format interface{}, carrier interface{}) error { | ||
sc, ok := sm.(*MultiSpanContext) | ||
if !ok { | ||
return ot.ErrInvalidSpanContext | ||
} | ||
errs := make([]error, len(m.Tracers)) | ||
for i, t := range m.Tracers { | ||
errs[i] = t.Inject(sc.SpanContexts[i], format, carrier) | ||
} | ||
|
||
// if every tracer returned an error, then also return the first error. | ||
var allErrors = true | ||
for _, err := range errs { | ||
if err == nil { | ||
allErrors = false | ||
break | ||
} | ||
} | ||
if allErrors { | ||
return errs[0] | ||
} | ||
return nil | ||
} | ||
|
||
// Extract reads context propagated using multiple tracers. Errors from individual tracers are dropped. | ||
func (m *MultiTracer) Extract(format interface{}, carrier interface{}) (ot.SpanContext, error) { | ||
ret := &MultiSpanContext{ | ||
multiTracer: m, | ||
SpanContexts: make([]ot.SpanContext, len(m.Tracers)), | ||
} | ||
errs := make([]error, len(m.Tracers)) | ||
for i, t := range m.Tracers { | ||
ret.SpanContexts[i], errs[i] = t.Extract(format, carrier) | ||
} | ||
|
||
// if every tracer returned an error, then also return the first error. | ||
var allErrors = true | ||
for _, err := range errs { | ||
if err == nil { | ||
allErrors = false | ||
break | ||
} | ||
} | ||
if allErrors { | ||
return nil, errs[0] | ||
} | ||
return ret, nil | ||
} | ||
|
||
func (m *MultiSpan) Finish() { | ||
for _, s := range m.Spans { | ||
s.Finish() | ||
} | ||
} | ||
|
||
func (m *MultiSpan) FinishWithOptions(opts ot.FinishOptions) { | ||
for _, s := range m.Spans { | ||
s.FinishWithOptions(opts) | ||
} | ||
} | ||
|
||
func (m *MultiSpan) Context() ot.SpanContext { | ||
ret := &MultiSpanContext{ | ||
multiTracer: m.multiTracer, | ||
SpanContexts: make([]ot.SpanContext, len(m.Spans)), | ||
} | ||
for i, s := range m.Spans { | ||
ret.SpanContexts[i] = s.Context() | ||
} | ||
return ret | ||
} | ||
|
||
func (m *MultiSpan) SetOperationName(operationName string) ot.Span { | ||
for i, s := range m.Spans { | ||
m.Spans[i] = s.SetOperationName(operationName) | ||
} | ||
return m | ||
} | ||
|
||
func (m *MultiSpan) SetTag(key string, value interface{}) ot.Span { | ||
for i, s := range m.Spans { | ||
m.Spans[i] = s.SetTag(key, value) | ||
} | ||
return m | ||
} | ||
|
||
func (m *MultiSpan) LogFields(fields ...log.Field) { | ||
for _, s := range m.Spans { | ||
s.LogFields(fields...) | ||
} | ||
} | ||
|
||
func (m *MultiSpan) LogKV(alternatingKeyValues ...interface{}) { | ||
for _, s := range m.Spans { | ||
s.LogKV(alternatingKeyValues...) | ||
} | ||
} | ||
|
||
// SetBaggageItem does nothing: baggage propagation is not supported. | ||
func (m *MultiSpan) SetBaggageItem(restrictedKey, value string) ot.Span { return m } | ||
|
||
// BaggageItem does nothing: baggage propagation is not supported. | ||
func (m *MultiSpan) BaggageItem(restrictedKey string) string { return "" } | ||
|
||
func (m *MultiSpan) Tracer() ot.Tracer { | ||
return m.multiTracer | ||
} | ||
|
||
func (m *MultiSpan) LogEvent(event string) { | ||
for _, s := range m.Spans { | ||
s.LogEvent(event) //nolint | ||
} | ||
} | ||
|
||
func (m *MultiSpan) LogEventWithPayload(event string, payload interface{}) { | ||
for _, s := range m.Spans { | ||
s.LogEventWithPayload(event, payload) //nolint | ||
} | ||
} | ||
|
||
func (m *MultiSpan) Log(data ot.LogData) { | ||
for _, s := range m.Spans { | ||
s.Log(data) //nolint | ||
} | ||
} | ||
|
||
// ForeachBaggageItem does nothing: baggage propagation is not supported. | ||
func (m *MultiSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {} |