diff --git a/CHANGELOG.md b/CHANGELOG.md index 941551d8a9..d393f5c46b 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 sampler plugin loading to declarative file configuration via the `opentelemetry_sampler` entry point group, matching the spec's PluginComponentProvider mechanism + ([#5095](https://github.com/open-telemetry/opentelemetry-python/pull/5095)) - `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/_tracer_provider.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py index 32dfd96567..6ebda1d959 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py @@ -18,7 +18,10 @@ from typing import Optional from opentelemetry import trace -from opentelemetry.sdk._configuration._common import _parse_headers +from opentelemetry.sdk._configuration._common import ( + _parse_headers, + load_entry_point, +) from opentelemetry.sdk._configuration._exceptions import ConfigurationError from opentelemetry.sdk._configuration.models import ( OtlpGrpcExporter as OtlpGrpcExporterConfig, @@ -184,7 +187,13 @@ def _create_span_processor( def _create_sampler(config: SamplerConfig) -> Sampler: - """Create a sampler from config.""" + """Create a sampler from config. + + Known sampler types are checked via typed fields on the Sampler + dataclass. Unknown sampler names captured in additional_properties + by the @_additional_properties decorator are loaded via the + ``opentelemetry_sampler`` entry point group. + """ if config.always_on is not None: return ALWAYS_ON if config.always_off is not None: @@ -194,6 +203,9 @@ def _create_sampler(config: SamplerConfig) -> Sampler: return TraceIdRatioBased(ratio if ratio is not None else 1.0) if config.parent_based is not None: return _create_parent_based_sampler(config.parent_based) + if config.additional_properties: + name = next(iter(config.additional_properties)) + return load_entry_point("opentelemetry_sampler", name)() raise ConfigurationError( f"Unknown or unsupported sampler type in config: {config!r}. " "Supported types: always_on, always_off, trace_id_ratio_based, parent_based." diff --git a/opentelemetry-sdk/tests/_configuration/test_tracer_provider.py b/opentelemetry-sdk/tests/_configuration/test_tracer_provider.py index 5caf077cd5..451a473a7c 100644 --- a/opentelemetry-sdk/tests/_configuration/test_tracer_provider.py +++ b/opentelemetry-sdk/tests/_configuration/test_tracer_provider.py @@ -69,6 +69,7 @@ ALWAYS_OFF, ALWAYS_ON, ParentBased, + Sampler, TraceIdRatioBased, ) @@ -219,9 +220,27 @@ def test_parent_based_with_delegate_samplers(self): def test_unknown_sampler_raises_configuration_error(self): with self.assertRaises(ConfigurationError): - create_tracer_provider( - TracerProviderConfig(processors=[], sampler=SamplerConfig()) - ) + self._make_provider(SamplerConfig()) + + def test_plugin_sampler_loaded_via_entry_point(self): + mock_sampler = MagicMock(spec=Sampler) + mock_class = MagicMock(return_value=mock_sampler) + with patch( + "opentelemetry.sdk._configuration._common.entry_points", + return_value=[MagicMock(**{"load.return_value": mock_class})], + ): + # pylint: disable=unexpected-keyword-arg + provider = self._make_provider(SamplerConfig(my_custom_sampler={})) + self.assertIs(provider.sampler, mock_sampler) + + def test_unknown_plugin_raises_configuration_error(self): + with patch( + "opentelemetry.sdk._configuration._common.entry_points", + return_value=[], + ): + with self.assertRaises(ConfigurationError): + # pylint: disable=unexpected-keyword-arg + self._make_provider(SamplerConfig(no_such_sampler={})) class TestCreateSpanExporterAndProcessor(unittest.TestCase):