diff --git a/.changelog/5363.changed b/.changelog/5363.changed new file mode 100644 index 0000000000..0874dd0375 --- /dev/null +++ b/.changelog/5363.changed @@ -0,0 +1 @@ +`opentelemetry-sdk`: wire id_generator from declarative configuration to TracerProvider diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py index f6c3a06c7f..56c8dbd893 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py @@ -25,6 +25,9 @@ from opentelemetry.sdk._configuration.models import ( ExperimentalOtlpFileExporter as ExperimentalOtlpFileExporterConfig, ) +from opentelemetry.sdk._configuration.models import ( + IdGenerator as IdGeneratorConfig, +) from opentelemetry.sdk._configuration.models import ( OtlpGrpcExporter as OtlpGrpcExporterConfig, ) @@ -84,6 +87,7 @@ SimpleSpanProcessor, SpanExporter, ) +from opentelemetry.sdk.trace.id_generator import IdGenerator, RandomIdGenerator from opentelemetry.sdk.trace.sampling import ( ALWAYS_OFF, ALWAYS_ON, @@ -348,6 +352,24 @@ def _create_sampler(config: SamplerConfig) -> Sampler: ) +def _create_id_generator(config: IdGeneratorConfig) -> IdGenerator: + """Create an IdGenerator from config. + + Built-in ``random`` resolves to ``RandomIdGenerator``; unknown names + load from the ``opentelemetry_id_generator`` entry point group (the + same group ``OTEL_PYTHON_ID_GENERATOR`` uses today). + """ + if config.random is not None: + return RandomIdGenerator() + if config.additional_properties: + name = next(iter(config.additional_properties)) + return load_entry_point("opentelemetry_id_generator", name)() + raise ConfigurationError( + "No id_generator type specified in config. " + "Supported built-in types: random." + ) + + def _create_parent_based_sampler(config: ParentBasedSamplerConfig) -> Sampler: """Create a ParentBased sampler from config, applying SDK defaults for absent delegates.""" root = ( @@ -431,6 +453,11 @@ def create_tracer_provider( if config is not None and config.sampler is not None else _DEFAULT_SAMPLER ) + id_generator = ( + _create_id_generator(config.id_generator) + if config is not None and config.id_generator is not None + else None + ) span_limits = ( _create_span_limits(config.limits) if config is not None and config.limits is not None @@ -447,6 +474,7 @@ def create_tracer_provider( resource=resource, sampler=sampler, span_limits=span_limits, + id_generator=id_generator, ) if config is not None: diff --git a/opentelemetry-sdk/tests/_configuration/test_tracer_provider.py b/opentelemetry-sdk/tests/_configuration/test_tracer_provider.py index abac735fe4..e57acdc713 100644 --- a/opentelemetry-sdk/tests/_configuration/test_tracer_provider.py +++ b/opentelemetry-sdk/tests/_configuration/test_tracer_provider.py @@ -42,6 +42,9 @@ from opentelemetry.sdk._configuration.models import ( ExperimentalSpanParent as SpanParentConfig, ) +from opentelemetry.sdk._configuration.models import ( + IdGenerator as IdGeneratorConfig, +) from opentelemetry.sdk._configuration.models import ( OtlpGrpcExporter as OtlpGrpcExporterConfig, ) @@ -82,6 +85,7 @@ ConsoleSpanExporter, SimpleSpanProcessor, ) +from opentelemetry.sdk.trace.id_generator import RandomIdGenerator from opentelemetry.sdk.trace.sampling import ( ALWAYS_OFF, ALWAYS_ON, @@ -852,3 +856,54 @@ def test_absent_limits_do_not_read_env_vars(self): provider = self._create_with_limits(SpanLimitsConfig()) self.assertEqual(provider._span_limits.max_span_attributes, 128) self.assertEqual(provider._span_limits.max_events, 128) + + +class TestCreateIdGenerator(unittest.TestCase): + """Tests for _create_id_generator and id_generator wiring in create_tracer_provider.""" + + @staticmethod + def _make_provider(id_generator_config): + return create_tracer_provider( + TracerProviderConfig( + processors=[], id_generator=id_generator_config + ) + ) + + def test_absent_id_generator_uses_sdk_default(self): + """When id_generator is omitted, the SDK's default RandomIdGenerator is used.""" + provider = create_tracer_provider(TracerProviderConfig(processors=[])) + self.assertIsInstance(provider.id_generator, RandomIdGenerator) + + def test_builtin_random_id_generator(self): + """Built-in 'random' id_generator resolves to RandomIdGenerator.""" + provider = self._make_provider(IdGeneratorConfig(random={})) + self.assertIsInstance(provider.id_generator, RandomIdGenerator) + + def test_plugin_id_generator_loaded_via_entry_point(self): + """Unknown id_generator name is loaded from opentelemetry_id_generator entry point group.""" + mock_generator = MagicMock() + mock_class = MagicMock(return_value=mock_generator) + 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( + IdGeneratorConfig(my_custom_generator={}) + ) + self.assertIs(provider.id_generator, mock_generator) + + def test_unknown_id_generator_raises_configuration_error(self): + """Unknown id_generator name with no matching entry point raises ConfigurationError.""" + with patch( + "opentelemetry.sdk._configuration._common.entry_points", + return_value=[], + ): + with self.assertRaises(ConfigurationError): + # pylint: disable=unexpected-keyword-arg + self._make_provider(IdGeneratorConfig(no_such_generator={})) + + 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())