Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Comment thread
pmcollins marked this conversation as resolved.
)
)
)
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,
Expand Down Expand Up @@ -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
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my distro I initialize the OpAMP client after the sdk has been setup but can't exclude other scenarios

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add this a SIG topic to get the execution order?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've renamed the entry point to pre_sdk_init_function so that we can add a post_sdk_init_function if required

# 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()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My org is interested in using OpAMP first to report effective SDK configuration. For that use case, it would be convenient if there were also an OpAMP hook after _init_tracing, _init_metrics, and _init_logging, so the OpAMP client could inspect how providers, processors, exporters, resources, etc. were actually configured from the various sources (env vars, CLI flags, file config, defaults, and entry points).

With only a pre-init hook, the OpAMP implementation would have to re-create or re-invoke parts of the SDK configuration logic and infer the effective config, or it would have to poll the global providers and traverse the private object graphs after an arbitrary delay.

I can see the value of the current pre-init hook for influencing initial configuration before providers are wired, but a post-init hook seems useful for reporting effective config. This could be a separate PR.

Longer term, maybe it would be better to introduce a configuration controller/object that could be the source of truth for SDK config: it could resolve the config from the supported sources, provide the effective config for reporting, accept updates from OpAMP, and notify listeners when the configuration changes. What do you think?

if _init_opamp is not None:
_init_opamp(resource)

_init_tracing(
exporters=span_exporters,
id_generator=id_generator,
Expand Down
64 changes: 64 additions & 0 deletions opentelemetry-sdk/tests/test_configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: "<EntryPoint>"

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,"
" <EntryPoint>: 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,
)
Loading