diff --git a/CHANGELOG.md b/CHANGELOG.md index 941551d8a9..9efd550053 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- `opentelemetry-sdk`: add propagator plugin loading to declarative file configuration via the `opentelemetry_propagator` entry point group, matching the spec's PluginComponentProvider mechanism + ([#5098](https://github.com/open-telemetry/opentelemetry-python/pull/5098)) - `opentelemetry-sdk`: add `additional_properties` support to generated config models via custom `datamodel-codegen` template, enabling plugin/custom component names to flow through typed dataclasses ([#5131](https://github.com/open-telemetry/opentelemetry-python/pull/5131)) - Fix incorrect code example in `create_tracer()` docstring diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_propagator.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_propagator.py index 315a4e8bed..a54672b61f 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_propagator.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_propagator.py @@ -31,25 +31,37 @@ TraceContextTextMapPropagator, ) - -def _load_entry_point_propagator(name: str) -> TextMapPropagator: - """Load and instantiate a propagator by name.""" - return load_entry_point("opentelemetry_propagator", name)() +# Propagators bundled with the SDK — no entry point lookup needed. +_PROPAGATOR_REGISTRY: dict = { + "tracecontext": lambda _: TraceContextTextMapPropagator(), + "baggage": lambda _: W3CBaggagePropagator(), +} def _propagators_from_textmap_config( config: TextMapPropagatorConfig, ) -> list[TextMapPropagator]: - """Resolve a single TextMapPropagator config entry to a list of propagators.""" + """Resolve a TextMapPropagator config to a list of propagators. + + Known names (tracecontext, baggage) are bootstrapped directly via + _PROPAGATOR_REGISTRY. Known schema fields not in the registry (b3, b3multi) + and unknown plugin names from additional_properties are loaded via the + ``opentelemetry_propagator`` entry point group. + """ result: list[TextMapPropagator] = [] - if config.tracecontext is not None: - result.append(TraceContextTextMapPropagator()) - if config.baggage is not None: - result.append(W3CBaggagePropagator()) - if config.b3 is not None: - result.append(_load_entry_point_propagator("b3")) - if config.b3multi is not None: - result.append(_load_entry_point_propagator("b3multi")) + for name in _PROPAGATOR_REGISTRY: + if getattr(config, name, None) is not None: + result.append(_PROPAGATOR_REGISTRY[name](getattr(config, name))) + + # Known schema fields not in registry (b3, b3multi) — loaded via entry point + for name in ("b3", "b3multi"): + if getattr(config, name, None) is not None: + result.append(load_entry_point("opentelemetry_propagator", name)()) + + # Plugin propagators from additional_properties + for name in config.additional_properties: + result.append(load_entry_point("opentelemetry_propagator", name)()) + return result @@ -85,7 +97,7 @@ def create_propagator( name = name.strip() if not name or name.lower() == "none": continue - propagator = _load_entry_point_propagator(name) + propagator = load_entry_point("opentelemetry_propagator", name)() propagators.setdefault(type(propagator), propagator) return CompositePropagator(list(propagators.values())) diff --git a/opentelemetry-sdk/tests/_configuration/test_propagator.py b/opentelemetry-sdk/tests/_configuration/test_propagator.py index d4aab75e74..bec6216f65 100644 --- a/opentelemetry-sdk/tests/_configuration/test_propagator.py +++ b/opentelemetry-sdk/tests/_configuration/test_propagator.py @@ -236,6 +236,40 @@ def test_unknown_composite_list_propagator_raises(self): with self.assertRaises(ConfigurationError): create_propagator(config) + def test_plugin_propagator_via_entry_point(self): + mock_propagator = MagicMock() + mock_ep = MagicMock() + mock_ep.load.return_value = lambda: mock_propagator + + with patch( + "opentelemetry.sdk._configuration._common.entry_points", + return_value=[mock_ep], + ): + config = PropagatorConfig( + composite=[ + # pylint: disable=unexpected-keyword-arg + TextMapPropagatorConfig(my_custom_propagator={}) + ] + ) + result = create_propagator(config) + + self.assertEqual(len(result._propagators), 1) # type: ignore[attr-defined] + self.assertIs(result._propagators[0], mock_propagator) # type: ignore[attr-defined] + + def test_unknown_composite_propagator_raises(self): + with patch( + "opentelemetry.sdk._configuration._common.entry_points", + return_value=[], + ): + config = PropagatorConfig( + composite=[ + # pylint: disable=unexpected-keyword-arg + TextMapPropagatorConfig(nonexistent={}) + ] + ) + with self.assertRaises(ConfigurationError): + create_propagator(config) + class TestConfigurePropagator(unittest.TestCase): def test_configure_propagator_calls_set_global_textmap(self):