Skip to content

Conversation

@xrmx
Copy link
Contributor

@xrmx xrmx commented Dec 17, 2025

Description

Implement part of the tracing SDK spec to configure the tracers https://opentelemetry.io/docs/specs/otel/trace/sdk/#configuration

At the moment this adds helper in order to enable or disable a tracer after it has been created.

The spec in is development so attributes, helpers and classes are prefixed with underscore.

TODO:

  • hook into sdk configuration
  • add an example rule based configurator so that we can express the tracer filter by name there

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

  • tox

Does This PR Require a Contrib Repo Change?

  • Yes. - Link to PR:
  • No.

Checklist:

  • Followed the style guidelines of this project
  • Changelogs have been updated
  • Unit tests have been added
  • Documentation has been updated

@xrmx xrmx requested a review from a team as a code owner December 17, 2025 11:40
id_generator: Optional[IdGenerator] = None,
span_limits: Optional[SpanLimits] = None,
*,
_tracer_configurator: Optional[_TracerConfiguratorT] = None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the java thing I was mentioning: https://github.com/open-telemetry/opentelemetry-java/tree/main/api/incubator

I agree it might be overkill for this feature though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm not sure how this maps into Python. The implementation I've added here with adding a new kwarg prefixed with the underscore is more or less the same it would be the concrete implementation when using overload to provide the one with only stable parameters.



_TracerConfiguratorT = Callable[[InstrumentationScope], _TracerConfig]
_TracerConfiguratorRulesPredicateT = Callable[
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess a callable is not very declarative config friendly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xrmx xrmx moved this to Ready for review in @xrmx's Python PR digest Dec 24, 2025
return environ.get(OTEL_PYTHON_ID_GENERATOR, _DEFAULT_ID_GENERATOR)


def _get_tracer_configurator() -> str | None:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added the configuration via env var to match what we are doing with the others TraceProvider parameters, since this is in development I can drop it. For my use case I can just use _OTelSDKConfigurator._configure.

set_status_on_exception: bool = True,
) -> trace_api.Span:
if not self._is_enabled:
return INVALID_SPAN
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key behavior of tracer configurator is:

If a Tracer is disabled, it MUST behave equivalently to a No-op Tracer.".

(from the spec)

Where the behavior of a noop tracer is:

However, there is one important exception to this general rule, and that is related to propagation of a SpanContext: The API MUST return a non-recording Span with the SpanContext in the parent Context (whether explicitly given or implicit current). If the Span in the parent Context is already non-recording, it SHOULD be returned directly without instantiating a new Span. If the parent Context contains no Span, an empty non-recording Span MUST be returned instead (i.e., having a SpanContext with all-zero Span and Trace IDs, empty Tracestate, and unsampled TraceFlags). This means that a SpanContext that has been provided by a configured Propagator will be propagated through to any child span and ultimately also Inject, but that no new SpanContexts will be created.

Does INVALID_SPAN here follow this behavior? In the java implementation, we use the noop tracer when a tracer is disabled, which returns a non-recording span to propagator the span context here.

Copy link
Contributor Author

@xrmx xrmx Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NoOpTracer in Python has the very same behavior of returning INVALID_SPAN, that is a NonRecordingSpan:

INVALID_SPAN = NonRecordingSpan(INVALID_SPAN_CONTEXT)

That may not match what the spec says though. Thanks for the review.

Copy link
Member

@aabmass aabmass Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the point here that the INVALID_SPAN from a disabled tracer will break trace propagation/parenting for any spans created under the disabled tracer's span?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Well, that's what I assume without looking deeply at the python code.

Suppose you have a trace:

-> (root) span A, from scope A
|-> span B, from scope B
|-> span C, from scope C

If I disable scope B, then I should see:

-> (root) span A, from scope A
|-> span C, from scope C

But unless I'm misunderstanding, in python the behavior is something like:

-> (root) span A, from scope A
-> span C, from scope C

Where span C's parent is an invalid span, and so results in a broken trace.

Please correct me if I'm misunderstanding.

Copy link
Contributor Author

@xrmx xrmx Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the NoOpTracer and the not enabled sdk tracer to not return an invalid span but a NonRecordingSpan with the proper context. Could use more tests I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed the part of reusing the very same NonRecordingSpan, updated and added more tests.

@xrmx xrmx force-pushed the experimental-tracer-configurator branch from b0e8ccb to 9726fec Compare January 22, 2026 10:08
]


def _tracer_name_matches_glob(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we'll want to add configurations for meters and loggers maybe we can make this more general and move it somewhere else to just be:

def _scope_name_matches_glob(
    glob_pattern: str,
) -> _InstrumentationScopePredicateT:
    def inner(scope: InstrumentationScope) -> bool:
        return fnmatch.fnmatch(scope.name, glob_pattern)
    return inner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Ready for review

Development

Successfully merging this pull request may close these issues.

5 participants