Skip to content

No tracer provider in the OpenTelemetrySdk builder makes the application do a lot of work for spans that will be dropped #8740

Description

@JnRouvignac

Describe the bug

When the tracer provider is not set in the OpenTelemetrySdk builder, spans claim they are recorded, but there is no exporter defined for them.
Therefore, despite appropriately guarding the attribute creation code with if (!span.isRecording()), an application can waste resources generating spans with attributes that will never be exported anyway.

Steps to reproduce
This reduced test case shows the problem:

    @Test
    public void shouldNotEmitSpansWhenTracingDisabledButLoggingEnabled2() {
        // Given
        OpenTelemetry openTelemetry = OpenTelemetrySdk.builder().setTracerProvider(null).build();
        // Same as OpenTelemetry openTelemetry = OpenTelemetrySdk.builder().build();

        // When
        Span span = openTelemetry.getTracer("test").spanBuilder("test-span").startSpan();

        // Then
        assertThat(span.isRecording()).isFalse(); // Fails!
    }

Of course our setup code is a bit more complicated: we have a real logger provider, but no tracer provider, and we ended up passing null for the tracer provider.

What did you expect to see?
In the test above, the assertion assertThat(span.isRecording()).isFalse(); should pass. As a result, all the application span code would be a noop.

What did you see instead?
In the test above, the assertion assertThat(span.isRecording()).isFalse(); does not pass. As a result, all the application span code does a lot of useless work.

Workaround
First attempt:
Do the following instead:

SdkTracerProvider noopTracer = SdkTracerProvider.builder().setSampler(alwaysOff()).build();
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder().setTracerProvider(noopTracer).build();

But even that is not optimal, it will run through a lot of code on this line:
openTelemetry.getTracer("test").spanBuilder("test-span").startSpan();

I was hoping I could find a way to setSdkTracer#tracerEnabled to false, but I could not.
I wanted to override SdkTracerProviderBuilder#tracerConfiguratorBuilder, but there is no public API to do so either.

Second attempt:
In the end, the best workaround I found is the following:

SdkTracerProvider noopTracer = SdkTracerProvider.builder().build();
noopTracer.close();
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder().setTracerProvider(noopTracer).build();

This ensures we take one of the fast paths in io.opentelemetry.sdk.trace.SdkTracer#spanBuilder, which returns a NoopSpanBuilder, and then the call to io.opentelemetry.api.trace.SpanBuilder.startSpan does as little as possible, and the returned io.opentelemetry.api.trace.PropagatedSpan isRecording() method returns false.
Which what we want.

Conclusion:
To cut a long story short, the default is surprising, and making it to behave as noop required a surprising amount of work, and it is a bit "heavy". i.e. doing the "right thing" in this case is not exactly trivial.

What version and what artifacts are you using?
io.opentelemetry:opentelemetry-sdk-trace:1.62.0

Environment
Compiler: Amazon Correto 17.0.19
OS: Ubuntu 24.04.4 LTS

Additional context
n/a

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions