|
4 | 4 | from unittest.mock import patch, MagicMock, create_autospec |
5 | 5 | from contextlib import ExitStack |
6 | 6 |
|
| 7 | +from opentelemetry.sdk.resources import Resource |
7 | 8 | from opentelemetry.sdk.trace import TracerProvider as SDKTracerProvider |
8 | 9 |
|
9 | 10 | from sap_cloud_sdk.core.telemetry.auto_instrument import auto_instrument |
@@ -232,3 +233,85 @@ def test_auto_instrument_passes_baggage_span_processor(self, mock_traceloop_comp |
232 | 233 |
|
233 | 234 | mock_traceloop_components['baggage_processor'].assert_called_once() |
234 | 235 | mock_traceloop_components['get_tracer_provider'].return_value.add_span_processor.assert_called_once_with(mock_processor_instance) |
| 236 | + |
| 237 | + def test_auto_instrument_merges_resource_when_wrapper_installed(self, mock_traceloop_components): |
| 238 | + """When an OTel auto-instrumentation wrapper (e.g. an OpenTelemetry-Operator |
| 239 | + init-container injection) has pre-installed a TracerProvider whose Resource |
| 240 | + carries the standard `telemetry.auto.version` marker, auto_instrument merges |
| 241 | + sap-cloud-sdk attrs onto that provider's existing Resource — preserving the |
| 242 | + operator-supplied attrs and adding our SAP enrichment on top.""" |
| 243 | + mock_traceloop_components['get_app_name'].return_value = 'cloud-sdk-app' |
| 244 | + sap_attrs = { |
| 245 | + 'service.name': 'cloud-sdk-app', |
| 246 | + 'sap.cloud_sdk.name': 'SAP Cloud SDK for Python', |
| 247 | + 'sap.cloud_sdk.language': 'python', |
| 248 | + 'sap.solution_area': 'fina', |
| 249 | + 'mlflow.experiment_id': '1635264705567712', |
| 250 | + } |
| 251 | + mock_traceloop_components['create_resource'].return_value = sap_attrs |
| 252 | + |
| 253 | + wrapper_provider = SDKTracerProvider( |
| 254 | + resource=Resource.create({ |
| 255 | + 'telemetry.auto.version': '0.62b1', |
| 256 | + 'k8s.deployment.name': 'cloud-sdk-app-deployment', |
| 257 | + 'service.name': 'cloud-sdk-app-deployment', |
| 258 | + }) |
| 259 | + ) |
| 260 | + mock_traceloop_components['get_tracer_provider'].return_value = wrapper_provider |
| 261 | + |
| 262 | + with patch.dict('os.environ', {'OTEL_EXPORTER_OTLP_ENDPOINT': 'http://localhost:4317'}, clear=True): |
| 263 | + auto_instrument() |
| 264 | + |
| 265 | + merged_attrs = wrapper_provider.resource.attributes |
| 266 | + # Operator-supplied attrs are preserved. |
| 267 | + assert merged_attrs['telemetry.auto.version'] == '0.62b1' |
| 268 | + assert merged_attrs['k8s.deployment.name'] == 'cloud-sdk-app-deployment' |
| 269 | + # sap-cloud-sdk attrs are added. |
| 270 | + assert merged_attrs['sap.cloud_sdk.name'] == 'SAP Cloud SDK for Python' |
| 271 | + assert merged_attrs['sap.cloud_sdk.language'] == 'python' |
| 272 | + assert merged_attrs['sap.solution_area'] == 'fina' |
| 273 | + assert merged_attrs['mlflow.experiment_id'] == '1635264705567712' |
| 274 | + |
| 275 | + def test_auto_instrument_skips_merge_when_no_wrapper_marker(self, mock_traceloop_components): |
| 276 | + """When the active TracerProvider's Resource lacks the |
| 277 | + `telemetry.auto.version` marker (e.g. a self-installed provider, or no |
| 278 | + wrapper at all), auto_instrument does not mutate the provider's Resource.""" |
| 279 | + mock_traceloop_components['get_app_name'].return_value = 'cloud-sdk-app' |
| 280 | + mock_traceloop_components['create_resource'].return_value = { |
| 281 | + 'sap.cloud_sdk.name': 'SAP Cloud SDK for Python', |
| 282 | + 'sap.solution_area': 'fina', |
| 283 | + } |
| 284 | + |
| 285 | + initial_resource = Resource.create({'service.name': 'self-installed'}) |
| 286 | + plain_provider = SDKTracerProvider(resource=initial_resource) |
| 287 | + mock_traceloop_components['get_tracer_provider'].return_value = plain_provider |
| 288 | + |
| 289 | + with patch.dict('os.environ', {'OTEL_EXPORTER_OTLP_ENDPOINT': 'http://localhost:4317'}, clear=True): |
| 290 | + auto_instrument() |
| 291 | + |
| 292 | + # Resource is the original instance — no merge took place. |
| 293 | + assert plain_provider.resource is initial_resource |
| 294 | + assert 'sap.cloud_sdk.name' not in plain_provider.resource.attributes |
| 295 | + assert 'sap.solution_area' not in plain_provider.resource.attributes |
| 296 | + |
| 297 | + def test_auto_instrument_merge_overrides_colliding_service_name(self, mock_traceloop_components): |
| 298 | + """On a wrapper-installed provider, sap-cloud-sdk attrs override colliding |
| 299 | + keys: service.name from APPFND_CONHOS_APP_NAME wins over the operator's |
| 300 | + k8s-deployment-derived service.name.""" |
| 301 | + mock_traceloop_components['get_app_name'].return_value = 'cloud-sdk-app' |
| 302 | + mock_traceloop_components['create_resource'].return_value = { |
| 303 | + 'service.name': 'cloud-sdk-app', |
| 304 | + } |
| 305 | + |
| 306 | + wrapper_provider = SDKTracerProvider( |
| 307 | + resource=Resource.create({ |
| 308 | + 'telemetry.auto.version': '0.62b1', |
| 309 | + 'service.name': 'operator-supplied-name', |
| 310 | + }) |
| 311 | + ) |
| 312 | + mock_traceloop_components['get_tracer_provider'].return_value = wrapper_provider |
| 313 | + |
| 314 | + with patch.dict('os.environ', {'OTEL_EXPORTER_OTLP_ENDPOINT': 'http://localhost:4317'}, clear=True): |
| 315 | + auto_instrument() |
| 316 | + |
| 317 | + assert wrapper_provider.resource.attributes['service.name'] == 'cloud-sdk-app' |
0 commit comments