From 16e889e862a08554d9bbf1460fc80d7dfef6c413 Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Wed, 15 Jul 2026 19:56:01 -0500 Subject: [PATCH 1/6] feat(config): wire tracer/meter/logger configurator experimental fields The declarative configuration parser reads the experimental tracer_configurator/development, meter_configurator/development and logger_configurator/development fields into dataclasses, but create_tracer_provider, create_meter_provider and create_logger_provider ignored them, so the parsed values were silently discarded. Map each ExperimentalXConfigurator to a rule-based SDK configurator: every per-scope entry becomes an instrumentation-scope name-glob rule carrying the scope's enabled flag, and default_config supplies the fallback config. The resulting configurator is passed to the provider constructor via the _x_configurator keyword, so per-instrumentation-scope enabled overrides now take effect. The logger minimum_severity and trace_based fields are accepted by the schema but unsupported by the Python SDK _LoggerConfig; they are ignored with a warning. --- .changelog/5390.added | 8 ++ .../configuration/_logger_provider.py | 65 ++++++++++- .../configuration/_meter_provider.py | 52 +++++++++ .../configuration/_tracer_provider.py | 50 ++++++++ .../tests/test_logger_provider.py | 109 ++++++++++++++++++ .../tests/test_meter_provider.py | 85 ++++++++++++++ .../tests/test_tracer_provider.py | 87 ++++++++++++++ 7 files changed, 455 insertions(+), 1 deletion(-) create mode 100644 .changelog/5390.added diff --git a/.changelog/5390.added b/.changelog/5390.added new file mode 100644 index 00000000000..0a957786517 --- /dev/null +++ b/.changelog/5390.added @@ -0,0 +1,8 @@ +`opentelemetry-configuration`: wire the experimental +`tracer_configurator/development`, `meter_configurator/development` and +`logger_configurator/development` fields into `create_tracer_provider`, +`create_meter_provider` and `create_logger_provider`, so per-instrumentation-scope +`enabled` overrides declared in the config file are applied to the provider +(previously these fields were parsed but silently discarded). The logger +`minimum_severity`/`trace_based` fields are not supported by the Python SDK and +are ignored with a warning. diff --git a/opentelemetry-configuration/src/opentelemetry/configuration/_logger_provider.py b/opentelemetry-configuration/src/opentelemetry/configuration/_logger_provider.py index 4214b2f9b76..a762cbd0e8a 100644 --- a/opentelemetry-configuration/src/opentelemetry/configuration/_logger_provider.py +++ b/opentelemetry-configuration/src/opentelemetry/configuration/_logger_provider.py @@ -19,6 +19,12 @@ from opentelemetry.configuration.models import ( BatchLogRecordProcessor as BatchLogRecordProcessorConfig, ) +from opentelemetry.configuration.models import ( + ExperimentalLoggerConfig as LoggerConfigConfig, +) +from opentelemetry.configuration.models import ( + ExperimentalLoggerConfigurator as LoggerConfiguratorConfig, +) from opentelemetry.configuration.models import ( ExperimentalOtlpFileExporter as ExperimentalOtlpFileExporterConfig, ) @@ -41,6 +47,10 @@ SimpleLogRecordProcessor as SimpleLogRecordProcessorConfig, ) from opentelemetry.sdk._logs import LoggerProvider +from opentelemetry.sdk._logs._internal import ( + _LoggerConfig, + _RuleBasedLoggerConfigurator, +) from opentelemetry.sdk._logs._internal.export import ( BatchLogRecordProcessor, ConsoleLogRecordExporter, @@ -48,6 +58,7 @@ SimpleLogRecordProcessor, ) from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.util.instrumentation import _scope_name_matches_glob _logger = logging.getLogger(__name__) @@ -216,6 +227,49 @@ def _create_log_record_processor( ) +def _to_logger_config(config: LoggerConfigConfig | None) -> _LoggerConfig: + """Map an experimental per-logger config to an SDK ``_LoggerConfig``. + + Only ``enabled`` is honored. ``minimum_severity`` and ``trace_based`` are + accepted by the config schema but not supported by the Python SDK + ``_LoggerConfig``; when set, they are ignored with a warning. An absent + ``enabled`` leaves the logger enabled. + """ + if config is None: + return _LoggerConfig.default() + if config.minimum_severity is not None or config.trace_based is not None: + _logger.warning( + "logger_configurator minimum_severity/trace_based are specified in " + "config but are not supported by the Python SDK LoggerProvider; " + "they will be ignored." + ) + if config.enabled is None: + return _LoggerConfig.default() + return _LoggerConfig(is_enabled=config.enabled) + + +def _create_logger_configurator( + config: LoggerConfiguratorConfig, +) -> _RuleBasedLoggerConfigurator: + """Build a rule-based logger configurator from experimental config. + + Each entry in ``loggers`` maps an instrumentation-scope name glob to a + per-logger config; ``default_config`` applies to scopes matching no glob. + Rules are evaluated in order, so earlier entries take precedence. + """ + rules = [ + ( + _scope_name_matches_glob(matcher.name), + _to_logger_config(matcher.config), + ) + for matcher in (config.loggers or []) + ] + return _RuleBasedLoggerConfigurator( + rules=rules, + default_config=_to_logger_config(config.default_config), + ) + + def create_logger_provider( config: LoggerProviderConfig | None, resource: Resource | None = None, @@ -232,7 +286,16 @@ def create_logger_provider( Returns: A configured LoggerProvider. """ - provider = LoggerProvider(resource=resource) + logger_configurator = ( + _create_logger_configurator(config.logger_configurator_development) + if config is not None + and config.logger_configurator_development is not None + else None + ) + + provider = LoggerProvider( + resource=resource, _logger_configurator=logger_configurator + ) if config is None: return provider diff --git a/opentelemetry-configuration/src/opentelemetry/configuration/_meter_provider.py b/opentelemetry-configuration/src/opentelemetry/configuration/_meter_provider.py index 98eb1357835..d3505a4b140 100644 --- a/opentelemetry-configuration/src/opentelemetry/configuration/_meter_provider.py +++ b/opentelemetry-configuration/src/opentelemetry/configuration/_meter_provider.py @@ -25,6 +25,12 @@ from opentelemetry.configuration.models import ( ExemplarFilter as ExemplarFilterConfig, ) +from opentelemetry.configuration.models import ( + ExperimentalMeterConfig as MeterConfigConfig, +) +from opentelemetry.configuration.models import ( + ExperimentalMeterConfigurator as MeterConfiguratorConfig, +) from opentelemetry.configuration.models import ( ExperimentalOtlpFileMetricExporter as ExperimentalOtlpFileMetricExporterConfig, ) @@ -76,6 +82,10 @@ UpDownCounter, _Gauge, ) +from opentelemetry.sdk.metrics._internal import ( + _MeterConfig, + _RuleBasedMeterConfigurator, +) from opentelemetry.sdk.metrics.export import ( AggregationTemporality, ConsoleMetricExporter, @@ -94,6 +104,7 @@ View, ) from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.util.instrumentation import _scope_name_matches_glob _logger = logging.getLogger(__name__) @@ -511,6 +522,39 @@ def _create_exemplar_filter( ) +def _to_meter_config(config: MeterConfigConfig | None) -> _MeterConfig: + """Map an experimental per-meter config to an SDK ``_MeterConfig``. + + Only ``enabled`` is honored — it is the sole field the SDK + ``_MeterConfig`` exposes. An absent ``enabled`` leaves the meter enabled. + """ + if config is None or config.enabled is None: + return _MeterConfig.default() + return _MeterConfig(is_enabled=config.enabled) + + +def _create_meter_configurator( + config: MeterConfiguratorConfig, +) -> _RuleBasedMeterConfigurator: + """Build a rule-based meter configurator from experimental config. + + Each entry in ``meters`` maps an instrumentation-scope name glob to a + per-meter config; ``default_config`` applies to scopes matching no glob. + Rules are evaluated in order, so earlier entries take precedence. + """ + rules = [ + ( + _scope_name_matches_glob(matcher.name), + _to_meter_config(matcher.config), + ) + for matcher in (config.meters or []) + ] + return _RuleBasedMeterConfigurator( + rules=rules, + default_config=_to_meter_config(config.default_config), + ) + + def create_meter_provider( config: MeterProviderConfig | None, resource: Resource | None = None, @@ -544,11 +588,19 @@ def create_meter_provider( for view_config in config.views: views.append(_create_view(view_config)) + meter_configurator = ( + _create_meter_configurator(config.meter_configurator_development) + if config is not None + and config.meter_configurator_development is not None + else None + ) + return MeterProvider( resource=resource, metric_readers=readers, exemplar_filter=exemplar_filter, # type: ignore[arg-type] views=views, + _meter_configurator=meter_configurator, ) diff --git a/opentelemetry-configuration/src/opentelemetry/configuration/_tracer_provider.py b/opentelemetry-configuration/src/opentelemetry/configuration/_tracer_provider.py index 733691dc67d..3a094250e8b 100644 --- a/opentelemetry-configuration/src/opentelemetry/configuration/_tracer_provider.py +++ b/opentelemetry-configuration/src/opentelemetry/configuration/_tracer_provider.py @@ -28,6 +28,12 @@ from opentelemetry.configuration.models import ( ExperimentalOtlpFileExporter as ExperimentalOtlpFileExporterConfig, ) +from opentelemetry.configuration.models import ( + ExperimentalTracerConfig as TracerConfigConfig, +) +from opentelemetry.configuration.models import ( + ExperimentalTracerConfigurator as TracerConfiguratorConfig, +) from opentelemetry.configuration.models import ( IdGenerator as IdGeneratorConfig, ) @@ -64,6 +70,8 @@ _DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT, SpanLimits, TracerProvider, + _RuleBasedTracerConfigurator, + _TracerConfig, ) from opentelemetry.sdk.trace._sampling_experimental import ( ComposableSampler, @@ -98,6 +106,7 @@ Sampler, TraceIdRatioBased, ) +from opentelemetry.sdk.util.instrumentation import _scope_name_matches_glob from opentelemetry.trace import SpanKind as TraceSpanKind _logger = logging.getLogger(__name__) @@ -389,6 +398,39 @@ def _create_span_limits(config: SpanLimitsConfig) -> SpanLimits: ) +def _to_tracer_config(config: TracerConfigConfig | None) -> _TracerConfig: + """Map an experimental per-tracer config to an SDK ``_TracerConfig``. + + Only ``enabled`` is honored — it is the sole field the SDK + ``_TracerConfig`` exposes. An absent ``enabled`` leaves the tracer enabled. + """ + if config is None or config.enabled is None: + return _TracerConfig.default() + return _TracerConfig(is_enabled=config.enabled) + + +def _create_tracer_configurator( + config: TracerConfiguratorConfig, +) -> _RuleBasedTracerConfigurator: + """Build a rule-based tracer configurator from experimental config. + + Each entry in ``tracers`` maps an instrumentation-scope name glob to a + per-tracer config; ``default_config`` applies to scopes matching no glob. + Rules are evaluated in order, so earlier entries take precedence. + """ + rules = [ + ( + _scope_name_matches_glob(matcher.name), + _to_tracer_config(matcher.config), + ) + for matcher in (config.tracers or []) + ] + return _RuleBasedTracerConfigurator( + rules=rules, + default_config=_to_tracer_config(config.default_config), + ) + + def create_tracer_provider( config: TracerProviderConfig | None, resource: Resource | None = None, @@ -422,11 +464,19 @@ def create_tracer_provider( ) ) + tracer_configurator = ( + _create_tracer_configurator(config.tracer_configurator_development) + if config is not None + and config.tracer_configurator_development is not None + else None + ) + provider = TracerProvider( resource=resource, sampler=sampler, span_limits=span_limits, id_generator=id_generator, + _tracer_configurator=tracer_configurator, ) if config is not None: diff --git a/opentelemetry-configuration/tests/test_logger_provider.py b/opentelemetry-configuration/tests/test_logger_provider.py index 9965e4b6bfe..6c95c42d6e0 100644 --- a/opentelemetry-configuration/tests/test_logger_provider.py +++ b/opentelemetry-configuration/tests/test_logger_provider.py @@ -25,6 +25,15 @@ from opentelemetry.configuration.models import ( BatchLogRecordProcessor as BatchLogRecordProcessorConfig, ) +from opentelemetry.configuration.models import ( + ExperimentalLoggerConfig as LoggerConfigConfig, +) +from opentelemetry.configuration.models import ( + ExperimentalLoggerConfigurator as LoggerConfiguratorConfig, +) +from opentelemetry.configuration.models import ( + ExperimentalLoggerMatcherAndConfig as LoggerMatcherAndConfig, +) from opentelemetry.configuration.models import ( ExperimentalOtlpFileExporter as ExperimentalOtlpFileExporterConfig, ) @@ -42,6 +51,7 @@ ) from opentelemetry.configuration.models import ( NameStringValuePair, + SeverityNumber, ) from opentelemetry.configuration.models import ( OtlpGrpcExporter as OtlpGrpcExporterConfig, @@ -59,6 +69,7 @@ SimpleLogRecordProcessor, ) from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.util.instrumentation import InstrumentationScope class TestCreateLoggerProviderBasic(unittest.TestCase): @@ -432,5 +443,103 @@ def test_no_limits_no_warning(): mock_logger.warning.assert_not_called() +# Configurator tests access the SDK LoggerProvider private +# _apply_logger_configurator to assert the wired config takes effect. +# pylint: disable=protected-access +class TestLoggerConfigurator(unittest.TestCase): + @staticmethod + def _enabled(provider, name): + return provider._apply_logger_configurator( + InstrumentationScope(name) + ).is_enabled + + def test_no_configurator_leaves_loggers_enabled(self): + provider = create_logger_provider(LoggerProviderConfig(processors=[])) + self.assertTrue(self._enabled(provider, "any.scope")) + + def test_matching_glob_disables_logger(self): + config = LoggerProviderConfig( + processors=[], + logger_configurator_development=LoggerConfiguratorConfig( + default_config=LoggerConfigConfig(enabled=True), + loggers=[ + LoggerMatcherAndConfig( + name="noisy.*", + config=LoggerConfigConfig(enabled=False), + ) + ], + ), + ) + provider = create_logger_provider(config) + self.assertFalse(self._enabled(provider, "noisy.http")) + self.assertTrue(self._enabled(provider, "app.service")) + + def test_default_config_applies_to_unmatched_scopes(self): + config = LoggerProviderConfig( + processors=[], + logger_configurator_development=LoggerConfiguratorConfig( + default_config=LoggerConfigConfig(enabled=False), + loggers=[ + LoggerMatcherAndConfig( + name="keep.*", + config=LoggerConfigConfig(enabled=True), + ) + ], + ), + ) + provider = create_logger_provider(config) + self.assertTrue(self._enabled(provider, "keep.me")) + self.assertFalse(self._enabled(provider, "other")) + + def test_first_matching_rule_wins(self): + config = LoggerProviderConfig( + processors=[], + logger_configurator_development=LoggerConfiguratorConfig( + loggers=[ + LoggerMatcherAndConfig( + name="a.*", + config=LoggerConfigConfig(enabled=False), + ), + LoggerMatcherAndConfig( + name="a.b", + config=LoggerConfigConfig(enabled=True), + ), + ], + ), + ) + provider = create_logger_provider(config) + self.assertFalse(self._enabled(provider, "a.b")) + + def test_absent_enabled_defaults_to_enabled(self): + config = LoggerProviderConfig( + processors=[], + logger_configurator_development=LoggerConfiguratorConfig( + default_config=LoggerConfigConfig(), + ), + ) + provider = create_logger_provider(config) + self.assertTrue(self._enabled(provider, "any.scope")) + + def test_unsupported_fields_log_warning(self): + config = LoggerProviderConfig( + processors=[], + logger_configurator_development=LoggerConfiguratorConfig( + default_config=LoggerConfigConfig( + enabled=True, + minimum_severity=SeverityNumber.warn, + ), + ), + ) + with self.assertLogs( + "opentelemetry.configuration._logger_provider", + level="WARNING", + ) as cm: + create_logger_provider(config) + self.assertTrue( + any("minimum_severity" in msg for msg in cm.output), + "Expected warning about unsupported minimum_severity/trace_based", + ) + + if __name__ == "__main__": unittest.main() diff --git a/opentelemetry-configuration/tests/test_meter_provider.py b/opentelemetry-configuration/tests/test_meter_provider.py index 83c9a8868c1..6ea11d8e7d0 100644 --- a/opentelemetry-configuration/tests/test_meter_provider.py +++ b/opentelemetry-configuration/tests/test_meter_provider.py @@ -24,6 +24,15 @@ from opentelemetry.configuration.models import ( ConsoleMetricExporter as ConsoleMetricExporterConfig, ) +from opentelemetry.configuration.models import ( + ExperimentalMeterConfig as MeterConfigConfig, +) +from opentelemetry.configuration.models import ( + ExperimentalMeterConfigurator as MeterConfiguratorConfig, +) +from opentelemetry.configuration.models import ( + ExperimentalMeterMatcherAndConfig as MeterMatcherAndConfig, +) from opentelemetry.configuration.models import ( ExperimentalOtlpFileMetricExporter as ExperimentalOtlpFileMetricExporterConfig, ) @@ -93,6 +102,7 @@ View, ) from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.util.instrumentation import InstrumentationScope class TestCreateMeterProviderBasic(unittest.TestCase): @@ -857,3 +867,78 @@ def test_stream_aggregation_sum(self): def test_stream_aggregation_default(self): view = self._get_view(self._make_view_config(stream_kwargs={"aggregation": AggregationConfig(default={})})) self.assertIsInstance(view._aggregation, DefaultAggregation) + + +class TestMeterConfigurator(unittest.TestCase): + @staticmethod + def _enabled(provider, name): + return provider._apply_meter_configurator( + InstrumentationScope(name) + ).is_enabled + + def test_no_configurator_leaves_meters_enabled(self): + provider = create_meter_provider(MeterProviderConfig(readers=[])) + self.assertTrue(self._enabled(provider, "any.scope")) + + def test_matching_glob_disables_meter(self): + config = MeterProviderConfig( + readers=[], + meter_configurator_development=MeterConfiguratorConfig( + default_config=MeterConfigConfig(enabled=True), + meters=[ + MeterMatcherAndConfig( + name="noisy.*", + config=MeterConfigConfig(enabled=False), + ) + ], + ), + ) + provider = create_meter_provider(config) + self.assertFalse(self._enabled(provider, "noisy.http")) + self.assertTrue(self._enabled(provider, "app.service")) + + def test_default_config_applies_to_unmatched_scopes(self): + config = MeterProviderConfig( + readers=[], + meter_configurator_development=MeterConfiguratorConfig( + default_config=MeterConfigConfig(enabled=False), + meters=[ + MeterMatcherAndConfig( + name="keep.*", + config=MeterConfigConfig(enabled=True), + ) + ], + ), + ) + provider = create_meter_provider(config) + self.assertTrue(self._enabled(provider, "keep.me")) + self.assertFalse(self._enabled(provider, "other")) + + def test_first_matching_rule_wins(self): + config = MeterProviderConfig( + readers=[], + meter_configurator_development=MeterConfiguratorConfig( + meters=[ + MeterMatcherAndConfig( + name="a.*", + config=MeterConfigConfig(enabled=False), + ), + MeterMatcherAndConfig( + name="a.b", + config=MeterConfigConfig(enabled=True), + ), + ], + ), + ) + provider = create_meter_provider(config) + self.assertFalse(self._enabled(provider, "a.b")) + + def test_absent_enabled_defaults_to_enabled(self): + config = MeterProviderConfig( + readers=[], + meter_configurator_development=MeterConfiguratorConfig( + default_config=MeterConfigConfig(), + ), + ) + provider = create_meter_provider(config) + self.assertTrue(self._enabled(provider, "any.scope")) diff --git a/opentelemetry-configuration/tests/test_tracer_provider.py b/opentelemetry-configuration/tests/test_tracer_provider.py index 9c950a0e049..19987fd5487 100644 --- a/opentelemetry-configuration/tests/test_tracer_provider.py +++ b/opentelemetry-configuration/tests/test_tracer_provider.py @@ -43,6 +43,15 @@ from opentelemetry.configuration.models import ( ExperimentalSpanParent as SpanParentConfig, ) +from opentelemetry.configuration.models import ( + ExperimentalTracerConfig as TracerConfigConfig, +) +from opentelemetry.configuration.models import ( + ExperimentalTracerConfigurator as TracerConfiguratorConfig, +) +from opentelemetry.configuration.models import ( + ExperimentalTracerMatcherAndConfig as TracerMatcherAndConfig, +) from opentelemetry.configuration.models import ( IdGenerator as IdGeneratorConfig, ) @@ -95,6 +104,7 @@ Sampler, TraceIdRatioBased, ) +from opentelemetry.sdk.util.instrumentation import InstrumentationScope from opentelemetry.trace import SpanContext, TraceFlags from opentelemetry.trace import SpanKind as TraceSpanKind @@ -830,3 +840,80 @@ def test_empty_id_generator_raises_configuration_error(self): """Empty IdGenerator config (no type specified) raises ConfigurationError.""" with self.assertRaises(ConfigurationError): self._make_provider(IdGeneratorConfig()) + + +# Configurator tests access the SDK TracerProvider private +# _apply_tracer_configurator to assert the wired config takes effect. +class TestTracerConfigurator(unittest.TestCase): + @staticmethod + def _enabled(provider, name): + return provider._apply_tracer_configurator( + InstrumentationScope(name) + ).is_enabled + + def test_no_configurator_leaves_tracers_enabled(self): + provider = create_tracer_provider(TracerProviderConfig(processors=[])) + self.assertTrue(self._enabled(provider, "any.scope")) + + def test_matching_glob_disables_tracer(self): + config = TracerProviderConfig( + processors=[], + tracer_configurator_development=TracerConfiguratorConfig( + default_config=TracerConfigConfig(enabled=True), + tracers=[ + TracerMatcherAndConfig( + name="noisy.*", + config=TracerConfigConfig(enabled=False), + ) + ], + ), + ) + provider = create_tracer_provider(config) + self.assertFalse(self._enabled(provider, "noisy.http")) + self.assertTrue(self._enabled(provider, "app.service")) + + def test_default_config_applies_to_unmatched_scopes(self): + config = TracerProviderConfig( + processors=[], + tracer_configurator_development=TracerConfiguratorConfig( + default_config=TracerConfigConfig(enabled=False), + tracers=[ + TracerMatcherAndConfig( + name="keep.*", + config=TracerConfigConfig(enabled=True), + ) + ], + ), + ) + provider = create_tracer_provider(config) + self.assertTrue(self._enabled(provider, "keep.me")) + self.assertFalse(self._enabled(provider, "other")) + + def test_first_matching_rule_wins(self): + config = TracerProviderConfig( + processors=[], + tracer_configurator_development=TracerConfiguratorConfig( + tracers=[ + TracerMatcherAndConfig( + name="a.*", + config=TracerConfigConfig(enabled=False), + ), + TracerMatcherAndConfig( + name="a.b", + config=TracerConfigConfig(enabled=True), + ), + ], + ), + ) + provider = create_tracer_provider(config) + self.assertFalse(self._enabled(provider, "a.b")) + + def test_absent_enabled_defaults_to_enabled(self): + config = TracerProviderConfig( + processors=[], + tracer_configurator_development=TracerConfiguratorConfig( + default_config=TracerConfigConfig(), + ), + ) + provider = create_tracer_provider(config) + self.assertTrue(self._enabled(provider, "any.scope")) From 8bbe04b5c0f19bf6301258f6f5a9b3adb994f63a Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Fri, 31 Jul 2026 10:26:01 -0500 Subject: [PATCH 2/6] Fix CI: rename changelog fragment to PR number and silence too-many-lines --- .changelog/{5390.added => 5418.added} | 0 opentelemetry-configuration/tests/test_meter_provider.py | 1 + opentelemetry-configuration/tests/test_tracer_provider.py | 1 + 3 files changed, 2 insertions(+) rename .changelog/{5390.added => 5418.added} (100%) diff --git a/.changelog/5390.added b/.changelog/5418.added similarity index 100% rename from .changelog/5390.added rename to .changelog/5418.added diff --git a/opentelemetry-configuration/tests/test_meter_provider.py b/opentelemetry-configuration/tests/test_meter_provider.py index 6ea11d8e7d0..77e031942a3 100644 --- a/opentelemetry-configuration/tests/test_meter_provider.py +++ b/opentelemetry-configuration/tests/test_meter_provider.py @@ -3,6 +3,7 @@ # Tests access private members of SDK classes to assert correct configuration. # pylint: disable=protected-access +# pylint: disable=too-many-lines import os import sys diff --git a/opentelemetry-configuration/tests/test_tracer_provider.py b/opentelemetry-configuration/tests/test_tracer_provider.py index 19987fd5487..0c28e63fbfb 100644 --- a/opentelemetry-configuration/tests/test_tracer_provider.py +++ b/opentelemetry-configuration/tests/test_tracer_provider.py @@ -3,6 +3,7 @@ # Tests access private members of SDK classes to assert correct configuration. # pylint: disable=protected-access +# pylint: disable=too-many-lines import os import sys From 0829283dbc80e3e7deeb4c798348c457ae0a42b0 Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Fri, 31 Jul 2026 11:21:17 -0500 Subject: [PATCH 3/6] Re-trigger CI (flaky Windows getting-started test) From 5ea8b33d6b9e13e250a029253550d6058c1d47db Mon Sep 17 00:00:00 2001 From: Diego Hurtado <5069942+ocelotl@users.noreply.github.com> Date: Thu, 20 Aug 2026 06:51:50 -0500 Subject: [PATCH 4/6] test(config): verify configurator nodes parse and wire from YAML Load a YAML fixture exercising the tracer/meter/logger configurator/development nodes through load_config_file, then assert both that the parsed model tree matches expectations and that each provider created from it applies the per-scope enabled overrides. --- .../tests/file/data/configurator_config.yaml | 37 +++++++ .../tests/file/test_configurator_yaml.py | 98 +++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 opentelemetry-configuration/tests/file/data/configurator_config.yaml create mode 100644 opentelemetry-configuration/tests/file/test_configurator_yaml.py diff --git a/opentelemetry-configuration/tests/file/data/configurator_config.yaml b/opentelemetry-configuration/tests/file/data/configurator_config.yaml new file mode 100644 index 00000000000..8f6fce78c6d --- /dev/null +++ b/opentelemetry-configuration/tests/file/data/configurator_config.yaml @@ -0,0 +1,37 @@ +file_format: "1.0" +tracer_provider: + processors: + - batch: + exporter: + console: {} + tracer_configurator/development: + default_config: + enabled: true + tracers: + - name: "noisy.*" + config: + enabled: false +meter_provider: + readers: + - periodic: + exporter: + console: {} + meter_configurator/development: + default_config: + enabled: false + meters: + - name: "keep.*" + config: + enabled: true +logger_provider: + processors: + - batch: + exporter: + console: {} + logger_configurator/development: + default_config: + enabled: true + loggers: + - name: "noisy.*" + config: + enabled: false diff --git a/opentelemetry-configuration/tests/file/test_configurator_yaml.py b/opentelemetry-configuration/tests/file/test_configurator_yaml.py new file mode 100644 index 00000000000..b9a64a65b32 --- /dev/null +++ b/opentelemetry-configuration/tests/file/test_configurator_yaml.py @@ -0,0 +1,98 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +# Tests access private members of SDK providers to assert wired configuration. +# pylint: disable=protected-access + +import unittest +from pathlib import Path + +from opentelemetry.configuration._logger_provider import ( + create_logger_provider, +) +from opentelemetry.configuration._meter_provider import ( + create_meter_provider, +) +from opentelemetry.configuration._tracer_provider import ( + create_tracer_provider, +) +from opentelemetry.configuration.file import load_config_file +from opentelemetry.configuration.models import ( + ExperimentalLoggerConfig, + ExperimentalLoggerConfigurator, + ExperimentalLoggerMatcherAndConfig, + ExperimentalMeterConfig, + ExperimentalMeterConfigurator, + ExperimentalMeterMatcherAndConfig, + ExperimentalTracerConfig, + ExperimentalTracerConfigurator, + ExperimentalTracerMatcherAndConfig, +) +from opentelemetry.sdk.util.instrumentation import InstrumentationScope + + +class TestConfiguratorYaml(unittest.TestCase): + """Verify the tracer/meter/logger configurator nodes are parsed from YAML + and wired into the providers they configure.""" + + @classmethod + def setUpClass(cls): + config_path = Path(__file__).parent / "data" / "configurator_config.yaml" + cls.config = load_config_file(str(config_path)) + + def test_tracer_configurator_parsed_from_yaml(self): + self.assertEqual( + self.config.tracer_provider.tracer_configurator_development, + ExperimentalTracerConfigurator( + default_config=ExperimentalTracerConfig(enabled=True), + tracers=[ + ExperimentalTracerMatcherAndConfig( + name="noisy.*", + config=ExperimentalTracerConfig(enabled=False), + ) + ], + ), + ) + + def test_meter_configurator_parsed_from_yaml(self): + self.assertEqual( + self.config.meter_provider.meter_configurator_development, + ExperimentalMeterConfigurator( + default_config=ExperimentalMeterConfig(enabled=False), + meters=[ + ExperimentalMeterMatcherAndConfig( + name="keep.*", + config=ExperimentalMeterConfig(enabled=True), + ) + ], + ), + ) + + def test_logger_configurator_parsed_from_yaml(self): + self.assertEqual( + self.config.logger_provider.logger_configurator_development, + ExperimentalLoggerConfigurator( + default_config=ExperimentalLoggerConfig(enabled=True), + loggers=[ + ExperimentalLoggerMatcherAndConfig( + name="noisy.*", + config=ExperimentalLoggerConfig(enabled=False), + ) + ], + ), + ) + + def test_tracer_configurator_from_yaml_is_wired(self): + provider = create_tracer_provider(self.config.tracer_provider) + self.assertFalse(provider._apply_tracer_configurator(InstrumentationScope("noisy.http")).is_enabled) + self.assertTrue(provider._apply_tracer_configurator(InstrumentationScope("app.service")).is_enabled) + + def test_meter_configurator_from_yaml_is_wired(self): + provider = create_meter_provider(self.config.meter_provider) + self.assertTrue(provider._apply_meter_configurator(InstrumentationScope("keep.me")).is_enabled) + self.assertFalse(provider._apply_meter_configurator(InstrumentationScope("other")).is_enabled) + + def test_logger_configurator_from_yaml_is_wired(self): + provider = create_logger_provider(self.config.logger_provider) + self.assertFalse(provider._apply_logger_configurator(InstrumentationScope("noisy.http")).is_enabled) + self.assertTrue(provider._apply_logger_configurator(InstrumentationScope("app.service")).is_enabled) From 802e9e3678727c20b72e6fc9b64dc5e66ac89eff Mon Sep 17 00:00:00 2001 From: Diego Hurtado <5069942+ocelotl@users.noreply.github.com> Date: Thu, 20 Aug 2026 06:58:53 -0500 Subject: [PATCH 5/6] test(config): warn test for unsupported logger trace_based field Split the unsupported-field warning test so both minimum_severity and trace_based are covered by their own assertions. --- .../tests/test_logger_provider.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/opentelemetry-configuration/tests/test_logger_provider.py b/opentelemetry-configuration/tests/test_logger_provider.py index 6c95c42d6e0..1e01880a2de 100644 --- a/opentelemetry-configuration/tests/test_logger_provider.py +++ b/opentelemetry-configuration/tests/test_logger_provider.py @@ -520,7 +520,7 @@ def test_absent_enabled_defaults_to_enabled(self): provider = create_logger_provider(config) self.assertTrue(self._enabled(provider, "any.scope")) - def test_unsupported_fields_log_warning(self): + def test_unsupported_minimum_severity_logs_warning(self): config = LoggerProviderConfig( processors=[], logger_configurator_development=LoggerConfiguratorConfig( @@ -537,7 +537,27 @@ def test_unsupported_fields_log_warning(self): create_logger_provider(config) self.assertTrue( any("minimum_severity" in msg for msg in cm.output), - "Expected warning about unsupported minimum_severity/trace_based", + "Expected warning about unsupported minimum_severity", + ) + + def test_unsupported_trace_based_logs_warning(self): + config = LoggerProviderConfig( + processors=[], + logger_configurator_development=LoggerConfiguratorConfig( + default_config=LoggerConfigConfig( + enabled=True, + trace_based=True, + ), + ), + ) + with self.assertLogs( + "opentelemetry.configuration._logger_provider", + level="WARNING", + ) as cm: + create_logger_provider(config) + self.assertTrue( + any("trace_based" in msg for msg in cm.output), + "Expected warning about unsupported trace_based", ) From c8148583c5e30c9c620d09c55f7c2390df7dd888 Mon Sep 17 00:00:00 2001 From: Diego Hurtado <5069942+ocelotl@users.noreply.github.com> Date: Thu, 20 Aug 2026 07:21:04 -0500 Subject: [PATCH 6/6] style(config): reformat to repo line-length 120 The configurator files were formatted at a shorter line length; ruff format at the repo line-length collapses the wrapped lines. Reformat so the precommit CI job passes. --- .../src/opentelemetry/configuration/_logger_provider.py | 7 ++----- .../src/opentelemetry/configuration/_meter_provider.py | 3 +-- .../src/opentelemetry/configuration/_tracer_provider.py | 3 +-- opentelemetry-configuration/tests/test_logger_provider.py | 4 +--- opentelemetry-configuration/tests/test_meter_provider.py | 4 +--- opentelemetry-configuration/tests/test_tracer_provider.py | 4 +--- 6 files changed, 7 insertions(+), 18 deletions(-) diff --git a/opentelemetry-configuration/src/opentelemetry/configuration/_logger_provider.py b/opentelemetry-configuration/src/opentelemetry/configuration/_logger_provider.py index a762cbd0e8a..980bc90c4ec 100644 --- a/opentelemetry-configuration/src/opentelemetry/configuration/_logger_provider.py +++ b/opentelemetry-configuration/src/opentelemetry/configuration/_logger_provider.py @@ -288,14 +288,11 @@ def create_logger_provider( """ logger_configurator = ( _create_logger_configurator(config.logger_configurator_development) - if config is not None - and config.logger_configurator_development is not None + if config is not None and config.logger_configurator_development is not None else None ) - provider = LoggerProvider( - resource=resource, _logger_configurator=logger_configurator - ) + provider = LoggerProvider(resource=resource, _logger_configurator=logger_configurator) if config is None: return provider diff --git a/opentelemetry-configuration/src/opentelemetry/configuration/_meter_provider.py b/opentelemetry-configuration/src/opentelemetry/configuration/_meter_provider.py index d3505a4b140..27904c41b2c 100644 --- a/opentelemetry-configuration/src/opentelemetry/configuration/_meter_provider.py +++ b/opentelemetry-configuration/src/opentelemetry/configuration/_meter_provider.py @@ -590,8 +590,7 @@ def create_meter_provider( meter_configurator = ( _create_meter_configurator(config.meter_configurator_development) - if config is not None - and config.meter_configurator_development is not None + if config is not None and config.meter_configurator_development is not None else None ) diff --git a/opentelemetry-configuration/src/opentelemetry/configuration/_tracer_provider.py b/opentelemetry-configuration/src/opentelemetry/configuration/_tracer_provider.py index 3a094250e8b..4168a64e36f 100644 --- a/opentelemetry-configuration/src/opentelemetry/configuration/_tracer_provider.py +++ b/opentelemetry-configuration/src/opentelemetry/configuration/_tracer_provider.py @@ -466,8 +466,7 @@ def create_tracer_provider( tracer_configurator = ( _create_tracer_configurator(config.tracer_configurator_development) - if config is not None - and config.tracer_configurator_development is not None + if config is not None and config.tracer_configurator_development is not None else None ) diff --git a/opentelemetry-configuration/tests/test_logger_provider.py b/opentelemetry-configuration/tests/test_logger_provider.py index 1e01880a2de..299a643d235 100644 --- a/opentelemetry-configuration/tests/test_logger_provider.py +++ b/opentelemetry-configuration/tests/test_logger_provider.py @@ -449,9 +449,7 @@ def test_no_limits_no_warning(): class TestLoggerConfigurator(unittest.TestCase): @staticmethod def _enabled(provider, name): - return provider._apply_logger_configurator( - InstrumentationScope(name) - ).is_enabled + return provider._apply_logger_configurator(InstrumentationScope(name)).is_enabled def test_no_configurator_leaves_loggers_enabled(self): provider = create_logger_provider(LoggerProviderConfig(processors=[])) diff --git a/opentelemetry-configuration/tests/test_meter_provider.py b/opentelemetry-configuration/tests/test_meter_provider.py index 77e031942a3..6dd8d054d6f 100644 --- a/opentelemetry-configuration/tests/test_meter_provider.py +++ b/opentelemetry-configuration/tests/test_meter_provider.py @@ -873,9 +873,7 @@ def test_stream_aggregation_default(self): class TestMeterConfigurator(unittest.TestCase): @staticmethod def _enabled(provider, name): - return provider._apply_meter_configurator( - InstrumentationScope(name) - ).is_enabled + return provider._apply_meter_configurator(InstrumentationScope(name)).is_enabled def test_no_configurator_leaves_meters_enabled(self): provider = create_meter_provider(MeterProviderConfig(readers=[])) diff --git a/opentelemetry-configuration/tests/test_tracer_provider.py b/opentelemetry-configuration/tests/test_tracer_provider.py index 0c28e63fbfb..bdcc359a127 100644 --- a/opentelemetry-configuration/tests/test_tracer_provider.py +++ b/opentelemetry-configuration/tests/test_tracer_provider.py @@ -848,9 +848,7 @@ def test_empty_id_generator_raises_configuration_error(self): class TestTracerConfigurator(unittest.TestCase): @staticmethod def _enabled(provider, name): - return provider._apply_tracer_configurator( - InstrumentationScope(name) - ).is_enabled + return provider._apply_tracer_configurator(InstrumentationScope(name)).is_enabled def test_no_configurator_leaves_tracers_enabled(self): provider = create_tracer_provider(TracerProviderConfig(processors=[]))