diff --git a/src/sap_cloud_sdk/core/telemetry/__init__.py b/src/sap_cloud_sdk/core/telemetry/__init__.py index 1febaddd..6552f7fd 100644 --- a/src/sap_cloud_sdk/core/telemetry/__init__.py +++ b/src/sap_cloud_sdk/core/telemetry/__init__.py @@ -16,6 +16,7 @@ from sap_cloud_sdk.core.telemetry.genai_operation import GenAIOperation from sap_cloud_sdk.core.telemetry.metrics_decorator import record_metrics from sap_cloud_sdk.core.telemetry.auto_instrument import auto_instrument +from sap_cloud_sdk.core.telemetry.config import register_sdk_resource_attributes from sap_cloud_sdk.core.telemetry.tracer import ( context_overlay, get_current_span, @@ -67,6 +68,7 @@ "set_tenant_id", "get_tenant_id", "auto_instrument", + "register_sdk_resource_attributes", "context_overlay", "get_current_span", "add_span_attribute", diff --git a/src/sap_cloud_sdk/core/telemetry/config.py b/src/sap_cloud_sdk/core/telemetry/config.py index 45f3565d..f3f5b7c2 100644 --- a/src/sap_cloud_sdk/core/telemetry/config.py +++ b/src/sap_cloud_sdk/core/telemetry/config.py @@ -22,6 +22,34 @@ SDK_NAME, ) +# Registry of extra resource attributes contributed by companion SDKs. +# Populated via register_sdk_resource_attributes() at import time of those SDKs. +_extra_sdk_attributes: dict = {} + + +def register_sdk_resource_attributes(attributes: dict) -> None: + """Register additional OTel resource attributes to be included in every provider. + + Companion SDKs call this once at import time so their version appears on every + span and metric without any change to agent startup code. Read the version from + ``importlib.metadata`` rather than hardcoding it:: + + from importlib.metadata import version, PackageNotFoundError + from sap_cloud_sdk.core.telemetry.config import register_sdk_resource_attributes + + try: + sdk_version = version("my-sdk-package") + except PackageNotFoundError: + sdk_version = "unknown" + + register_sdk_resource_attributes({ + "sap.my_sdk.version": sdk_version, + "sap.my_sdk.language": "python", + }) + """ + _extra_sdk_attributes.update(attributes) + + # Default attribute values DEFAULT_UNKNOWN = "unknown" @@ -172,6 +200,8 @@ def create_resource_attributes_from_env() -> dict: if service_display_name is not None: attributes[ATTR_SAP_SERVICE_DISPLAY_NAME] = service_display_name + attributes.update(_extra_sdk_attributes) + return attributes diff --git a/tests/core/unit/telemetry/test_config.py b/tests/core/unit/telemetry/test_config.py index fe2f1f68..022409b2 100644 --- a/tests/core/unit/telemetry/test_config.py +++ b/tests/core/unit/telemetry/test_config.py @@ -7,6 +7,8 @@ create_resource_attributes_from_env, get_config, set_config, + register_sdk_resource_attributes, + _extra_sdk_attributes, ) from sap_cloud_sdk.core.telemetry.constants import ( ATTR_MLFLOW_EXPERIMENT_ID, @@ -299,3 +301,40 @@ def test_service_display_name_independent_of_other_attributes(self): assert attrs[ATTR_SAP_SERVICE_DISPLAY_NAME] == "My Service" assert attrs[ATTR_SAP_ORD_ID] == "my-ord-id" + + +class TestRegisterSdkResourceAttributes: + """Tests for the companion-SDK resource attribute registry.""" + + def setup_method(self): + _extra_sdk_attributes.clear() + + def teardown_method(self): + _extra_sdk_attributes.clear() + + def test_registered_attributes_appear_in_resource(self): + register_sdk_resource_attributes({"sap.internal_sdk.version": "1.0.0"}) + attrs = create_resource_attributes_from_env() + assert attrs["sap.internal_sdk.version"] == "1.0.0" + + def test_multiple_registrations_are_merged(self): + register_sdk_resource_attributes({"sap.foo.version": "1.0"}) + register_sdk_resource_attributes({"sap.bar.version": "2.0"}) + attrs = create_resource_attributes_from_env() + assert attrs["sap.foo.version"] == "1.0" + assert attrs["sap.bar.version"] == "2.0" + + def test_registered_attributes_do_not_override_cloud_sdk_keys(self): + from sap_cloud_sdk.core.telemetry.constants import ATTR_SAP_SDK_VERSION + original = create_resource_attributes_from_env()[ATTR_SAP_SDK_VERSION] + register_sdk_resource_attributes({ATTR_SAP_SDK_VERSION: "999"}) + attrs = create_resource_attributes_from_env() + assert attrs[ATTR_SAP_SDK_VERSION] == "999" + _extra_sdk_attributes.clear() + assert create_resource_attributes_from_env()[ATTR_SAP_SDK_VERSION] == original + + def test_empty_registry_does_not_affect_output(self): + attrs_without = create_resource_attributes_from_env() + register_sdk_resource_attributes({}) + attrs_with = create_resource_attributes_from_env() + assert attrs_without == attrs_with