diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index 4c6b5330de..5573f46eec 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -554,6 +554,33 @@ def _import_id_generator(id_generator_name: str) -> IdGenerator: raise RuntimeError(f"{id_generator_name} is not an IdGenerator") +def _import_opamp() -> Callable[[Resource], None] | None: + # this in development, at the moment we are looking for a callable that takes + # the resource and instantiate an OpAMP agent. + # Since configuration is not specified every implementers may have its own. + # Refer to opentelemetry-opamp-client package on how to setup the OpAMP agent. + entry_point = None + try: + entry_point = next( + iter( + entry_points( + group="_opentelemetry_opamp", name="pre_sdk_init_function" + ) + ) + ) + return entry_point.load() + except StopIteration: + _logger.debug("No OpAMP init function found") + except AttributeError as exc: + _logger.warning( + "Failed to load OpAMP init function from entry point, %s: %s", + entry_point, + exc, + ) + + return None + + def _initialize_components( auto_instrumentation_version: str | None = None, trace_exporter_names: list[str] | None = None, @@ -618,6 +645,15 @@ def _initialize_components( # from the env variable else defaults to "unknown_service" resource = Resource.create(resource_attributes) + # OpAMP is a system created to configure OpenTelemetry SDKs with a remote config. + # This is different than other init helpers because setting up OpAMP requires distro + # provided code as it's not strictly specified. We call OpAMP init before other code + # because people may want to have it blocking to get an updated config before setting + # up the rest of the SDK. + _init_opamp = _import_opamp() + if _init_opamp is not None: + _init_opamp(resource) + _init_tracing( exporters=span_exporters, id_generator=id_generator, diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index 17a2b85bae..aa3d526131 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -1573,3 +1573,67 @@ def f(x): self.assertEqual(logging.config.dictConfig.__name__, "dictConfig") self.assertEqual(logging.basicConfig.__name__, "basicConfig") self.assertEqual(logging.config.fileConfig.__name__, "fileConfig") + + +class TestOpAMPInit(TestCase): + @patch("opentelemetry.sdk._configuration.entry_points") + @patch("opentelemetry.sdk._configuration.Resource") + def test_init_function_found(self, mock_resource, mock_entry_points): + init_function = mock.Mock() + mock_entry_points.configure_mock( + return_value=[ + IterEntryPoint("pre_sdk_init_function", init_function) + ] + ) + + _initialize_components(id_generator=1) + + mock_entry_points.assert_has_calls( + [ + mock.call( + group="_opentelemetry_opamp", name="pre_sdk_init_function" + ) + ] + ) + init_function.assert_called_once_with( + mock_resource.create.return_value + ) + + @patch("opentelemetry.sdk._configuration.entry_points") + def test_init_function_load_failure(self, mock_entry_points): + entry_point_mock = mock.Mock() + entry_point_mock.load.side_effect = AttributeError( + "module 'foo' has no attribute 'OpampInit'" + ) + mock_entry_points.configure_mock( + return_value=[entry_point_mock], + ) + entry_point_mock.__str__ = lambda x: "" + + with self.assertLogs(level="WARNING") as cm: + _initialize_components(id_generator=1) + + mock_entry_points.assert_has_calls( + [ + mock.call( + group="_opentelemetry_opamp", name="pre_sdk_init_function" + ) + ] + ) + + self.assertIn( + "WARNING:opentelemetry.sdk._configuration:Failed to load OpAMP init function from entry point," + " : module 'foo' has no attribute 'OpampInit'", + cm.output, + ) + + @patch("opentelemetry.sdk._configuration.entry_points") + def test_init_function_not_found(self, mock_entry_points): + mock_entry_points.configure_mock(return_value=[]) + + with self.assertLogs(level="DEBUG") as cm: + _initialize_components(id_generator=1) + self.assertIn( + "DEBUG:opentelemetry.sdk._configuration:No OpAMP init function found", + cm.output, + )