-
Notifications
You must be signed in to change notification settings - Fork 778
opentelemetry-sdk: Implement tracer configurator #4861
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e5d7765
6e23261
c4578ac
787e2ef
6d212bf
9b4a4b5
cac3f64
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |
| import threading | ||
| import traceback | ||
| import typing | ||
| from dataclasses import dataclass | ||
| from os import environ | ||
| from time import time_ns | ||
| from types import MappingProxyType, TracebackType | ||
|
|
@@ -39,6 +40,7 @@ | |
| Union, | ||
| ) | ||
| from warnings import filterwarnings | ||
| from weakref import WeakSet | ||
|
|
||
| from typing_extensions import deprecated | ||
|
|
||
|
|
@@ -71,7 +73,7 @@ | |
| EXCEPTION_STACKTRACE, | ||
| EXCEPTION_TYPE, | ||
| ) | ||
| from opentelemetry.trace import NoOpTracer, SpanContext | ||
| from opentelemetry.trace import INVALID_SPAN, NoOpTracer, SpanContext | ||
| from opentelemetry.trace.status import Status, StatusCode | ||
| from opentelemetry.util import types | ||
| from opentelemetry.util._decorator import _agnosticcontextmanager | ||
|
|
@@ -292,7 +294,7 @@ def force_flush(self, timeout_millis: int = 30000) -> bool: | |
| timeout, False otherwise. | ||
| """ | ||
| futures = [] | ||
| for sp in self._span_processors: # type: SpanProcessor | ||
| for sp in self._span_processors: | ||
| future = self._executor.submit(sp.force_flush, timeout_millis) | ||
| futures.append(future) | ||
|
|
||
|
|
@@ -1071,6 +1073,11 @@ class _Span(Span): | |
| """ | ||
|
|
||
|
|
||
| @dataclass | ||
| class _TracerConfig: | ||
| is_enabled: bool | ||
|
|
||
|
|
||
| class Tracer(trace_api.Tracer): | ||
| """See `opentelemetry.trace.Tracer`.""" | ||
|
|
||
|
|
@@ -1085,6 +1092,8 @@ def __init__( | |
| instrumentation_info: InstrumentationInfo, | ||
| span_limits: SpanLimits, | ||
| instrumentation_scope: InstrumentationScope, | ||
| *, | ||
| _tracer_config: Optional[_TracerConfig] = None, | ||
| ) -> None: | ||
| self.sampler = sampler | ||
| self.resource = resource | ||
|
|
@@ -1094,6 +1103,19 @@ def __init__( | |
| self._span_limits = span_limits | ||
| self._instrumentation_scope = instrumentation_scope | ||
|
|
||
| self._enabled = ( | ||
| _tracer_config.is_enabled if _tracer_config is not None else True | ||
| ) | ||
|
|
||
| def _update_tracer_config(self, tracer_config: _TracerConfig): | ||
| self._enabled = tracer_config.is_enabled | ||
|
|
||
| @property | ||
| def _is_enabled(self) -> bool: | ||
| """Instrumentations needs to call this API each time to check if they should | ||
| create a new span.""" | ||
| return self._enabled | ||
|
|
||
| @_agnosticcontextmanager # pylint: disable=protected-access | ||
| def start_as_current_span( | ||
| self, | ||
|
|
@@ -1136,6 +1158,9 @@ def start_span( # pylint: disable=too-many-locals | |
| record_exception: bool = True, | ||
| set_status_on_exception: bool = True, | ||
| ) -> trace_api.Span: | ||
| if not self._is_enabled: | ||
| return INVALID_SPAN | ||
|
|
||
| parent_span_context = trace_api.get_current_span( | ||
| context | ||
| ).get_span_context() | ||
|
|
@@ -1202,6 +1227,51 @@ def start_span( # pylint: disable=too-many-locals | |
| return span | ||
|
|
||
|
|
||
| _TracerConfiguratorT = Callable[[InstrumentationScope], _TracerConfig] | ||
| _TracerConfiguratorRulesPredicateT = Callable[ | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess a callable is not very declarative config friendly |
||
| [Optional[InstrumentationScope]], bool | ||
| ] | ||
| _TracerConfiguratorRulesT = Sequence[ | ||
| Tuple[_TracerConfiguratorRulesPredicateT, _TracerConfig] | ||
| ] | ||
|
|
||
|
|
||
| class _RuleBaseTracerConfigurator: | ||
| def __init__(self, *, rules: _TracerConfiguratorRulesT): | ||
| self._rules = rules | ||
|
|
||
| def __call__( | ||
| self, tracer_scope: Optional[InstrumentationScope] = None | ||
| ) -> _TracerConfig: | ||
| for predicate, tracer_config in self._rules: | ||
| if predicate(tracer_scope): | ||
| return tracer_config | ||
|
|
||
| # if no rule matched return a default one | ||
| return _TracerConfig(is_enabled=True) | ||
|
|
||
|
|
||
| def _default_tracer_configurator( | ||
| tracer_scope: InstrumentationScope, | ||
| ) -> _TracerConfig: | ||
| """Default Tracer Configurator implementation | ||
| In order to update Tracers configs you need to call | ||
| TracerProvider._set_tracer_configurator with a function | ||
| implementing this interface returning a Tracer Config.""" | ||
| return _RuleBaseTracerConfigurator( | ||
| rules=[(lambda x: True, _TracerConfig(is_enabled=True))], | ||
| )(tracer_scope=tracer_scope) | ||
|
|
||
|
|
||
| def _disable_tracer_configurator( | ||
| tracer_scope: InstrumentationScope, | ||
| ) -> _TracerConfig: | ||
| return _RuleBaseTracerConfigurator( | ||
| rules=[(lambda x: True, _TracerConfig(is_enabled=False))], | ||
| )(tracer_scope=tracer_scope) | ||
|
|
||
|
|
||
| class TracerProvider(trace_api.TracerProvider): | ||
| """See `opentelemetry.trace.TracerProvider`.""" | ||
|
|
||
|
|
@@ -1215,6 +1285,8 @@ def __init__( | |
| ] = None, | ||
| id_generator: Optional[IdGenerator] = None, | ||
| span_limits: Optional[SpanLimits] = None, | ||
| *, | ||
| _tracer_configurator: Optional[_TracerConfiguratorT] = None, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| ) -> None: | ||
| self._active_span_processor = ( | ||
| active_span_processor or SynchronousMultiSpanProcessor() | ||
|
|
@@ -1238,6 +1310,37 @@ def __init__( | |
| if shutdown_on_exit: | ||
| self._atexit_handler = atexit.register(self.shutdown) | ||
|
|
||
| self._tracer_configurator = ( | ||
| _tracer_configurator or _default_tracer_configurator | ||
| ) | ||
| self._cached_tracers: WeakSet[Tracer] = WeakSet() | ||
|
|
||
| def _set_tracer_configurator( | ||
| self, *, tracer_configurator: _TracerConfiguratorT | ||
| ): | ||
| self._tracer_configurator = tracer_configurator | ||
| self._update_tracers(tracer_configurator=tracer_configurator) | ||
|
|
||
| def _update_tracers( | ||
| self, | ||
| *, | ||
| tracer_configurator: _TracerConfiguratorT, | ||
| ): | ||
| # pylint: disable=protected-access | ||
| for tracer in self._cached_tracers: | ||
| tracer_config = tracer_configurator(tracer._instrumentation_scope) | ||
| tracer._update_tracer_config(tracer_config) | ||
|
|
||
| def _enable_tracers(self): | ||
| self._update_tracers( | ||
| tracer_configurator=_default_tracer_configurator, | ||
| ) | ||
|
|
||
| def _disable_tracers(self): | ||
| self._update_tracers( | ||
| tracer_configurator=_disable_tracer_configurator, | ||
| ) | ||
|
|
||
| @property | ||
| def resource(self) -> Resource: | ||
| return self._resource | ||
|
|
@@ -1272,21 +1375,30 @@ def get_tracer( | |
| schema_url, | ||
| ) | ||
|
|
||
| return Tracer( | ||
| instrumentation_scope = InstrumentationScope( | ||
| instrumenting_module_name, | ||
| instrumenting_library_version, | ||
| schema_url, | ||
| attributes, | ||
| ) | ||
|
|
||
| tracer_config = self._tracer_configurator(instrumentation_scope) | ||
|
|
||
| tracer = Tracer( | ||
| self.sampler, | ||
| self.resource, | ||
| self._active_span_processor, | ||
| self.id_generator, | ||
| instrumentation_info, | ||
| self._span_limits, | ||
| InstrumentationScope( | ||
| instrumenting_module_name, | ||
| instrumenting_library_version, | ||
| schema_url, | ||
| attributes, | ||
| ), | ||
| instrumentation_scope, | ||
| _tracer_config=tracer_config, | ||
| ) | ||
|
|
||
| self._cached_tracers.add(tracer) | ||
|
|
||
| return tracer | ||
|
|
||
| def add_span_processor(self, span_processor: SpanProcessor) -> None: | ||
| """Registers a new :class:`SpanProcessor` for this `TracerProvider`. | ||
|
|
||
There was a problem hiding this comment.
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.