diff --git a/CHANGELOG.md b/CHANGELOG.md index e3091794e0b..fc75c20de41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4798](https://github.com/open-telemetry/opentelemetry-python/pull/4798)) - Silence events API warnings for internal users ([#4847](https://github.com/open-telemetry/opentelemetry-python/pull/4847)) +- Implement experimental TracerConfigurator + ([#4861](https://github.com/open-telemetry/opentelemetry-python/pull/4861)) ## Version 1.39.0/0.60b0 (2025-12-03) diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py index 529c73989c8..a23bbe8b801 100644 --- a/opentelemetry-api/src/opentelemetry/trace/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py @@ -471,7 +471,15 @@ def start_span( record_exception: bool = True, set_status_on_exception: bool = True, ) -> "Span": - return INVALID_SPAN + parent_span_context = get_current_span(context).get_span_context() + if parent_span_context is not None and not isinstance( + parent_span_context, SpanContext + ): + raise TypeError( + "parent_span_context must be a SpanContext or None." + ) + + return NonRecordingSpan(context=parent_span_context) @_agnosticcontextmanager def start_as_current_span( @@ -486,7 +494,23 @@ def start_as_current_span( set_status_on_exception: bool = True, end_on_exit: bool = True, ) -> Iterator["Span"]: - yield INVALID_SPAN + span = self.start_span( + name=name, + context=context, + kind=kind, + attributes=attributes, + links=links, + start_time=start_time, + record_exception=record_exception, + set_status_on_exception=set_status_on_exception, + ) + with use_span( + span, + end_on_exit=end_on_exit, + record_exception=record_exception, + set_status_on_exception=set_status_on_exception, + ) as span: + yield span @deprecated("You should use NoOpTracer. Deprecated since version 1.9.0.") diff --git a/opentelemetry-api/tests/test_implementation.py b/opentelemetry-api/tests/test_implementation.py index 913efbffb3b..4747fa2d9ef 100644 --- a/opentelemetry-api/tests/test_implementation.py +++ b/opentelemetry-api/tests/test_implementation.py @@ -36,18 +36,29 @@ def test_default_tracer(self): tracer_provider = trace.NoOpTracerProvider() tracer = tracer_provider.get_tracer(__name__) with tracer.start_span("test") as span: - self.assertEqual( - span.get_span_context(), trace.INVALID_SPAN_CONTEXT - ) - self.assertEqual(span, trace.INVALID_SPAN) + self.assertFalse(span.get_span_context().is_valid) self.assertIs(span.is_recording(), False) with tracer.start_span("test2") as span2: - self.assertEqual( - span2.get_span_context(), trace.INVALID_SPAN_CONTEXT - ) - self.assertEqual(span2, trace.INVALID_SPAN) + self.assertFalse(span2.get_span_context().is_valid) self.assertIs(span2.is_recording(), False) + def test_default_tracer_context_propagation(self): + tracer_provider = trace.NoOpTracerProvider() + tracer = tracer_provider.get_tracer(__name__) + ctx = trace.set_span_in_context( + trace.NonRecordingSpan( + trace.SpanContext( + 2604504634922341076776623263868986797, + 5213367945872657620, + False, + trace.TraceFlags(0x01), + ) + ) + ) + with tracer.start_span("test", context=ctx) as span: + self.assertTrue(span.get_span_context().is_valid) + self.assertIs(span.is_recording(), False) + def test_span(self): with self.assertRaises(TypeError): # pylint: disable=abstract-class-instantiated diff --git a/opentelemetry-api/tests/trace/test_proxy.py b/opentelemetry-api/tests/trace/test_proxy.py index caf847777cf..ad969e76617 100644 --- a/opentelemetry-api/tests/trace/test_proxy.py +++ b/opentelemetry-api/tests/trace/test_proxy.py @@ -96,8 +96,8 @@ def my_function() -> Span: return trace.get_current_span() # call function before configuring tracing provider, should - # return INVALID_SPAN from the NoOpTracer - self.assertEqual(my_function(), trace.INVALID_SPAN) + # return NonRecordingSpan from the NoOpTracer + self.assertFalse(my_function().is_recording()) # configure tracing provider trace.set_tracer_provider(TestProvider()) diff --git a/opentelemetry-api/tests/trace/test_tracer.py b/opentelemetry-api/tests/trace/test_tracer.py index fae836d564f..632232e59ef 100644 --- a/opentelemetry-api/tests/trace/test_tracer.py +++ b/opentelemetry-api/tests/trace/test_tracer.py @@ -79,5 +79,5 @@ async def function_async(data: str) -> int: def test_get_current_span(self): with self.tracer.start_as_current_span("test") as span: get_current_span().set_attribute("test", "test") - self.assertEqual(span, INVALID_SPAN) + self.assertFalse(span.is_recording()) self.assertFalse(hasattr("span", "attributes")) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index 7c0d0468f80..39a5a1471f0 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -48,6 +48,7 @@ OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, + OTEL_PYTHON_TRACER_CONFIGURATOR, OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG, ) @@ -58,7 +59,7 @@ PeriodicExportingMetricReader, ) from opentelemetry.sdk.resources import Attributes, Resource -from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace import TracerProvider, _TracerConfiguratorT from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter from opentelemetry.sdk.trace.id_generator import IdGenerator from opentelemetry.sdk.trace.sampling import Sampler @@ -146,6 +147,10 @@ def _get_id_generator() -> str: return environ.get(OTEL_PYTHON_ID_GENERATOR, _DEFAULT_ID_GENERATOR) +def _get_tracer_configurator() -> str | None: + return environ.get(OTEL_PYTHON_TRACER_CONFIGURATOR, None) + + def _get_exporter_entry_point( exporter_name: str, signal_type: Literal["traces", "metrics", "logs"] ): @@ -210,11 +215,13 @@ def _init_tracing( sampler: Sampler | None = None, resource: Resource | None = None, exporter_args_map: ExporterArgsMap | None = None, + tracer_configurator: _TracerConfiguratorT | None = None, ): provider = TracerProvider( id_generator=id_generator, sampler=sampler, resource=resource, + _tracer_configurator=tracer_configurator, ) set_tracer_provider(provider) @@ -315,6 +322,27 @@ def overwritten_config_fn(*args, **kwargs): logging.basicConfig = wrapper(logging.basicConfig) +def _import_tracer_configurator( + tracer_configurator_name: str | None, +) -> _TracerConfiguratorT | None: + if not tracer_configurator_name: + return None + + try: + _, tracer_configurator_impl = _import_config_components( + [tracer_configurator_name.strip()], + "_opentelemetry_tracer_configurator", + )[0] + except Exception as exc: # pylint: disable=broad-exception-caught + _logger.warning( + "Using default tracer configurator. Failed to load tracer configurator, %s: %s", + tracer_configurator_name, + exc, + ) + return None + return tracer_configurator_impl + + def _import_exporters( trace_exporter_names: Sequence[str], metric_exporter_names: Sequence[str], @@ -429,7 +457,9 @@ def _initialize_components( id_generator: IdGenerator | None = None, setup_logging_handler: bool | None = None, exporter_args_map: ExporterArgsMap | None = None, + tracer_configurator: _TracerConfiguratorT | None = None, ): + # pylint: disable=too-many-locals if trace_exporter_names is None: trace_exporter_names = [] if metric_exporter_names is None: @@ -454,6 +484,12 @@ def _initialize_components( resource_attributes[ResourceAttributes.TELEMETRY_AUTO_VERSION] = ( # type: ignore[reportIndexIssue] auto_instrumentation_version ) + if tracer_configurator is None: + tracer_configurator_name = _get_tracer_configurator() + tracer_configurator = _import_tracer_configurator( + tracer_configurator_name + ) + # if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name # from the env variable else defaults to "unknown_service" resource = Resource.create(resource_attributes) @@ -464,6 +500,7 @@ def _initialize_components( sampler=sampler, resource=resource, exporter_args_map=exporter_args_map, + tracer_configurator=tracer_configurator, ) _init_metrics( metric_exporters, resource, exporter_args_map=exporter_args_map diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index 5baf5fcd55d..ae3959ef15e 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -866,3 +866,15 @@ def channel_credential_provider() -> grpc.ChannelCredentials: This is an experimental environment variable and the name of this variable and its behavior can change in a non-backwards compatible way. """ + +OTEL_PYTHON_TRACER_CONFIGURATOR = "OTEL_PYTHON_TRACER_CONFIGURATOR" +""" +.. envvar:: OTEL_PYTHON_TRACER_CONFIGURATOR + +The :envvar:`OTEL_PYTHON_TRACER_CONFIGURATOR` environment variable allows users to set a +custom Tracer Configurator function. +Default: opentelemetry.sdk.trace._default_tracer_configurator + +This is an experimental environment variable and the name of this variable and its behavior can +change in a non-backwards compatible way. +""" diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 0e7e1f6db3b..6c58362639d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -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 @@ -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, @@ -1147,6 +1169,9 @@ def start_span( # pylint: disable=too-many-locals "parent_span_context must be a SpanContext or None." ) + if not self._is_enabled: + return trace_api.NonRecordingSpan(context=parent_span_context) + # is_valid determines root span if parent_span_context is None or not parent_span_context.is_valid: parent_span_context = None @@ -1202,6 +1227,51 @@ def start_span( # pylint: disable=too-many-locals return span +_TracerConfiguratorT = Callable[[InstrumentationScope], _TracerConfig] +_TracerConfiguratorRulesPredicateT = Callable[ + [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, ) -> 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`. diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index 8edc9190dac..a747df195b8 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + # type: ignore # pylint: skip-file from __future__ import annotations @@ -35,10 +36,12 @@ _get_exporter_names, _get_id_generator, _get_sampler, + _get_tracer_configurator, _import_config_components, _import_exporters, _import_id_generator, _import_sampler, + _import_tracer_configurator, _init_logging, _init_metrics, _init_tracing, @@ -62,6 +65,7 @@ ) from opentelemetry.sdk.metrics.view import Aggregation from opentelemetry.sdk.resources import SERVICE_NAME, Resource +from opentelemetry.sdk.trace import _RuleBaseTracerConfigurator from opentelemetry.sdk.trace.export import ConsoleSpanExporter from opentelemetry.sdk.trace.id_generator import IdGenerator, RandomIdGenerator from opentelemetry.sdk.trace.sampling import ( @@ -79,10 +83,18 @@ class Provider: - def __init__(self, resource=None, sampler=None, id_generator=None): + def __init__( + self, + resource=None, + sampler=None, + id_generator=None, + *, + _tracer_configurator=None, + ): self.sampler = sampler self.id_generator = id_generator self.processor = None + self._tracer_configurator = _tracer_configurator self.resource = resource or Resource.create({}) def add_span_processor(self, processor): @@ -597,6 +609,52 @@ def verify_default_sampler(self, tracer_provider): # pylint: disable=protected-access self.assertEqual(tracer_provider.sampler._root, ALWAYS_ON) + @patch.dict( + "os.environ", + {"OTEL_PYTHON_TRACER_CONFIGURATOR": "non_existent_entry_point"}, + ) + def test_trace_init_custom_tracer_configurator_with_env_non_existent_entry_point( + self, + ): + tracer_configurator_name = _get_tracer_configurator() + with self.assertLogs(level=WARNING): + tracer_configurator = _import_tracer_configurator( + tracer_configurator_name + ) + _init_tracing({}, tracer_configurator=tracer_configurator) + + @patch("opentelemetry.sdk._configuration.entry_points") + @patch.dict( + "os.environ", + {"OTEL_PYTHON_TRACER_CONFIGURATOR": "custom_tracer_configurator"}, + ) + def test_trace_init_custom_tracer_configurator_with_env( + self, mock_entry_points + ): + def custom_tracer_configurator(tracer_scope): + return mock.Mock(spec=_RuleBaseTracerConfigurator)( + tracer_scope=tracer_scope + ) + + mock_entry_points.configure_mock( + return_value=[ + IterEntryPoint( + "custom_tracer_configurator", + custom_tracer_configurator, + ) + ] + ) + + tracer_configurator_name = _get_tracer_configurator() + tracer_configurator = _import_tracer_configurator( + tracer_configurator_name + ) + _init_tracing({}, tracer_configurator=tracer_configurator) + provider = self.set_provider_mock.call_args[0][0] + self.assertEqual( + provider._tracer_configurator, custom_tracer_configurator + ) + class TestLoggingInit(TestCase): def setUp(self): @@ -843,6 +901,7 @@ def test_initialize_components_kwargs( "id_generator": "TEST_GENERATOR", "setup_logging_handler": True, "exporter_args_map": {1: {"compression": "gzip"}}, + "tracer_configurator": "tracer_configurator_test", } _initialize_components(**kwargs) @@ -877,6 +936,7 @@ def test_initialize_components_kwargs( sampler="TEST_SAMPLER", resource="TEST_RESOURCE", exporter_args_map={1: {"compression": "gzip"}}, + tracer_configurator="tracer_configurator_test", ) metrics_mock.assert_called_once_with( "TEST_METRICS_EXPORTERS_DICT", diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index b83b000f4d1..4e3a0db9694 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -43,7 +43,12 @@ OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG, ) -from opentelemetry.sdk.trace import Resource, TracerProvider +from opentelemetry.sdk.trace import ( + Resource, + TracerProvider, + _RuleBaseTracerConfigurator, + _TracerConfig, +) from opentelemetry.sdk.trace.id_generator import RandomIdGenerator from opentelemetry.sdk.trace.sampling import ( ALWAYS_OFF, @@ -196,6 +201,45 @@ def test_get_tracer_with_sdk_disabled(self): tracer_provider.get_tracer(Mock()), trace_api.NoOpTracer ) + def test_update_tracer_config(self): + # pylint: disable=protected-access + tracer_provider = trace.TracerProvider() + tracer = tracer_provider.get_tracer( + "module_name", + "library_version", + "schema_url", + {}, + ) + + self.assertEqual(tracer._is_enabled, True) + + tracer_config = _TracerConfig(is_enabled=False) + tracer._update_tracer_config(tracer_config) + self.assertEqual(tracer._is_enabled, False) + + tracer_config = _TracerConfig(is_enabled=True) + tracer._update_tracer_config(tracer_config) + self.assertEqual(tracer._is_enabled, True) + + def test_start_span_returns_invalid_span_if_not_enabled(self): + # pylint: disable=protected-access + tracer_provider = trace.TracerProvider() + tracer = tracer_provider.get_tracer( + "module_name", + "library_version", + "schema_url", + {}, + ) + + self.assertEqual(tracer._is_enabled, True) + + tracer_config = _TracerConfig(is_enabled=False) + tracer._update_tracer_config(tracer_config) + self.assertEqual(tracer._is_enabled, False) + + span = tracer.start_span(name="invalid span") + self.assertFalse(span.is_recording()) + class TestTracerSampling(unittest.TestCase): def tearDown(self): @@ -2183,6 +2227,119 @@ def test_tracer_provider_init_default(self, resource_patch, sample_patch): self.assertIsNotNone(tracer_provider._span_limits) self.assertIsNotNone(tracer_provider._atexit_handler) + def test_default_tracer_configurator(self): + # pylint: disable=protected-access + tracer_provider = trace.TracerProvider() + tracer = tracer_provider.get_tracer( + "module_name", + "library_version", + "schema_url", + {}, + ) + other_tracer = tracer_provider.get_tracer( + "other_module_name", + "library_version", + "schema_url", + {}, + ) + self.assertEqual(tracer._instrumentation_scope.name, "module_name") + self.assertEqual( + other_tracer._instrumentation_scope.name, "other_module_name" + ) + + self.assertEqual(tracer._is_enabled, True) + self.assertEqual(other_tracer._is_enabled, True) + + tracer_provider._disable_tracers() + self.assertEqual(tracer._is_enabled, False) + self.assertEqual(other_tracer._is_enabled, False) + + tracer_provider._enable_tracers() + self.assertEqual(tracer._is_enabled, True) + self.assertEqual(other_tracer._is_enabled, True) + + def test_rule_based_tracer_configurator(self): + # pylint: disable=protected-access + rules = [ + ( + lambda x: x.name == "module_name", + _TracerConfig(is_enabled=True), + ), + ( + lambda x: x.name == "other_module_name", + _TracerConfig(is_enabled=False), + ), + ] + configurator = _RuleBaseTracerConfigurator(rules=rules) + + tracer_provider = trace.TracerProvider() + tracer = tracer_provider.get_tracer( + "module_name", + "library_version", + "schema_url", + {}, + ) + other_tracer = tracer_provider.get_tracer( + "other_module_name", + "library_version", + "schema_url", + {}, + ) + self.assertEqual(tracer._instrumentation_scope.name, "module_name") + self.assertEqual( + other_tracer._instrumentation_scope.name, "other_module_name" + ) + + self.assertEqual(tracer._is_enabled, True) + self.assertEqual(other_tracer._is_enabled, True) + + tracer_provider._set_tracer_configurator( + tracer_configurator=configurator + ) + + self.assertEqual(tracer._is_enabled, True) + self.assertEqual(other_tracer._is_enabled, False) + + def test_rule_based_tracer_configurator_default_when_rules_dont_match( + self, + ): + # pylint: disable=protected-access + rules = [ + ( + lambda x: x.name == "module_name", + _TracerConfig(is_enabled=False), + ), + ] + configurator = _RuleBaseTracerConfigurator(rules=rules) + + tracer_provider = trace.TracerProvider() + tracer = tracer_provider.get_tracer( + "module_name", + "library_version", + "schema_url", + {}, + ) + other_tracer = tracer_provider.get_tracer( + "other_module_name", + "library_version", + "schema_url", + {}, + ) + self.assertEqual(tracer._instrumentation_scope.name, "module_name") + self.assertEqual( + other_tracer._instrumentation_scope.name, "other_module_name" + ) + + self.assertEqual(tracer._is_enabled, True) + self.assertEqual(other_tracer._is_enabled, True) + + tracer_provider._set_tracer_configurator( + tracer_configurator=configurator + ) + + self.assertEqual(tracer._is_enabled, False) + self.assertEqual(other_tracer._is_enabled, True) + class TestRandomIdGenerator(unittest.TestCase): _TRACE_ID_MAX_VALUE = 2**128 - 1