You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
No tracer provider in the OpenTelemetrySdk builder makes the application do a lot of work for spans that will be dropped #8740
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:
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:
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:
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.PropagatedSpanisRecording() 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
Thanks I will try it out and report back.
The only thing I was wondering is: without tracing provider, the log lines won't get any traceId and spanId.
Is there a use case for having traceId and spanId on log lines but without exporting traces? In theory one could correlate log lines through distributed applications without incurring the higher cost of storing traces, and in effect I know some companies who did this successfully for years.
WDYT?
It's at least one use case worth keeping maybe.
Thanks I will try it out and report back. The only thing I was wondering is: without tracing provider, the log lines won't get any traceId and spanId.
Is there a use case for having traceId and spanId on log lines but without exporting traces? In theory one could correlate log lines through distributed applications without incurring the higher cost of storing traces, and in effect I know some companies who did this successfully for years.
WDYT? It's at least one use case worth keeping maybe.
A tracer provider with a no-op exporter will still generate spans, whose ids will get injected into logs, as appropriate.
Thanks I will try it out and report back. The only thing I was wondering is: without tracing provider, the log lines won't get any traceId and spanId.
Is there a use case for having traceId and spanId on log lines but without exporting traces? In theory one could correlate log lines through distributed applications without incurring the higher cost of storing traces, and in effect I know some companies who did this successfully for years.
WDYT? It's at least one use case worth keeping maybe.
A tracer provider with a no-op exporter will still generate spans, whose ids will get injected into logs, as appropriate.
Yes, in that case one should probably use an always-on sampler to get traceId / spanId on all the logs.
Then for that case, one should have:
Duh! I should have read the javadoc 🤦
It solves the problem reported in the issue description indeed, but it's still quite a bit of code though.
Since this is an incubating API, we won't use it in for the moment. SdkTracerProvider.close() is good enough for us right now.
Also, I am not sure how to achieve #8740 (comment) though? But this is not really a use case I need right now, so I don't really need an answer here. It's more like somebody else's use case at the minute. 😄
@jack-berg@JnRouvignac Hi! I’d like to work on this issue if it’s still available. I’ve gone through the discussion and would be happy to investigate the current default tracer-provider behavior, add a regression test, and propose a minimal fix. Please let me know if there’s anything I should keep in mind before starting.
@jack-berg, I think the fix for this issue might have introduced a breaking change.
When we now set: otel.traces.exporter=none
There will be:
No trace_id / span_id in logs
No Trace context propagated to downstream services
I think we can achieve the same behavior by setting: otel.traces.sampler=always_off
However, this will produce non recording spans.
none should be different from no-op and probably have different configs.
Hmm I knew that this would change behavior, but considered it a bug fix.
none should be different from no-op and probably have different configs.
How should you specify no op vs. non-span context generating? As far as I can tell, the spec is ambiguous about:
How you indicate that a noop implementation should be used for TracerProvider, MeterProvider, LoggerProvider?OTEL_SDK_DISABLED=true disable all but how do you select for a specific signal?
What should happen if no processors (and therefore no exporters) are registered? Span creation spec doesn't mention this scenario. Tracer.enabled operation should return false when no processors are registered, which results in instrumentation skipping calling span start entirely in this case. So arguably, disabling span context creation when no span processors are registered is most consistent since if not, the existence of span context would depend on whether or not instrumentation calls Tracer.enabled.
Is there a workaround? Are non recording spans a problem? It seems accurate in this case.
How should you specify no op vs. non-span context generating?
The way we can do it in Quarkus is through *.trace.enabled and *.exporter.*.enabled configs but that doesn't exist here in upstream.
I think the spec says a disabled tracer should be no-op but I think we have no config to express that... This what the current main branch behavior achieves when setting otel.traces.exporter=none. otel.traces.exporter=none should only apply to exporters, leaving the remaining tracer functionality enabled. #8815 effectively collapsed the tracer and exporter configs.
Either we add a new tracer config or we can chose a less intrusive option like use the otel.traces.exporter and keep the none as before but introduce a noop (or some other name) that would not generate any spans, like the behavior in current main.
otel.traces.exporter=none should only apply to exporters, leaving the remaining tracer functionality enabled.
Problem is:
SdkMeterProvider and SdkLoggerProvider already behaved like this, so SdkTracerProvider was the exception, which I why I treated it as a bug fix despite the behavior change.
Not everyone agrees that a tracer provider should keep producing trace context when there is nothing configured to process the data.
Either we add a new tracer config or we can chose a less intrusive option like use the otel.traces.exporter and keep the none as before but introduce a noop (or some other name) that would not generate any spans, like the behavior in current main.
Attaching a noop behavior to otel.traces.exporter=none actually seems appropriate to me. If there is no exporter, then there is no processor and if there's no processor then there's no point in processing the data. Hence noop. But I understand the desire to have an escape hatch and keep producing trace context. Idea: We have a package private noop exporters built in like NoopSpanExporter. I wonder if we should attach a special behavior to otel.traces.exporter=noop. This would instruct configuration to wire in a processor configured with a noop exporter, and trace context is still created. So:
Across all signals, if there is nothing configured to process the data, we configure a noop implementation.
Anything configured to process the data, even a simple span processor which calls a noop exporter, retains proper implementation.
Activity
jkwatson commentedon Aug 24, 2026
how about just using
TracerProvider.noop()?JnRouvignac commentedon Aug 25, 2026
I tried that, but it does not work because these are different types:
TracerProvider.noop()returns aio.opentelemetry.api.trace.TracerProviderio.opentelemetry.sdk.OpenTelemetrySdkBuilder#setTracerProviderexpects aio.opentelemetry.sdk.SdkTracerProviderjack-berg commentedon Aug 25, 2026
Yeah I agree we can and should do better than this.
You can get the behavior your desire using this experimental feature called scope config, which allows you to select/disable specific tracers:
But an OpenTelemetrySdk instance with no SdkTracerProvider configured should have the behavior you discuss out of the box.
JnRouvignac commentedon Aug 25, 2026
Thanks I will try it out and report back.
The only thing I was wondering is: without tracing provider, the log lines won't get any traceId and spanId.
Is there a use case for having traceId and spanId on log lines but without exporting traces? In theory one could correlate log lines through distributed applications without incurring the higher cost of storing traces, and in effect I know some companies who did this successfully for years.
WDYT?
It's at least one use case worth keeping maybe.
jkwatson commentedon Aug 25, 2026
A tracer provider with a no-op exporter will still generate spans, whose ids will get injected into logs, as appropriate.
JnRouvignac commentedon Aug 26, 2026
Yes, in that case one should probably use an always-on sampler to get traceId / spanId on all the logs.
Then for that case, one should have:
JnRouvignac commentedon Aug 26, 2026
Duh! I should have read the javadoc 🤦
It solves the problem reported in the issue description indeed, but it's still quite a bit of code though.
Since this is an incubating API, we won't use it in for the moment.
SdkTracerProvider.close()is good enough for us right now.Also, I am not sure how to achieve #8740 (comment) though? But this is not really a use case I need right now, so I don't really need an answer here. It's more like somebody else's use case at the minute. 😄
Thank you!
Debashismitra01 commentedon Sep 3, 2026
@jack-berg @JnRouvignac Hi! I’d like to work on this issue if it’s still available. I’ve gone through the discussion and would be happy to investigate the current default tracer-provider behavior, add a regression test, and propose a minimal fix. Please let me know if there’s anything I should keep in mind before starting.
Update SdkTracerProviderTest.java
brunobat commentedon Nov 3, 2026
@jack-berg, I think the fix for this issue might have introduced a breaking change.
When we now set:
otel.traces.exporter=noneThere will be:
I think we can achieve the same behavior by setting:
otel.traces.sampler=always_offHowever, this will produce non recording spans.
noneshould be different from no-op and probably have different configs.jack-berg commentedon Nov 3, 2026
Hmm I knew that this would change behavior, but considered it a bug fix.
How should you specify no op vs. non-span context generating? As far as I can tell, the spec is ambiguous about:
Is there a workaround? Are non recording spans a problem? It seems accurate in this case.
brunobat commentedon Nov 3, 2026
The way we can do it in Quarkus is through
*.trace.enabledand*.exporter.*.enabledconfigs but that doesn't exist here in upstream.I think the spec says a disabled tracer should be no-op but I think we have no config to express that... This what the current main branch behavior achieves when setting
otel.traces.exporter=none.otel.traces.exporter=noneshould only apply to exporters, leaving the remaining tracer functionality enabled.#8815 effectively collapsed the tracer and exporter configs.
Either we add a new tracer config or we can chose a less intrusive option like use the
otel.traces.exporterand keep thenoneas before but introduce anoop(or some other name) that would not generate any spans, like the behavior in current main.jack-berg commentedon Nov 3, 2026
Problem is:
Attaching a noop behavior to
otel.traces.exporter=noneactually seems appropriate to me. If there is no exporter, then there is no processor and if there's no processor then there's no point in processing the data. Hence noop. But I understand the desire to have an escape hatch and keep producing trace context. Idea: We have a package private noop exporters built in like NoopSpanExporter. I wonder if we should attach a special behavior tootel.traces.exporter=noop. This would instruct configuration to wire in a processor configured with a noop exporter, and trace context is still created. So: