From 3f5e1a9a89f89b3ae030a3a2aba66033112e9378 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 22 Oct 2025 14:22:57 -0700 Subject: [PATCH 1/7] Add operation name to dependencies --- .../exporter/export/trace/_exporter.py | 14 +++++++++++++- .../tests/trace/test_trace.py | 8 ++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py index 4025080e270c..9c9ce0f96887 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py @@ -321,7 +321,6 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem: data.url = data.url[:2048] else: # INTERNAL, CLIENT, PRODUCER envelope.name = _REMOTE_DEPENDENCY_ENVELOPE_NAME - # TODO: ai.operation.name for non-server spans time = 0 if span.end_time and span.start_time: time = span.end_time - span.start_time @@ -334,6 +333,7 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem: properties={}, ) envelope.data = MonitorBase(base_data=data, base_type="RemoteDependencyData") + envelope.tags[ContextTagKeys.AI_OPERATION_NAME] = span.name target = trace_utils._get_target_for_dependency_from_peer(span.attributes) if span.kind is SpanKind.CLIENT: gen_ai_attributes_val = "" @@ -351,6 +351,12 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem: # TODO: Not exposed in Swagger, need to update def envelope.tags["ai.user.userAgent"] = user_agent url = trace_utils._get_url_for_http_dependency(span.attributes) + # Http specific logic for ai.operation.name + if SpanAttributes.HTTP_ROUTE in span.attributes: + envelope.tags[ContextTagKeys.AI_OPERATION_NAME] = "{} {}".format( + span.attributes.get(HTTP_REQUEST_METHOD) or span.attributes.get(SpanAttributes.HTTP_METHOD), + span.attributes[SpanAttributes.HTTP_ROUTE], + ) # data if url: data.data = url @@ -365,6 +371,10 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem: span.attributes.get(SpanAttributes.HTTP_METHOD), path, ) + envelope.tags[ContextTagKeys.AI_OPERATION_NAME] = "{} {}".format( + span.attributes.get(HTTP_REQUEST_METHOD) or span.attributes.get(SpanAttributes.HTTP_METHOD), + path, + ) status_code = span.attributes.get(HTTP_RESPONSE_STATUS_CODE) or \ span.attributes.get(SpanAttributes.HTTP_STATUS_CODE) if status_code: @@ -447,6 +457,8 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem: data.type += " | {}".format(span.attributes[_AZURE_SDK_NAMESPACE_NAME]) # Apply truncation # See https://github.com/MohanGsk/ApplicationInsights-Home/tree/master/EndpointSpecs/Schemas/Bond + if envelope.tags.get(ContextTagKeys.AI_OPERATION_NAME): + data.name = envelope.tags[ContextTagKeys.AI_OPERATION_NAME][:1024] if data.name: data.name = str(data.name)[:1024] if data.result_code: diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_trace.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_trace.py index 6fc18959b7a8..77aeee0127c0 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_trace.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_trace.py @@ -380,6 +380,7 @@ def test_span_to_envelope_client_http(self): self.assertEqual(envelope.data.base_data.data, "https://www.wikipedia.org/wiki/Rabbit") self.assertEqual(envelope.data.base_type, "RemoteDependencyData") + self.assertEqual(envelope.tags[ContextTagKeys.AI_OPERATION_NAME], "GET /wiki/Rabbit") self.assertEqual(envelope.data.base_data.type, "HTTP") self.assertEqual(envelope.data.base_data.target, "service") self.assertEqual( @@ -584,6 +585,7 @@ def test_span_to_envelope_client_db(self): self.assertTrue(envelope.data.base_data.success) self.assertEqual(envelope.data.base_type, "RemoteDependencyData") + self.assertEqual(envelope.tags[ContextTagKeys.AI_OPERATION_NAME], "test") self.assertEqual(envelope.data.base_data.type, "db2 system") self.assertEqual(envelope.data.base_data.target, "service") self.assertEqual(envelope.data.base_data.data, "SELECT * from test") @@ -703,6 +705,7 @@ def test_span_to_envelope_client_rpc(self): self.assertEqual(envelope.data.base_data.result_code, "0") self.assertEqual(envelope.data.base_type, "RemoteDependencyData") + self.assertEqual(envelope.tags[ContextTagKeys.AI_OPERATION_NAME], "test") self.assertEqual(envelope.data.base_data.type, "rpc.system") self.assertEqual(envelope.data.base_data.target, "service") self.assertEqual(len(envelope.data.base_data.properties), 0) @@ -748,6 +751,7 @@ def test_span_to_envelope_client_messaging(self): self.assertEqual(envelope.data.base_data.result_code, "0") self.assertEqual(envelope.data.base_type, "RemoteDependencyData") + self.assertEqual(envelope.tags[ContextTagKeys.AI_OPERATION_NAME], "test") self.assertEqual(envelope.data.base_data.type, "messaging") self.assertEqual(envelope.data.base_data.target, "celery") self.assertEqual(len(envelope.data.base_data.properties), 0) @@ -791,6 +795,7 @@ def test_span_to_envelope_client_gen_ai(self): self.assertEqual(envelope.data.base_data.result_code, "0") self.assertEqual(envelope.data.base_type, "RemoteDependencyData") + self.assertEqual(envelope.tags[ContextTagKeys.AI_OPERATION_NAME], "test") self.assertEqual(envelope.data.base_data.type, "GenAI | az.ai.inference") self.assertEqual(envelope.data.base_data.target, "az.ai.inference") self.assertEqual(len(envelope.data.base_data.properties), 1) @@ -880,6 +885,7 @@ def test_span_to_envelope_client_azure(self): self.assertEqual(envelope.data.base_data.result_code, "0") self.assertEqual(envelope.data.base_type, "RemoteDependencyData") + self.assertEqual(envelope.tags[ContextTagKeys.AI_OPERATION_NAME], "test") self.assertEqual(envelope.data.base_data.type, "Microsoft.EventHub") self.assertEqual(envelope.data.base_data.target, "test_address/test_destination") self.assertEqual(len(envelope.data.base_data.properties), 2) @@ -924,6 +930,7 @@ def test_span_to_envelope_producer_messaging(self): self.assertEqual(envelope.data.base_data.result_code, "0") self.assertEqual(envelope.data.base_type, "RemoteDependencyData") + self.assertEqual(envelope.tags[ContextTagKeys.AI_OPERATION_NAME], "test") self.assertEqual(envelope.data.base_data.type, "Queue Message | messaging") self.assertEqual(envelope.data.base_data.target, "celery") self.assertEqual(len(envelope.data.base_data.properties), 0) @@ -978,6 +985,7 @@ def test_span_to_envelope_internal(self): self.assertTrue(envelope.data.base_data.success) self.assertEqual(envelope.data.base_type, "RemoteDependencyData") + self.assertEqual(envelope.tags[ContextTagKeys.AI_OPERATION_NAME], "test") self.assertEqual(envelope.data.base_data.type, "InProc") self.assertEqual(envelope.data.base_data.result_code, "0") self.assertEqual(len(envelope.data.base_data.properties), 0) From c34bfea59c4115241f5f928c7c02685510a25669 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 22 Oct 2025 16:40:04 -0700 Subject: [PATCH 2/7] Add operation name to logs --- .../opentelemetry/exporter/export/logs/_exporter.py | 4 ++++ .../tests/logs/test_logs.py | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/logs/_exporter.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/logs/_exporter.py index 9e3b12f7de39..7fee67a95797 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/logs/_exporter.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/logs/_exporter.py @@ -116,6 +116,7 @@ def _log_data_is_event(log_data: LogData) -> bool: # pylint: disable=protected-access +# pylint: disable=too-many-statements def _convert_log_to_envelope(log_data: LogData) -> TelemetryItem: log_record = log_data.log_record time_stamp = log_record.timestamp if log_record.timestamp is not None else log_record.observed_timestamp @@ -127,6 +128,9 @@ def _convert_log_to_envelope(log_data: LogData) -> TelemetryItem: envelope.tags[ContextTagKeys.AI_OPERATION_PARENT_ID] = "{:016x}".format( # type: ignore log_record.span_id or _DEFAULT_SPAN_ID ) + envelope.tags[ContextTagKeys.AI_OPERATION_NAME] = log_record.attributes.get( #type: ignore + ContextTagKeys.AI_OPERATION_NAME + ) if _utils._is_any_synthetic_source(log_record.attributes): envelope.tags[ContextTagKeys.AI_OPERATION_SYNTHETIC_SOURCE] = "True" # type: ignore # Special use case: Customers want to be able to set location ip on log records diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/logs/test_logs.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/logs/test_logs.py index 5c6aa8171ebb..7880cc093d9d 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/logs/test_logs.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/logs/test_logs.py @@ -73,7 +73,7 @@ def setUpClass(cls): severity_number=SeverityNumber.WARN, body="Test message", resource=Resource.create(attributes={"asd": "test_resource"}), - attributes={"test": "attribute"}, + attributes={"test": "attribute", "ai.operation.name": "TestOperationName"}, ), InstrumentationScope("test_name"), ) @@ -87,7 +87,7 @@ def setUpClass(cls): severity_number=SeverityNumber.WARN, body="", resource=Resource.create(attributes={"asd": "test_resource"}), - attributes={"test": "attribute"}, + attributes={"test": "attribute", "ai.operation.name": "TestOperationName"}, ), InstrumentationScope("test_name"), ) @@ -115,7 +115,7 @@ def setUpClass(cls): severity_number=SeverityNumber.WARN, body={"foo": {"bar": "baz", "qux": 42}}, resource=Resource.create(attributes={"asd": "test_resource"}), - attributes={"test": "attribute"}, + attributes={"test": "attribute", "ai.operation.name": "TestOperationName"}, ), InstrumentationScope("test_name"), ) @@ -394,6 +394,7 @@ def test_log_to_envelope_partA(self): span_id = self._log_data.log_record.span_id self.assertEqual(envelope.tags.get(ContextTagKeys.AI_OPERATION_PARENT_ID), "{:016x}".format(span_id)) self._log_data.log_record.resource = old_resource + self.assertEqual(envelope.tags.get(ContextTagKeys.AI_OPERATION_NAME), "TestOperationName") def test_log_to_envelope_partA_default(self): exporter = self._exporter @@ -433,6 +434,7 @@ def test_log_to_envelope_log_empty(self): self.assertEqual(envelope.name, "Microsoft.ApplicationInsights.Message") self.assertEqual(envelope.data.base_type, "MessageData") self.assertEqual(envelope.data.base_data.message, _DEFAULT_LOG_MESSAGE) + self.assertEqual(envelope.tags.get(ContextTagKeys.AI_OPERATION_NAME), "TestOperationName") def test_log_to_envelope_log_empty_with_whitespaces(self): exporter = self._exporter @@ -447,6 +449,7 @@ def test_log_to_envelope_log_complex_body(self): self.assertEqual(envelope.name, "Microsoft.ApplicationInsights.Message") self.assertEqual(envelope.data.base_type, "MessageData") self.assertEqual(envelope.data.base_data.message, json.dumps(self._log_data_complex_body.log_record.body)) + self.assertEqual(envelope.tags.get(ContextTagKeys.AI_OPERATION_NAME), "TestOperationName") def test_log_to_envelope_log_complex_body_not_serializeable(self): exporter = self._exporter From f40f5f10b22ecd661dbd70fcb207d6e6e43cd865 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 22 Oct 2025 16:41:35 -0700 Subject: [PATCH 3/7] Add CHANGELOG --- sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md index 333f98b7dbfc..b3355762edd5 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.0.0b45 (Unreleased) ### Features Added +- Added Operation Name Propagation for Dependencies and Logs ### Breaking Changes From 012cabb779713e4f1f2bbdc6823ee48a446bd5d9 Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 22 Oct 2025 16:43:41 -0700 Subject: [PATCH 4/7] Updated CHANGELOG --- sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md index b3355762edd5..7a44c37ed06b 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features Added - Added Operation Name Propagation for Dependencies and Logs + ([#43588](https://github.com/Azure/azure-sdk-for-python/pull/43588)) ### Breaking Changes From 402e78695e32e0452e4c5cccfc360bf990e93a26 Mon Sep 17 00:00:00 2001 From: rads-1996 Date: Wed, 22 Oct 2025 16:44:52 -0700 Subject: [PATCH 5/7] Remove extra space Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../monitor/opentelemetry/exporter/export/logs/_exporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/logs/_exporter.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/logs/_exporter.py index 7fee67a95797..31a70269a56f 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/logs/_exporter.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/logs/_exporter.py @@ -128,7 +128,7 @@ def _convert_log_to_envelope(log_data: LogData) -> TelemetryItem: envelope.tags[ContextTagKeys.AI_OPERATION_PARENT_ID] = "{:016x}".format( # type: ignore log_record.span_id or _DEFAULT_SPAN_ID ) - envelope.tags[ContextTagKeys.AI_OPERATION_NAME] = log_record.attributes.get( #type: ignore + envelope.tags[ContextTagKeys.AI_OPERATION_NAME] = log_record.attributes.get( # type: ignore ContextTagKeys.AI_OPERATION_NAME ) if _utils._is_any_synthetic_source(log_record.attributes): From 45d640614cb352f4332f655214082fa3abac69fe Mon Sep 17 00:00:00 2001 From: rads-1996 Date: Wed, 22 Oct 2025 16:48:20 -0700 Subject: [PATCH 6/7] Fix data.name override Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../monitor/opentelemetry/exporter/export/trace/_exporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py index 9c9ce0f96887..8347b08f7d69 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py @@ -459,7 +459,7 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem: # See https://github.com/MohanGsk/ApplicationInsights-Home/tree/master/EndpointSpecs/Schemas/Bond if envelope.tags.get(ContextTagKeys.AI_OPERATION_NAME): data.name = envelope.tags[ContextTagKeys.AI_OPERATION_NAME][:1024] - if data.name: + elif data.name: data.name = str(data.name)[:1024] if data.result_code: data.result_code = str(data.result_code)[:1024] From d0fa710aca79a12c5c6cb800cfffcec83c70340a Mon Sep 17 00:00:00 2001 From: Radhika Gupta Date: Wed, 22 Oct 2025 17:02:53 -0700 Subject: [PATCH 7/7] added type safety --- .../opentelemetry/exporter/export/logs/_exporter.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/logs/_exporter.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/logs/_exporter.py index 31a70269a56f..ad83e55bb809 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/logs/_exporter.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/logs/_exporter.py @@ -128,9 +128,14 @@ def _convert_log_to_envelope(log_data: LogData) -> TelemetryItem: envelope.tags[ContextTagKeys.AI_OPERATION_PARENT_ID] = "{:016x}".format( # type: ignore log_record.span_id or _DEFAULT_SPAN_ID ) - envelope.tags[ContextTagKeys.AI_OPERATION_NAME] = log_record.attributes.get( # type: ignore - ContextTagKeys.AI_OPERATION_NAME - ) + if ( + log_record.attributes + and ContextTagKeys.AI_OPERATION_NAME in log_record.attributes + and log_record.attributes[ContextTagKeys.AI_OPERATION_NAME] is not None + ): + envelope.tags[ContextTagKeys.AI_OPERATION_NAME] = log_record.attributes.get( # type: ignore + ContextTagKeys.AI_OPERATION_NAME + ) if _utils._is_any_synthetic_source(log_record.attributes): envelope.tags[ContextTagKeys.AI_OPERATION_SYNTHETIC_SOURCE] = "True" # type: ignore # Special use case: Customers want to be able to set location ip on log records