diff --git a/CHANGELOG.md b/CHANGELOG.md index b5fd504ec02f..9bc3ac0a18ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Jaeger exporter populates Jaeger's Span Process from Resource. (#1673) - `"go.opentelemetry.io/otel/sdk/resource".NewWithAttributes` will now drop any invalid attributes passed. (#1703) - `"go.opentelemetry.io/otel/sdk/resource".StringDetector` will now error if the produced attribute is invalid. (#1703) +- Changed `WithSDK` to `WithSDKOptions` to accept variadic arguments of `TracerProviderOption` type in `go.opentelemetry.io/otel/exporters/trace/jaeger` package. (#1693) +- Changed `WithSDK` to `WithSDKOptions` to accept variadic arguments of `TracerProviderOption` type in `go.opentelemetry.io/otel/exporters/trace/zipkin` package. (#1693) ### Removed @@ -41,6 +43,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Removed `WithConfig` from tracer provider to avoid overriding configuration. (#1633) - Removed `serviceName` parameter from Zipkin exporter and uses resource instead. (#1549) - Removed `jaeger.WithProcess`. (#1673) +- Removed `ApplyConfig` method and `Config` struct from tracer provider. (#1693) ### Fixed diff --git a/example/jaeger/main.go b/example/jaeger/main.go index e05f62ca7ea9..832f7ac4f06c 100644 --- a/example/jaeger/main.go +++ b/example/jaeger/main.go @@ -34,14 +34,14 @@ func initTracer() func() { // Create and install Jaeger export pipeline. flush, err := jaeger.InstallNewPipeline( jaeger.WithCollectorEndpoint("http://localhost:14268/api/traces"), - jaeger.WithSDK(&sdktrace.Config{ - DefaultSampler: sdktrace.AlwaysSample(), - Resource: resource.NewWithAttributes( + jaeger.WithSDKOptions( + sdktrace.WithSampler(sdktrace.AlwaysSample()), + sdktrace.WithResource(resource.NewWithAttributes( semconv.ServiceNameKey.String("trace-demo"), attribute.String("exporter", "jaeger"), attribute.Float64("float", 312.23), - ), - }), + )), + ), ) if err != nil { log.Fatal(err) diff --git a/example/zipkin/main.go b/example/zipkin/main.go index b9cf0ca6f80c..f299b4bc2331 100644 --- a/example/zipkin/main.go +++ b/example/zipkin/main.go @@ -43,7 +43,7 @@ func initTracer(url string) func() { exporter, err := zipkin.NewRawExporter( url, zipkin.WithLogger(logger), - zipkin.WithSDK(&sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}), + zipkin.WithSDKOptions(sdktrace.WithSampler(sdktrace.AlwaysSample())), ) if err != nil { log.Fatal(err) diff --git a/exporters/trace/jaeger/jaeger.go b/exporters/trace/jaeger/jaeger.go index 1409f3636d69..11b3e5d9c461 100644 --- a/exporters/trace/jaeger/jaeger.go +++ b/exporters/trace/jaeger/jaeger.go @@ -52,7 +52,8 @@ type options struct { // BatchMaxCount defines the maximum number of spans sent in one batch BatchMaxCount int - Config *sdktrace.Config + // TracerProviderOptions defines the options for tracer provider of sdk. + TracerProviderOptions []sdktrace.TracerProviderOption Disabled bool } @@ -71,10 +72,10 @@ func WithBatchMaxCount(batchMaxCount int) Option { } } -// WithSDK sets the SDK config for the exporter pipeline. -func WithSDK(config *sdktrace.Config) Option { +// WithSDKOptions configures options for tracer provider of sdk. +func WithSDKOptions(opts ...sdktrace.TracerProviderOption) Option { return func(o *options) { - o.Config = config + o.TracerProviderOptions = opts } } @@ -157,15 +158,7 @@ func NewExportPipeline(endpointOption EndpointOption, opts ...Option) (trace.Tra return nil, nil, err } - pOpts := []sdktrace.TracerProviderOption{sdktrace.WithSyncer(exporter)} - if exporter.o.Config != nil { - pOpts = append(pOpts, - sdktrace.WithSampler(exporter.o.Config.DefaultSampler), - sdktrace.WithIDGenerator(exporter.o.Config.IDGenerator), - sdktrace.WithSpanLimits(exporter.o.Config.SpanLimits), - sdktrace.WithResource(exporter.o.Config.Resource), - ) - } + pOpts := append(exporter.o.TracerProviderOptions, sdktrace.WithSyncer(exporter)) tp := sdktrace.NewTracerProvider(pOpts...) return tp, exporter.Flush, nil } diff --git a/exporters/trace/jaeger/jaeger_test.go b/exporters/trace/jaeger/jaeger_test.go index 036771c677fc..0226d79a8a0f 100644 --- a/exporters/trace/jaeger/jaeger_test.go +++ b/exporters/trace/jaeger/jaeger_test.go @@ -17,6 +17,7 @@ package jaeger import ( "context" "encoding/binary" + "fmt" "os" "sort" "strings" @@ -114,9 +115,7 @@ func TestNewExportPipeline(t *testing.T) { name: "always on", endpoint: WithCollectorEndpoint(collectorEndpoint), options: []Option{ - WithSDK(&sdktrace.Config{ - DefaultSampler: sdktrace.AlwaysSample(), - }), + WithSDKOptions(sdktrace.WithSampler(sdktrace.AlwaysSample())), }, expectedProviderType: &sdktrace.TracerProvider{}, testSpanSampling: true, @@ -126,9 +125,7 @@ func TestNewExportPipeline(t *testing.T) { name: "never", endpoint: WithCollectorEndpoint(collectorEndpoint), options: []Option{ - WithSDK(&sdktrace.Config{ - DefaultSampler: sdktrace.NeverSample(), - }), + WithSDKOptions(sdktrace.WithSampler(sdktrace.NeverSample())), }, expectedProviderType: &sdktrace.TracerProvider{}, testSpanSampling: true, @@ -281,11 +278,11 @@ func TestNewRawExporterShouldFailIfCollectorUnset(t *testing.T) { } type testCollectorEnpoint struct { - spansUploaded []*gen.Span + batchesUploaded []*gen.Batch } func (c *testCollectorEnpoint) upload(batch *gen.Batch) error { - c.spansUploaded = append(c.spansUploaded, batch.Spans...) + c.batchesUploaded = append(c.batchesUploaded, batch) return nil } @@ -297,6 +294,12 @@ func withTestCollectorEndpoint() func() (batchUploader, error) { } } +func withTestCollectorEndpointInjected(ce *testCollectorEnpoint) func() (batchUploader, error) { + return func() (batchUploader, error) { + return ce, nil + } +} + func TestExporter_ExportSpan(t *testing.T) { const ( serviceName = "test-service" @@ -306,12 +309,12 @@ func TestExporter_ExportSpan(t *testing.T) { // Create Jaeger Exporter exp, err := NewRawExporter( withTestCollectorEndpoint(), - WithSDK(&sdktrace.Config{ - Resource: resource.NewWithAttributes( + WithSDKOptions( + sdktrace.WithResource(resource.NewWithAttributes( semconv.ServiceNameKey.String(serviceName), attribute.String(tagKey, tagVal), - ), - }), + )), + ), ) assert.NoError(t, err) @@ -328,7 +331,8 @@ func TestExporter_ExportSpan(t *testing.T) { exp.Flush() tc := exp.uploader.(*testCollectorEnpoint) - assert.True(t, len(tc.spansUploaded) == 1) + assert.True(t, len(tc.batchesUploaded) == 1) + assert.True(t, len(tc.batchesUploaded[0].GetSpans()) == 1) } func Test_spanSnapshotToThrift(t *testing.T) { @@ -886,3 +890,50 @@ func TestProcess(t *testing.T) { }) } } + +func TestNewExporterPipelineWithOptions(t *testing.T) { + envStore, err := ottest.SetEnvVariables(map[string]string{ + envDisabled: "false", + }) + require.NoError(t, err) + defer func() { + require.NoError(t, envStore.Restore()) + }() + + const ( + serviceName = "test-service" + eventCountLimit = 10 + ) + + testCollector := &testCollectorEnpoint{} + tp, spanFlush, err := NewExportPipeline( + withTestCollectorEndpointInjected(testCollector), + WithSDKOptions( + sdktrace.WithResource(resource.NewWithAttributes( + semconv.ServiceNameKey.String(serviceName), + )), + sdktrace.WithSpanLimits(sdktrace.SpanLimits{ + EventCountLimit: eventCountLimit, + }), + ), + ) + assert.NoError(t, err) + + otel.SetTracerProvider(tp) + _, span := otel.Tracer("test-tracer").Start(context.Background(), "test-span") + for i := 0; i < eventCountLimit*2; i++ { + span.AddEvent(fmt.Sprintf("event-%d", i)) + } + span.End() + spanFlush() + + assert.True(t, span.SpanContext().IsValid()) + + batchesUploaded := testCollector.batchesUploaded + assert.True(t, len(batchesUploaded) == 1) + uploadedBatch := batchesUploaded[0] + assert.Equal(t, serviceName, uploadedBatch.GetProcess().GetServiceName()) + assert.True(t, len(uploadedBatch.GetSpans()) == 1) + uploadedSpan := uploadedBatch.GetSpans()[0] + assert.Equal(t, eventCountLimit, len(uploadedSpan.GetLogs())) +} diff --git a/exporters/trace/zipkin/zipkin.go b/exporters/trace/zipkin/zipkin.go index 9da317ee709c..f6669eda0aea 100644 --- a/exporters/trace/zipkin/zipkin.go +++ b/exporters/trace/zipkin/zipkin.go @@ -53,7 +53,7 @@ var ( type options struct { client *http.Client logger *log.Logger - config *sdktrace.Config + tpOpts []sdktrace.TracerProviderOption } // Option defines a function that configures the exporter. @@ -73,10 +73,10 @@ func WithClient(client *http.Client) Option { } } -// WithSDK sets the SDK config for the exporter pipeline. -func WithSDK(config *sdktrace.Config) Option { - return func(o *options) { - o.config = config +// WithSDKOptions configures options passed to the created TracerProvider. +func WithSDKOptions(tpOpts ...sdktrace.TracerProviderOption) Option { + return func(opts *options) { + opts.tpOpts = tpOpts } } @@ -116,10 +116,8 @@ func NewExportPipeline(collectorURL string, opts ...Option) (*sdktrace.TracerPro return nil, err } - tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exporter)) - if exporter.o.config != nil { - tp.ApplyConfig(*exporter.o.config) - } + tpOpts := append(exporter.o.tpOpts, sdktrace.WithBatcher(exporter)) + tp := sdktrace.NewTracerProvider(tpOpts...) return tp, err } diff --git a/exporters/trace/zipkin/zipkin_test.go b/exporters/trace/zipkin/zipkin_test.go index fce98f033e4c..e45cf91e1371 100644 --- a/exporters/trace/zipkin/zipkin_test.go +++ b/exporters/trace/zipkin/zipkin_test.go @@ -63,9 +63,7 @@ func TestNewExportPipeline(t *testing.T) { { name: "always on", options: []Option{ - WithSDK(&sdktrace.Config{ - DefaultSampler: sdktrace.AlwaysSample(), - }), + WithSDKOptions(sdktrace.WithSampler(sdktrace.AlwaysSample())), }, testSpanSampling: true, spanShouldBeSampled: true, @@ -73,9 +71,7 @@ func TestNewExportPipeline(t *testing.T) { { name: "never", options: []Option{ - WithSDK(&sdktrace.Config{ - DefaultSampler: sdktrace.NeverSample(), - }), + WithSDKOptions(sdktrace.WithSampler(sdktrace.NeverSample())), }, testSpanSampling: true, spanShouldBeSampled: false, @@ -389,3 +385,34 @@ func TestErrorOnExportShutdownExporter(t *testing.T) { assert.NoError(t, exp.Shutdown(context.Background())) assert.NoError(t, exp.ExportSpans(context.Background(), nil)) } + +func TestNewExportPipelineWithOptions(t *testing.T) { + const eventCountLimit = 10 + + collector := startMockZipkinCollector(t) + defer collector.Close() + + tp, err := NewExportPipeline(collector.url, + WithSDKOptions( + sdktrace.WithResource(resource.NewWithAttributes( + semconv.ServiceNameKey.String("zipkin-test"), + )), + sdktrace.WithSpanLimits(sdktrace.SpanLimits{ + EventCountLimit: eventCountLimit, + }), + ), + ) + require.NoError(t, err) + + otel.SetTracerProvider(tp) + _, span := otel.Tracer("zipkin-tracer").Start(context.Background(), "test-span") + for i := 0; i < eventCountLimit*2; i++ { + span.AddEvent(fmt.Sprintf("event-%d", i)) + } + span.End() + + require.NoError(t, tp.Shutdown(context.Background())) + require.Equal(t, 1, collector.ModelsLen()) + model := collector.StealModels()[0] + require.Equal(t, len(model.Annotations), eventCountLimit) +} diff --git a/sdk/trace/config.go b/sdk/trace/config.go index 87ca50baed8e..61a30439251f 100644 --- a/sdk/trace/config.go +++ b/sdk/trace/config.go @@ -14,25 +14,6 @@ package trace // import "go.opentelemetry.io/otel/sdk/trace" -import ( - "go.opentelemetry.io/otel/sdk/resource" -) - -// Config represents the global tracing configuration. -type Config struct { - // DefaultSampler is the default sampler used when creating new spans. - DefaultSampler Sampler - - // IDGenerator is for internal use only. - IDGenerator IDGenerator - - // SpanLimits used to limit the number of attributes, events and links to a span. - SpanLimits SpanLimits - - // Resource contains attributes representing an entity that produces telemetry. - Resource *resource.Resource -} - // SpanLimits represents the limits of a span. type SpanLimits struct { // AttributeCountLimit is the maximum allowed span attribute count. @@ -51,6 +32,24 @@ type SpanLimits struct { AttributePerLinkCountLimit int } +func (sl *SpanLimits) ensureDefault() { + if sl.EventCountLimit <= 0 { + sl.EventCountLimit = DefaultEventCountLimit + } + if sl.AttributeCountLimit <= 0 { + sl.AttributeCountLimit = DefaultAttributeCountLimit + } + if sl.LinkCountLimit <= 0 { + sl.LinkCountLimit = DefaultLinkCountLimit + } + if sl.AttributePerEventCountLimit <= 0 { + sl.AttributePerEventCountLimit = DefaultAttributePerEventCountLimit + } + if sl.AttributePerLinkCountLimit <= 0 { + sl.AttributePerLinkCountLimit = DefaultAttributePerLinkCountLimit + } +} + const ( // DefaultAttributeCountLimit is the default maximum allowed span attribute count. DefaultAttributeCountLimit = 128 diff --git a/sdk/trace/provider.go b/sdk/trace/provider.go index df1f4f296863..fde086ffbb7c 100644 --- a/sdk/trace/provider.go +++ b/sdk/trace/provider.go @@ -38,7 +38,18 @@ const ( // TracerProviderConfig type TracerProviderConfig struct { processors []SpanProcessor - config Config + + // sampler is the default sampler used when creating new spans. + sampler Sampler + + // idGenerator is used to generate all Span and Trace IDs when needed. + idGenerator IDGenerator + + // spanLimits defines the attribute, event, and link limits for spans. + spanLimits SpanLimits + + // resource contains attributes representing an entity that produces telemetry. + resource *resource.Resource } type TracerProviderOption func(*TracerProviderConfig) @@ -47,7 +58,10 @@ type TracerProvider struct { mu sync.Mutex namedTracer map[instrumentation.Library]*tracer spanProcessors atomic.Value - config atomic.Value // access atomically + sampler Sampler + idGenerator IDGenerator + spanLimits SpanLimits + resource *resource.Resource } var _ trace.TracerProvider = &TracerProvider{} @@ -69,27 +83,20 @@ func NewTracerProvider(opts ...TracerProviderOption) *TracerProvider { opt(o) } + ensureValidTracerProviderConfig(o) + tp := &TracerProvider{ namedTracer: make(map[instrumentation.Library]*tracer), + sampler: o.sampler, + idGenerator: o.idGenerator, + spanLimits: o.spanLimits, + resource: o.resource, } - tp.config.Store(&Config{ - DefaultSampler: ParentBased(AlwaysSample()), - IDGenerator: defaultIDGenerator(), - SpanLimits: SpanLimits{ - AttributeCountLimit: DefaultAttributeCountLimit, - EventCountLimit: DefaultEventCountLimit, - LinkCountLimit: DefaultLinkCountLimit, - AttributePerEventCountLimit: DefaultAttributePerEventCountLimit, - AttributePerLinkCountLimit: DefaultAttributePerLinkCountLimit, - }, - }) for _, sp := range o.processors { tp.RegisterSpanProcessor(sp) } - tp.ApplyConfig(o.config) - return tp } @@ -175,40 +182,6 @@ func (p *TracerProvider) UnregisterSpanProcessor(s SpanProcessor) { p.spanProcessors.Store(spss) } -// ApplyConfig changes the configuration of the provider. -// If a field in the configuration is empty or nil then its original value is preserved. -func (p *TracerProvider) ApplyConfig(cfg Config) { - p.mu.Lock() - defer p.mu.Unlock() - c := *p.config.Load().(*Config) - if cfg.DefaultSampler != nil { - c.DefaultSampler = cfg.DefaultSampler - } - if cfg.IDGenerator != nil { - c.IDGenerator = cfg.IDGenerator - } - if cfg.SpanLimits.EventCountLimit > 0 { - c.SpanLimits.EventCountLimit = cfg.SpanLimits.EventCountLimit - } - if cfg.SpanLimits.AttributeCountLimit > 0 { - c.SpanLimits.AttributeCountLimit = cfg.SpanLimits.AttributeCountLimit - } - if cfg.SpanLimits.LinkCountLimit > 0 { - c.SpanLimits.LinkCountLimit = cfg.SpanLimits.LinkCountLimit - } - if cfg.SpanLimits.AttributePerEventCountLimit > 0 { - c.SpanLimits.AttributePerEventCountLimit = cfg.SpanLimits.AttributePerEventCountLimit - } - if cfg.SpanLimits.AttributePerLinkCountLimit > 0 { - c.SpanLimits.AttributePerLinkCountLimit = cfg.SpanLimits.AttributePerLinkCountLimit - } - c.Resource = cfg.Resource - if c.Resource == nil { - c.Resource = resource.Default() - } - p.config.Store(&c) -} - // ForceFlush immediately exports all spans that have not yet been exported for // all the registered span processors. func (p *TracerProvider) ForceFlush(ctx context.Context) error { @@ -290,7 +263,9 @@ func WithSpanProcessor(sp SpanProcessor) TracerProviderOption { // resource.Default() Resource by default. func WithResource(r *resource.Resource) TracerProviderOption { return func(opts *TracerProviderConfig) { - opts.config.Resource = r + if r != nil { + opts.resource = r + } } } @@ -303,7 +278,9 @@ func WithResource(r *resource.Resource) TracerProviderOption { // IDGenerator by default. func WithIDGenerator(g IDGenerator) TracerProviderOption { return func(opts *TracerProviderConfig) { - opts.config.IDGenerator = g + if g != nil { + opts.idGenerator = g + } } } @@ -316,7 +293,9 @@ func WithIDGenerator(g IDGenerator) TracerProviderOption { // ParentBased(AlwaysSample) Sampler by default. func WithSampler(s Sampler) TracerProviderOption { return func(opts *TracerProviderConfig) { - opts.config.DefaultSampler = s + if s != nil { + opts.sampler = s + } } } @@ -329,6 +308,20 @@ func WithSampler(s Sampler) TracerProviderOption { // SpanLimits. func WithSpanLimits(sl SpanLimits) TracerProviderOption { return func(opts *TracerProviderConfig) { - opts.config.SpanLimits = sl + opts.spanLimits = sl + } +} + +// ensureValidTracerProviderConfig ensures that given TracerProviderConfig is valid. +func ensureValidTracerProviderConfig(cfg *TracerProviderConfig) { + if cfg.sampler == nil { + cfg.sampler = ParentBased(AlwaysSample()) + } + if cfg.idGenerator == nil { + cfg.idGenerator = defaultIDGenerator() + } + cfg.spanLimits.ensureDefault() + if cfg.resource == nil { + cfg.resource = resource.Default() } } diff --git a/sdk/trace/span.go b/sdk/trace/span.go index e303ca22b969..4710cd9ed0fd 100644 --- a/sdk/trace/span.go +++ b/sdk/trace/span.go @@ -521,18 +521,18 @@ func (*span) private() {} func startSpanInternal(ctx context.Context, tr *tracer, name string, parent trace.SpanContext, remoteParent bool, o *trace.SpanConfig) *span { span := &span{} - cfg := tr.provider.config.Load().(*Config) + provider := tr.provider var tid trace.TraceID var sid trace.SpanID if hasEmptySpanContext(parent) { // Generate both TraceID and SpanID - tid, sid = cfg.IDGenerator.NewIDs(ctx) + tid, sid = provider.idGenerator.NewIDs(ctx) } else { // TraceID already exists, just generate a SpanID tid = parent.TraceID() - sid = cfg.IDGenerator.NewSpanID(ctx, tid) + sid = provider.idGenerator.NewSpanID(ctx, tid) } span.spanContext = trace.NewSpanContext(trace.SpanContextConfig{ @@ -542,17 +542,18 @@ func startSpanInternal(ctx context.Context, tr *tracer, name string, parent trac TraceState: parent.TraceState(), }) - span.attributes = newAttributesMap(cfg.SpanLimits.AttributeCountLimit) - span.messageEvents = newEvictedQueue(cfg.SpanLimits.EventCountLimit) - span.links = newEvictedQueue(cfg.SpanLimits.LinkCountLimit) - span.spanLimits = cfg.SpanLimits + spanLimits := provider.spanLimits + span.attributes = newAttributesMap(spanLimits.AttributeCountLimit) + span.messageEvents = newEvictedQueue(spanLimits.EventCountLimit) + span.links = newEvictedQueue(spanLimits.LinkCountLimit) + span.spanLimits = spanLimits data := samplingData{ noParent: hasEmptySpanContext(parent), remoteParent: remoteParent, parent: parent, name: name, - cfg: cfg, + sampler: provider.sampler, span: span, attributes: o.Attributes, links: o.Links, @@ -579,7 +580,7 @@ func startSpanInternal(ctx context.Context, tr *tracer, name string, parent trac span.spanKind = trace.ValidateSpanKind(o.SpanKind) span.name = name span.hasRemoteParent = remoteParent - span.resource = cfg.Resource + span.resource = provider.resource span.instrumentationLibrary = tr.instrumentationLibrary span.SetAttributes(samplingResult.Attributes...) @@ -598,7 +599,7 @@ type samplingData struct { remoteParent bool parent trace.SpanContext name string - cfg *Config + sampler Sampler span *span attributes []attribute.KeyValue links []trace.Link @@ -606,8 +607,7 @@ type samplingData struct { } func makeSamplingDecision(data samplingData) SamplingResult { - sampler := data.cfg.DefaultSampler - return sampler.ShouldSample(SamplingParameters{ + return data.sampler.ShouldSample(SamplingParameters{ ParentContext: data.parent, TraceID: data.span.spanContext.TraceID(), Name: data.name, diff --git a/sdk/trace/trace_test.go b/sdk/trace/trace_test.go index ff4726bf5d68..1bb05b417f72 100644 --- a/sdk/trace/trace_test.go +++ b/sdk/trace/trace_test.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "math" + "strconv" "strings" "sync" "sync/atomic" @@ -1375,11 +1376,10 @@ func TestReadOnlySpan(t *testing.T) { kv := attribute.String("foo", "bar") tp := NewTracerProvider(WithResource(resource.NewWithAttributes(kv))) - cfg := tp.config.Load().(*Config) tr := tp.Tracer("ReadOnlySpan", trace.WithInstrumentationVersion("3")) // Initialize parent context. - tID, sID := cfg.IDGenerator.NewIDs(context.Background()) + tID, sID := tp.idGenerator.NewIDs(context.Background()) parent := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tID, SpanID: sID, @@ -1389,7 +1389,7 @@ func TestReadOnlySpan(t *testing.T) { ctx := trace.ContextWithRemoteSpanContext(context.Background(), parent) // Initialize linked context. - tID, sID = cfg.IDGenerator.NewIDs(context.Background()) + tID, sID = tp.idGenerator.NewIDs(context.Background()) linked := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tID, SpanID: sID, @@ -1456,11 +1456,10 @@ func TestReadOnlySpan(t *testing.T) { func TestReadWriteSpan(t *testing.T) { tp := NewTracerProvider(WithResource(resource.Empty())) - cfg := tp.config.Load().(*Config) tr := tp.Tracer("ReadWriteSpan") // Initialize parent context. - tID, sID := cfg.IDGenerator.NewIDs(context.Background()) + tID, sID := tp.idGenerator.NewIDs(context.Background()) parent := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tID, SpanID: sID, @@ -1742,3 +1741,51 @@ func TestSamplerTraceState(t *testing.T) { } } + +type testIDGenerator struct { + traceID int + spanID int +} + +func (gen *testIDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trace.SpanID) { + traceIDHex := fmt.Sprintf("%032x", gen.traceID) + traceID, _ := trace.TraceIDFromHex(traceIDHex) + gen.traceID++ + + spanID := gen.NewSpanID(ctx, traceID) + return traceID, spanID +} + +func (gen *testIDGenerator) NewSpanID(ctx context.Context, traceID trace.TraceID) trace.SpanID { + spanIDHex := fmt.Sprintf("%016x", gen.spanID) + spanID, _ := trace.SpanIDFromHex(spanIDHex) + gen.spanID++ + return spanID +} + +var _ IDGenerator = (*testIDGenerator)(nil) + +func TestWithIDGenerator(t *testing.T) { + const ( + startTraceID = 1 + startSpanID = 1 + numSpan = 10 + ) + + gen := &testIDGenerator{traceID: startSpanID, spanID: startSpanID} + + for i := 0; i < numSpan; i++ { + te := NewTestExporter() + tp := NewTracerProvider( + WithSyncer(te), + WithIDGenerator(gen), + ) + span := startSpan(tp, "TestWithIDGenerator") + got, err := strconv.ParseUint(span.SpanContext().SpanID().String(), 16, 64) + require.NoError(t, err) + want := uint64(startSpanID + i) + assert.Equal(t, got, want) + _, err = endSpan(te, span) + require.NoError(t, err) + } +}