mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-06-04 23:07:40 +02:00
Unify trace and metric exporter helpers (#944)
* Adjust Jaeger and Zipkin exporters helper methods * Update and add tests, examples, various minor improvements * Update changelog * Correct the Zipkin example - wait for the spans to be exported - rebuild the example * Zipkin service name as argument * Rework Jaeger and Zipkin tests * Include more detailed Changelog Co-authored-by: ET <evantorrie@users.noreply.github.com> Co-authored-by: Liz Fong-Jones <lizf@honeycomb.io> Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
This commit is contained in:
parent
c5d77d234c
commit
f31d8ec1d0
@ -8,6 +8,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Jaeger exporter helpers: added InstallNewPipeline and removed RegisterGlobal option instead. (#944)
|
||||||
|
- Zipkin exporter helpers: pipeline methods introduced, new exporter method adjusted. (#944)
|
||||||
|
|
||||||
## [0.9.0] - 2020-07-20
|
## [0.9.0] - 2020-07-20
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -30,7 +30,7 @@ import (
|
|||||||
// initTracer creates a new trace provider instance and registers it as global trace provider.
|
// initTracer creates a new trace provider instance and registers it as global trace provider.
|
||||||
func initTracer() func() {
|
func initTracer() func() {
|
||||||
// Create and install Jaeger export pipeline
|
// Create and install Jaeger export pipeline
|
||||||
_, flush, err := jaeger.NewExportPipeline(
|
flush, err := jaeger.InstallNewPipeline(
|
||||||
jaeger.WithCollectorEndpoint("http://localhost:14268/api/traces"),
|
jaeger.WithCollectorEndpoint("http://localhost:14268/api/traces"),
|
||||||
jaeger.WithProcess(jaeger.Process{
|
jaeger.WithProcess(jaeger.Process{
|
||||||
ServiceName: "trace-demo",
|
ServiceName: "trace-demo",
|
||||||
@ -39,7 +39,6 @@ func initTracer() func() {
|
|||||||
kv.Float64("float", 312.23),
|
kv.Float64("float", 312.23),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
jaeger.RegisterAsGlobal(),
|
|
||||||
jaeger.WithSDK(&sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
|
jaeger.WithSDK(&sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -33,30 +33,20 @@ var logger = log.New(os.Stderr, "zipkin-example", log.Ldate|log.Ltime|log.Llongf
|
|||||||
|
|
||||||
// initTracer creates a new trace provider instance and registers it as global trace provider.
|
// initTracer creates a new trace provider instance and registers it as global trace provider.
|
||||||
func initTracer(url string) {
|
func initTracer(url string) {
|
||||||
// Create Zipkin Exporter
|
// Create Zipkin Exporter and install it as a global tracer.
|
||||||
exporter, err := zipkin.NewExporter(
|
//
|
||||||
url,
|
|
||||||
"zipkin-example",
|
|
||||||
zipkin.WithLogger(logger),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For demoing purposes, always sample. In a production application, you should
|
// For demoing purposes, always sample. In a production application, you should
|
||||||
// configure this to a trace.ProbabilitySampler set at the desired
|
// configure the sampler to a trace.ProbabilitySampler set at the desired
|
||||||
// probability.
|
// probability.
|
||||||
tp, err := sdktrace.NewProvider(
|
err := zipkin.InstallNewPipeline(
|
||||||
sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
|
url,
|
||||||
sdktrace.WithBatcher(exporter,
|
"zipkin-test",
|
||||||
sdktrace.WithBatchTimeout(5),
|
zipkin.WithLogger(logger),
|
||||||
sdktrace.WithMaxExportBatchSize(10),
|
zipkin.WithSDK(&sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
global.SetTraceProvider(tp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -73,7 +63,9 @@ func main() {
|
|||||||
bar(ctx)
|
bar(ctx)
|
||||||
<-time.After(6 * time.Millisecond)
|
<-time.After(6 * time.Millisecond)
|
||||||
span.End()
|
span.End()
|
||||||
<-time.After(24 * time.Millisecond)
|
|
||||||
|
// Wait for the spans to be exported.
|
||||||
|
<-time.After(5 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
func bar(ctx context.Context) {
|
func bar(ctx context.Context) {
|
||||||
|
@ -47,10 +47,6 @@ type options struct {
|
|||||||
|
|
||||||
Config *sdktrace.Config
|
Config *sdktrace.Config
|
||||||
|
|
||||||
// RegisterGlobal is set to true if the trace provider of the new pipeline should be
|
|
||||||
// registered as Global Trace Provider
|
|
||||||
RegisterGlobal bool
|
|
||||||
|
|
||||||
Disabled bool
|
Disabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,14 +78,8 @@ func WithSDK(config *sdktrace.Config) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterAsGlobal enables the registration of the trace provider of the new pipeline
|
// WithDisabled option will cause pipeline methods to use
|
||||||
// as Global Trace Provider.
|
// a no-op provider
|
||||||
func RegisterAsGlobal() Option {
|
|
||||||
return func(o *options) {
|
|
||||||
o.RegisterGlobal = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithDisabled(disabled bool) Option {
|
func WithDisabled(disabled bool) Option {
|
||||||
return func(o *options) {
|
return func(o *options) {
|
||||||
o.Disabled = disabled
|
o.Disabled = disabled
|
||||||
@ -177,13 +167,22 @@ func NewExportPipeline(endpointOption EndpointOption, opts ...Option) (apitrace.
|
|||||||
if exporter.o.Config != nil {
|
if exporter.o.Config != nil {
|
||||||
tp.ApplyConfig(*exporter.o.Config)
|
tp.ApplyConfig(*exporter.o.Config)
|
||||||
}
|
}
|
||||||
if exporter.o.RegisterGlobal {
|
|
||||||
global.SetTraceProvider(tp)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tp, exporter.Flush, nil
|
return tp, exporter.Flush, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InstallNewPipeline instantiates a NewExportPipeline with the
|
||||||
|
// recommended configuration and registers it globally.
|
||||||
|
func InstallNewPipeline(endpointOption EndpointOption, opts ...Option) (func(), error) {
|
||||||
|
tp, flushFn, err := NewExportPipeline(endpointOption, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
global.SetTraceProvider(tp)
|
||||||
|
return flushFn, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Process contains the information exported to jaeger about the source
|
// Process contains the information exported to jaeger about the source
|
||||||
// of the trace data.
|
// of the trace data.
|
||||||
type Process struct {
|
type Process struct {
|
||||||
|
@ -26,10 +26,12 @@ import (
|
|||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/api/support/bundler"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/api/global"
|
"go.opentelemetry.io/otel/api/global"
|
||||||
"go.opentelemetry.io/otel/api/kv"
|
"go.opentelemetry.io/otel/api/kv"
|
||||||
|
"go.opentelemetry.io/otel/api/trace"
|
||||||
apitrace "go.opentelemetry.io/otel/api/trace"
|
apitrace "go.opentelemetry.io/otel/api/trace"
|
||||||
gen "go.opentelemetry.io/otel/exporters/trace/jaeger/internal/gen-go/jaeger"
|
gen "go.opentelemetry.io/otel/exporters/trace/jaeger/internal/gen-go/jaeger"
|
||||||
ottest "go.opentelemetry.io/otel/internal/testing"
|
ottest "go.opentelemetry.io/otel/internal/testing"
|
||||||
@ -38,77 +40,251 @@ import (
|
|||||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewExporterPipelineWithRegistration(t *testing.T) {
|
const (
|
||||||
tp, fn, err := NewExportPipeline(
|
collectorEndpoint = "http://localhost:14268/api/traces"
|
||||||
WithCollectorEndpoint("http://localhost:14268/api/traces"),
|
agentEndpoint = "localhost:6831"
|
||||||
RegisterAsGlobal(),
|
)
|
||||||
)
|
|
||||||
defer fn()
|
func TestInstallNewPipeline(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
testCases := []struct {
|
||||||
assert.Same(t, tp, global.TraceProvider())
|
name string
|
||||||
|
endpoint EndpointOption
|
||||||
|
options []Option
|
||||||
|
expectedProvider trace.Provider
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple pipeline",
|
||||||
|
endpoint: WithCollectorEndpoint(collectorEndpoint),
|
||||||
|
expectedProvider: &sdktrace.Provider{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with agent endpoint",
|
||||||
|
endpoint: WithAgentEndpoint(agentEndpoint),
|
||||||
|
expectedProvider: &sdktrace.Provider{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with disabled",
|
||||||
|
endpoint: WithCollectorEndpoint(collectorEndpoint),
|
||||||
|
options: []Option{
|
||||||
|
WithDisabled(true),
|
||||||
|
},
|
||||||
|
expectedProvider: &apitrace.NoopProvider{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
fn, err := InstallNewPipeline(
|
||||||
|
tc.endpoint,
|
||||||
|
tc.options...,
|
||||||
|
)
|
||||||
|
defer fn()
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, tc.expectedProvider, global.TraceProvider())
|
||||||
|
|
||||||
|
global.SetTraceProvider(nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewExporterPipelineWithoutRegistration(t *testing.T) {
|
func TestNewExportPipeline(t *testing.T) {
|
||||||
tp, fn, err := NewExportPipeline(
|
testCases := []struct {
|
||||||
WithCollectorEndpoint("http://localhost:14268/api/traces"),
|
name string
|
||||||
)
|
endpoint EndpointOption
|
||||||
defer fn()
|
options []Option
|
||||||
assert.NoError(t, err)
|
expectedProviderType trace.Provider
|
||||||
assert.NotEqual(t, tp, global.TraceProvider())
|
testSpanSampling, spanShouldBeSampled bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple pipeline",
|
||||||
|
endpoint: WithCollectorEndpoint(collectorEndpoint),
|
||||||
|
expectedProviderType: &sdktrace.Provider{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with disabled",
|
||||||
|
endpoint: WithCollectorEndpoint(collectorEndpoint),
|
||||||
|
options: []Option{
|
||||||
|
WithDisabled(true),
|
||||||
|
},
|
||||||
|
expectedProviderType: &apitrace.NoopProvider{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "always on",
|
||||||
|
endpoint: WithCollectorEndpoint(collectorEndpoint),
|
||||||
|
options: []Option{
|
||||||
|
WithSDK(&sdktrace.Config{
|
||||||
|
DefaultSampler: sdktrace.AlwaysSample(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
expectedProviderType: &sdktrace.Provider{},
|
||||||
|
testSpanSampling: true,
|
||||||
|
spanShouldBeSampled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "never",
|
||||||
|
endpoint: WithCollectorEndpoint(collectorEndpoint),
|
||||||
|
options: []Option{
|
||||||
|
WithSDK(&sdktrace.Config{
|
||||||
|
DefaultSampler: sdktrace.NeverSample(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
expectedProviderType: &sdktrace.Provider{},
|
||||||
|
testSpanSampling: true,
|
||||||
|
spanShouldBeSampled: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
tp, fn, err := NewExportPipeline(
|
||||||
|
tc.endpoint,
|
||||||
|
tc.options...,
|
||||||
|
)
|
||||||
|
defer fn()
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEqual(t, tp, global.TraceProvider())
|
||||||
|
assert.IsType(t, tc.expectedProviderType, tp)
|
||||||
|
|
||||||
|
if tc.testSpanSampling {
|
||||||
|
_, span := tp.Tracer("jaeger test").Start(context.Background(), tc.name)
|
||||||
|
spanCtx := span.SpanContext()
|
||||||
|
assert.Equal(t, tc.spanShouldBeSampled, spanCtx.IsSampled())
|
||||||
|
span.End()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewExporterPipelineWithSDK(t *testing.T) {
|
func TestNewExportPipelineWithDisabledFromEnv(t *testing.T) {
|
||||||
tp, fn, err := NewExportPipeline(
|
envStore, err := ottest.SetEnvVariables(map[string]string{
|
||||||
WithCollectorEndpoint("http://localhost:14268/api/traces"),
|
envDisabled: "true",
|
||||||
WithSDK(&sdktrace.Config{
|
})
|
||||||
DefaultSampler: sdktrace.AlwaysSample(),
|
require.NoError(t, err)
|
||||||
}),
|
envStore.Record(envDisabled)
|
||||||
)
|
defer func() {
|
||||||
defer fn()
|
require.NoError(t, envStore.Restore())
|
||||||
assert.NoError(t, err)
|
}()
|
||||||
_, span := tp.Tracer("jaeger test").Start(context.Background(), "always-on")
|
|
||||||
spanCtx := span.SpanContext()
|
|
||||||
assert.True(t, spanCtx.IsSampled())
|
|
||||||
span.End()
|
|
||||||
|
|
||||||
tp2, fn, err := NewExportPipeline(
|
tp, fn, err := NewExportPipeline(
|
||||||
WithCollectorEndpoint("http://localhost:14268/api/traces"),
|
WithCollectorEndpoint(collectorEndpoint),
|
||||||
WithSDK(&sdktrace.Config{
|
|
||||||
DefaultSampler: sdktrace.NeverSample(),
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
defer fn()
|
defer fn()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
_, span2 := tp2.Tracer("jaeger test").Start(context.Background(), "never")
|
assert.IsType(t, &apitrace.NoopProvider{}, tp)
|
||||||
span2Ctx := span2.SpanContext()
|
|
||||||
assert.False(t, span2Ctx.IsSampled())
|
|
||||||
span2.End()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewRawExporter(t *testing.T) {
|
func TestNewRawExporter(t *testing.T) {
|
||||||
const (
|
testCases := []struct {
|
||||||
collectorEndpoint = "http://localhost"
|
name string
|
||||||
serviceName = "test-service"
|
endpoint EndpointOption
|
||||||
tagKey = "key"
|
options []Option
|
||||||
tagVal = "val"
|
expectedServiceName string
|
||||||
)
|
expectedTagsLen, expectedBufferMaxCount, expectedBatchMaxCount int
|
||||||
// Create Jaeger Exporter
|
}{
|
||||||
exp, err := NewRawExporter(
|
{
|
||||||
WithCollectorEndpoint(collectorEndpoint),
|
name: "default exporter",
|
||||||
WithProcess(Process{
|
endpoint: WithCollectorEndpoint(collectorEndpoint),
|
||||||
ServiceName: serviceName,
|
expectedServiceName: defaultServiceName,
|
||||||
Tags: []kv.KeyValue{
|
expectedBufferMaxCount: bundler.DefaultBufferedByteLimit,
|
||||||
kv.String(tagKey, tagVal),
|
expectedBatchMaxCount: bundler.DefaultBundleCountThreshold,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default exporter with agent endpoint",
|
||||||
|
endpoint: WithAgentEndpoint(agentEndpoint),
|
||||||
|
expectedServiceName: defaultServiceName,
|
||||||
|
expectedBufferMaxCount: bundler.DefaultBufferedByteLimit,
|
||||||
|
expectedBatchMaxCount: bundler.DefaultBundleCountThreshold,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with process",
|
||||||
|
endpoint: WithCollectorEndpoint(collectorEndpoint),
|
||||||
|
options: []Option{
|
||||||
|
WithProcess(
|
||||||
|
Process{
|
||||||
|
ServiceName: "jaeger-test",
|
||||||
|
Tags: []kv.KeyValue{
|
||||||
|
kv.String("key", "val"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}),
|
expectedServiceName: "jaeger-test",
|
||||||
)
|
expectedTagsLen: 1,
|
||||||
|
expectedBufferMaxCount: bundler.DefaultBufferedByteLimit,
|
||||||
|
expectedBatchMaxCount: bundler.DefaultBundleCountThreshold,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with buffer and batch max count",
|
||||||
|
endpoint: WithCollectorEndpoint(collectorEndpoint),
|
||||||
|
options: []Option{
|
||||||
|
WithProcess(
|
||||||
|
Process{
|
||||||
|
ServiceName: "jaeger-test",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
WithBufferMaxCount(99),
|
||||||
|
WithBatchMaxCount(99),
|
||||||
|
},
|
||||||
|
expectedServiceName: "jaeger-test",
|
||||||
|
expectedBufferMaxCount: 99,
|
||||||
|
expectedBatchMaxCount: 99,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
assert.NoError(t, err)
|
for _, tc := range testCases {
|
||||||
assert.EqualValues(t, serviceName, exp.process.ServiceName)
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
assert.Len(t, exp.process.Tags, 1)
|
exp, err := NewRawExporter(
|
||||||
|
tc.endpoint,
|
||||||
|
tc.options...,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.expectedServiceName, exp.process.ServiceName)
|
||||||
|
assert.Len(t, exp.process.Tags, tc.expectedTagsLen)
|
||||||
|
assert.Equal(t, tc.expectedBufferMaxCount, exp.bundler.BufferedByteLimit)
|
||||||
|
assert.Equal(t, tc.expectedBatchMaxCount, exp.bundler.BundleCountThreshold)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewRawExporterShouldFailIfCollectorEndpointEmpty(t *testing.T) {
|
func TestNewRawExporterShouldFail(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
endpoint EndpointOption
|
||||||
|
expectedErrMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with empty collector endpoint",
|
||||||
|
endpoint: WithCollectorEndpoint(""),
|
||||||
|
expectedErrMsg: "collectorEndpoint must not be empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with empty agent endpoint",
|
||||||
|
endpoint: WithAgentEndpoint(""),
|
||||||
|
expectedErrMsg: "agentEndpoint must not be empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with invalid agent endpoint",
|
||||||
|
endpoint: WithAgentEndpoint("localhost"),
|
||||||
|
expectedErrMsg: "address localhost: missing port in address",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
_, err := NewRawExporter(
|
||||||
|
tc.endpoint,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.EqualError(t, err, tc.expectedErrMsg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewRawExporterShouldFailIfCollectorUnset(t *testing.T) {
|
||||||
// Record and restore env
|
// Record and restore env
|
||||||
envStore := ottest.NewEnvStore()
|
envStore := ottest.NewEnvStore()
|
||||||
envStore.Record(envEndpoint)
|
envStore.Record(envEndpoint)
|
||||||
@ -179,29 +355,6 @@ func TestExporter_ExportSpan(t *testing.T) {
|
|||||||
assert.True(t, len(tc.spansUploaded) == 1)
|
assert.True(t, len(tc.spansUploaded) == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewRawExporterWithAgentEndpoint(t *testing.T) {
|
|
||||||
const agentEndpoint = "localhost:6831"
|
|
||||||
// Create Jaeger Exporter
|
|
||||||
_, err := NewRawExporter(
|
|
||||||
WithAgentEndpoint(agentEndpoint),
|
|
||||||
)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewRawExporterWithAgentShouldFailIfEndpointInvalid(t *testing.T) {
|
|
||||||
//empty
|
|
||||||
_, err := NewRawExporter(
|
|
||||||
WithAgentEndpoint(""),
|
|
||||||
)
|
|
||||||
assert.Error(t, err)
|
|
||||||
|
|
||||||
//invalid endpoint addr
|
|
||||||
_, err = NewRawExporter(
|
|
||||||
WithAgentEndpoint("http://localhost"),
|
|
||||||
)
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_spanDataToThrift(t *testing.T) {
|
func Test_spanDataToThrift(t *testing.T) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
traceID, _ := apitrace.IDFromHex("0102030405060708090a0b0c0d0e0f10")
|
traceID, _ := apitrace.IDFromHex("0102030405060708090a0b0c0d0e0f10")
|
||||||
@ -319,31 +472,3 @@ func Test_spanDataToThrift(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewExporterPipelineWithDisabled(t *testing.T) {
|
|
||||||
tp, fn, err := NewExportPipeline(
|
|
||||||
WithCollectorEndpoint("http://localhost:14268/api/traces"),
|
|
||||||
WithDisabled(true),
|
|
||||||
)
|
|
||||||
defer fn()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.IsType(t, &apitrace.NoopProvider{}, tp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewExporterPipelineWithDisabledFromEnv(t *testing.T) {
|
|
||||||
envStore, err := ottest.SetEnvVariables(map[string]string{
|
|
||||||
envDisabled: "true",
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
envStore.Record(envDisabled)
|
|
||||||
defer func() {
|
|
||||||
require.NoError(t, envStore.Restore())
|
|
||||||
}()
|
|
||||||
|
|
||||||
tp, fn, err := NewExportPipeline(
|
|
||||||
WithCollectorEndpoint("http://localhost:14268/api/traces"),
|
|
||||||
)
|
|
||||||
defer fn()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.IsType(t, &apitrace.NoopProvider{}, tp)
|
|
||||||
}
|
|
||||||
|
16
exporters/trace/zipkin/doc.go
Normal file
16
exporters/trace/zipkin/doc.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package zipkin contains an OpenTelemetry tracing exporter for Zipkin.
|
||||||
|
package zipkin // import "go.opentelemetry.io/otel/exporters/trace/zipkin"
|
@ -18,13 +18,16 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/api/global"
|
||||||
export "go.opentelemetry.io/otel/sdk/export/trace"
|
export "go.opentelemetry.io/otel/sdk/export/trace"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Exporter exports SpanData to the zipkin collector. It implements
|
// Exporter exports SpanData to the zipkin collector. It implements
|
||||||
@ -35,6 +38,7 @@ type Exporter struct {
|
|||||||
serviceName string
|
serviceName string
|
||||||
client *http.Client
|
client *http.Client
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
|
o options
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -42,51 +46,97 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Options contains configuration for the exporter.
|
// Options contains configuration for the exporter.
|
||||||
type Options struct {
|
type options struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
|
config *sdktrace.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option defines a function that configures the exporter.
|
// Option defines a function that configures the exporter.
|
||||||
type Option func(*Options)
|
type Option func(*options)
|
||||||
|
|
||||||
// WithLogger configures the exporter to use the passed logger.
|
// WithLogger configures the exporter to use the passed logger.
|
||||||
func WithLogger(logger *log.Logger) Option {
|
func WithLogger(logger *log.Logger) Option {
|
||||||
return func(opts *Options) {
|
return func(opts *options) {
|
||||||
opts.logger = logger
|
opts.logger = logger
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithClient configures the exporter to use the passed HTTP client.
|
// WithClient configures the exporter to use the passed HTTP client.
|
||||||
func WithClient(client *http.Client) Option {
|
func WithClient(client *http.Client) Option {
|
||||||
return func(opts *Options) {
|
return func(opts *options) {
|
||||||
opts.client = client
|
opts.client = client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExporter creates a new zipkin exporter.
|
// WithSDK sets the SDK config for the exporter pipeline.
|
||||||
func NewExporter(collectorURL string, serviceName string, os ...Option) (*Exporter, error) {
|
func WithSDK(config *sdktrace.Config) Option {
|
||||||
if _, err := url.Parse(collectorURL); err != nil {
|
return func(o *options) {
|
||||||
|
o.config = config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRawExporter creates a new Zipkin exporter.
|
||||||
|
func NewRawExporter(collectorURL, serviceName string, opts ...Option) (*Exporter, error) {
|
||||||
|
if collectorURL == "" {
|
||||||
|
return nil, errors.New("collector URL cannot be empty")
|
||||||
|
}
|
||||||
|
u, err := url.Parse(collectorURL)
|
||||||
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid collector URL: %v", err)
|
return nil, fmt.Errorf("invalid collector URL: %v", err)
|
||||||
}
|
}
|
||||||
if serviceName == "" {
|
if u.Scheme == "" || u.Host == "" {
|
||||||
return nil, fmt.Errorf("service name must be non-empty string")
|
return nil, errors.New("invalid collector URL")
|
||||||
}
|
}
|
||||||
opts := Options{}
|
|
||||||
for _, o := range os {
|
o := options{}
|
||||||
o(&opts)
|
for _, opt := range opts {
|
||||||
|
opt(&o)
|
||||||
}
|
}
|
||||||
if opts.client == nil {
|
if o.client == nil {
|
||||||
opts.client = http.DefaultClient
|
o.client = http.DefaultClient
|
||||||
}
|
}
|
||||||
return &Exporter{
|
return &Exporter{
|
||||||
url: collectorURL,
|
url: collectorURL,
|
||||||
client: opts.client,
|
client: o.client,
|
||||||
logger: opts.logger,
|
logger: o.logger,
|
||||||
serviceName: serviceName,
|
serviceName: serviceName,
|
||||||
|
o: o,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewExportPipeline sets up a complete export pipeline
|
||||||
|
// with the recommended setup for trace provider
|
||||||
|
func NewExportPipeline(collectorURL, serviceName string, opts ...Option) (*sdktrace.Provider, error) {
|
||||||
|
exp, err := NewRawExporter(collectorURL, serviceName, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
batcher := sdktrace.WithBatcher(exp)
|
||||||
|
tp, err := sdktrace.NewProvider(batcher)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if exp.o.config != nil {
|
||||||
|
tp.ApplyConfig(*exp.o.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallNewPipeline instantiates a NewExportPipeline with the
|
||||||
|
// recommended configuration and registers it globally.
|
||||||
|
func InstallNewPipeline(collectorURL, serviceName string, opts ...Option) error {
|
||||||
|
tp, err := NewExportPipeline(collectorURL, serviceName, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
global.SetTraceProvider(tp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ExportSpans is a part of an implementation of the SpanBatcher
|
// ExportSpans is a part of an implementation of the SpanBatcher
|
||||||
// interface.
|
// interface.
|
||||||
func (e *Exporter) ExportSpans(ctx context.Context, batch []*export.SpanData) {
|
func (e *Exporter) ExportSpans(ctx context.Context, batch []*export.SpanData) {
|
||||||
|
@ -27,13 +27,118 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
zkmodel "github.com/openzipkin/zipkin-go/model"
|
zkmodel "github.com/openzipkin/zipkin-go/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/api/global"
|
||||||
"go.opentelemetry.io/otel/api/trace"
|
"go.opentelemetry.io/otel/api/trace"
|
||||||
export "go.opentelemetry.io/otel/sdk/export/trace"
|
export "go.opentelemetry.io/otel/sdk/export/trace"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
collectorURL = "http://localhost:9411/api/v2/spans"
|
||||||
|
serviceName = "zipkin-test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInstallNewPipeline(t *testing.T) {
|
||||||
|
err := InstallNewPipeline(
|
||||||
|
collectorURL,
|
||||||
|
serviceName,
|
||||||
|
)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, &sdktrace.Provider{}, global.TraceProvider())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewExportPipeline(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
options []Option
|
||||||
|
testSpanSampling, spanShouldBeSampled bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple pipeline",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "always on",
|
||||||
|
options: []Option{
|
||||||
|
WithSDK(&sdktrace.Config{
|
||||||
|
DefaultSampler: sdktrace.AlwaysSample(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
testSpanSampling: true,
|
||||||
|
spanShouldBeSampled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "never",
|
||||||
|
options: []Option{
|
||||||
|
WithSDK(&sdktrace.Config{
|
||||||
|
DefaultSampler: sdktrace.NeverSample(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
testSpanSampling: true,
|
||||||
|
spanShouldBeSampled: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
tp, err := NewExportPipeline(
|
||||||
|
collectorURL,
|
||||||
|
serviceName,
|
||||||
|
tc.options...,
|
||||||
|
)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEqual(t, tp, global.TraceProvider())
|
||||||
|
|
||||||
|
if tc.testSpanSampling {
|
||||||
|
_, span := tp.Tracer("zipkin test").Start(context.Background(), tc.name)
|
||||||
|
spanCtx := span.SpanContext()
|
||||||
|
assert.Equal(t, tc.spanShouldBeSampled, spanCtx.IsSampled())
|
||||||
|
span.End()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewRawExporter(t *testing.T) {
|
||||||
|
exp, err := NewRawExporter(
|
||||||
|
collectorURL,
|
||||||
|
serviceName,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, serviceName, exp.serviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewRawExporterShouldFailInvalidCollectorURL(t *testing.T) {
|
||||||
|
var (
|
||||||
|
exp *Exporter
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// cannot be empty
|
||||||
|
exp, err = NewRawExporter(
|
||||||
|
"",
|
||||||
|
serviceName,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.EqualError(t, err, "collector URL cannot be empty")
|
||||||
|
assert.Nil(t, exp)
|
||||||
|
|
||||||
|
// invalid URL
|
||||||
|
exp, err = NewRawExporter(
|
||||||
|
"localhost",
|
||||||
|
serviceName,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.EqualError(t, err, "invalid collector URL")
|
||||||
|
assert.Nil(t, exp)
|
||||||
|
}
|
||||||
|
|
||||||
type mockZipkinCollector struct {
|
type mockZipkinCollector struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
url string
|
url string
|
||||||
@ -230,9 +335,7 @@ func TestExportSpans(t *testing.T) {
|
|||||||
defer collector.Close()
|
defer collector.Close()
|
||||||
ls := &logStore{T: t}
|
ls := &logStore{T: t}
|
||||||
logger := logStoreLogger(ls)
|
logger := logStoreLogger(ls)
|
||||||
_, err := NewExporter(collector.url, "", WithLogger(logger))
|
exporter, err := NewRawExporter(collector.url, "exporter-test", WithLogger(logger))
|
||||||
require.Error(t, err, "service name must be non-empty string")
|
|
||||||
exporter, err := NewExporter(collector.url, "exporter-test", WithLogger(logger))
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
require.Len(t, ls.Messages, 0)
|
require.Len(t, ls.Messages, 0)
|
@ -29,7 +29,7 @@ type EnvStore interface {
|
|||||||
// Records the environment variable into the store.
|
// Records the environment variable into the store.
|
||||||
Record(key string)
|
Record(key string)
|
||||||
|
|
||||||
// Restore recover the environment variables in the store.
|
// Restore recovers the environment variables in the store.
|
||||||
Restore() error
|
Restore() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user