diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md index 118baac1dc6a..4cb75b3f5e73 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md @@ -3,6 +3,8 @@ ## 1.0.0b47 (Unreleased) ### Features Added +- Add support for user id and authId +([#44662](https://github.com/Azure/azure-sdk-for-python/pull/44662)) ### Breaking Changes 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 b9ec20119620..664b26189aae 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 @@ -5,20 +5,35 @@ from typing import Optional, Sequence, Any from opentelemetry._logs.severity import SeverityNumber +from opentelemetry.sdk._logs import ReadableLogRecord +from opentelemetry.sdk._logs.export import LogRecordExporter, LogRecordExportResult from opentelemetry.semconv.attributes.exception_attributes import ( EXCEPTION_ESCAPED, EXCEPTION_MESSAGE, EXCEPTION_STACKTRACE, EXCEPTION_TYPE, ) -from opentelemetry.sdk._logs import ReadableLogRecord -from opentelemetry.sdk._logs.export import LogRecordExporter, LogRecordExportResult + +try: + from opentelemetry.semconv.logs import ( + LogRecordAttributes as _SemconvLogRecordAttributes, + ) +except ImportError: + _SemconvLogRecordAttributes = None +try: + from opentelemetry.semconv._incubating.attributes import ( + enduser_attributes as _enduser_attributes, + ) +except ImportError: + _enduser_attributes = None from azure.monitor.opentelemetry.exporter import _utils from azure.monitor.opentelemetry.exporter._constants import ( + _APPLICATION_INSIGHTS_EVENT_MARKER_ATTRIBUTE, + _DEFAULT_LOG_MESSAGE, _EXCEPTION_ENVELOPE_NAME, _MESSAGE_ENVELOPE_NAME, - _DEFAULT_LOG_MESSAGE, + _MICROSOFT_CUSTOM_EVENT_NAME, ) from azure.monitor.opentelemetry.exporter._generated.models import ( ContextTagKeys, @@ -34,10 +49,6 @@ ExportResult, ) from azure.monitor.opentelemetry.exporter.export.trace import _utils as trace_utils -from azure.monitor.opentelemetry.exporter._constants import ( - _APPLICATION_INSIGHTS_EVENT_MARKER_ATTRIBUTE, - _MICROSOFT_CUSTOM_EVENT_NAME, -) from azure.monitor.opentelemetry.exporter.statsbeat._state import ( get_statsbeat_shutdown, get_statsbeat_custom_events_feature_set, @@ -45,6 +56,17 @@ set_statsbeat_custom_events_feature_set, ) +_ENDUSER_ID_ATTRIBUTE = ( + getattr(_SemconvLogRecordAttributes, "ENDUSER_ID", None) + or getattr(_enduser_attributes, "ENDUSER_ID", None) + or "enduser.id" +) +_ENDUSER_PSEUDO_ID_ATTRIBUTE = ( + getattr(_SemconvLogRecordAttributes, "ENDUSER_PSEUDO_ID", None) + or getattr(_enduser_attributes, "ENDUSER_PSEUDO_ID", None) + or "enduser.pseudo.id" +) + _logger = logging.getLogger(__name__) _DEFAULT_SPAN_ID = 0 @@ -123,11 +145,16 @@ def _convert_log_to_envelope(readable_log_record: ReadableLogRecord) -> Telemetr log_record = readable_log_record.log_record time_stamp = log_record.timestamp if log_record.timestamp is not None else log_record.observed_timestamp envelope = _utils._create_telemetry_item(time_stamp) - envelope.tags.update(_utils._populate_part_a_fields(readable_log_record.resource)) # type: ignore - envelope.tags[ContextTagKeys.AI_OPERATION_ID] = "{:032x}".format( # type: ignore - log_record.trace_id or _DEFAULT_TRACE_ID - ) - envelope.tags[ContextTagKeys.AI_OPERATION_PARENT_ID] = "{:016x}".format( # type: ignore + tags = envelope.tags or {} + envelope.tags = tags + tags.update(_utils._populate_part_a_fields(readable_log_record.resource)) # type: ignore + tags[ContextTagKeys.AI_OPERATION_ID] = "{:032x}".format(log_record.trace_id or _DEFAULT_TRACE_ID) # type: ignore + if log_record.attributes and _ENDUSER_ID_ATTRIBUTE in log_record.attributes: + tags[ContextTagKeys.AI_USER_AUTH_USER_ID] = log_record.attributes[_ENDUSER_ID_ATTRIBUTE] + if log_record.attributes and _ENDUSER_PSEUDO_ID_ATTRIBUTE in log_record.attributes: + tags[ContextTagKeys.AI_USER_ID] = log_record.attributes[_ENDUSER_PSEUDO_ID_ATTRIBUTE] + + tags[ContextTagKeys.AI_OPERATION_PARENT_ID] = "{:016x}".format( # type: ignore log_record.span_id or _DEFAULT_SPAN_ID ) if ( @@ -135,15 +162,15 @@ def _convert_log_to_envelope(readable_log_record: ReadableLogRecord) -> Telemetr 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 + 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 + tags[ContextTagKeys.AI_OPERATION_SYNTHETIC_SOURCE] = "True" # type: ignore # Special use case: Customers want to be able to set location ip on log records location_ip = trace_utils._get_location_ip(log_record.attributes) if location_ip: - envelope.tags[ContextTagKeys.AI_LOCATION_IP] = location_ip # type: ignore + tags[ContextTagKeys.AI_LOCATION_IP] = location_ip # type: ignore properties = _utils._filter_custom_properties( log_record.attributes, lambda key, val: not _is_ignored_attribute(key) # type: ignore ) @@ -236,7 +263,7 @@ def _map_body_to_message(log_body: Any) -> str: try: return json.dumps(log_body)[:32768] - except: # pylint: disable=bare-except + except Exception: # pylint: disable=broad-except return str(log_body)[:32768] @@ -252,6 +279,8 @@ def _is_ignored_attribute(key: str) -> bool: EXCEPTION_ESCAPED, _APPLICATION_INSIGHTS_EVENT_MARKER_ATTRIBUTE, _MICROSOFT_CUSTOM_EVENT_NAME, + _ENDUSER_ID_ATTRIBUTE, + _ENDUSER_PSEUDO_ID_ATTRIBUTE, ) ) 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 123dfbf0d7e3..ecd2c452c34d 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 @@ -14,6 +14,13 @@ ) from opentelemetry.semconv.trace import DbSystemValues, SpanAttributes from opentelemetry.semconv._incubating.attributes import gen_ai_attributes + +try: + from opentelemetry.semconv._incubating.attributes import ( + enduser_attributes as _enduser_attributes, + ) +except ImportError: + _enduser_attributes = None from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import ReadableSpan from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult @@ -56,6 +63,17 @@ __all__ = ["AzureMonitorTraceExporter"] +_ENDUSER_ID_ATTRIBUTE = ( + getattr(SpanAttributes, "ENDUSER_ID", None) + or (getattr(_enduser_attributes, "ENDUSER_ID", None) if _enduser_attributes is not None else None) + or "enduser.id" +) +_ENDUSER_PSEUDO_ID_ATTRIBUTE = ( + getattr(SpanAttributes, "ENDUSER_PSEUDO_ID", None) + or (getattr(_enduser_attributes, "ENDUSER_PSEUDO_ID", None) if _enduser_attributes is not None else None) + or "enduser.pseudo.id" +) + _STANDARD_OPENTELEMETRY_ATTRIBUTE_PREFIXES = [ "http.", "db.", @@ -107,9 +125,7 @@ def __init__(self, **kwargs: Any): self._tracer_provider = kwargs.pop("tracer_provider", None) super().__init__(**kwargs) - def export( - self, spans: Sequence[ReadableSpan], **kwargs: Any # pylint: disable=unused-argument - ) -> SpanExportResult: + def export(self, spans: Sequence[ReadableSpan], **_kwargs: Any) -> SpanExportResult: """Export span data. :param spans: Open Telemetry Spans to export. @@ -222,8 +238,10 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem: envelope = _utils._create_telemetry_item(start_time) envelope.tags.update(_utils._populate_part_a_fields(span.resource)) envelope.tags[ContextTagKeys.AI_OPERATION_ID] = "{:032x}".format(span.context.trace_id) - if SpanAttributes.ENDUSER_ID in span.attributes: - envelope.tags[ContextTagKeys.AI_USER_ID] = span.attributes[SpanAttributes.ENDUSER_ID] + if _ENDUSER_ID_ATTRIBUTE in span.attributes: + envelope.tags[ContextTagKeys.AI_USER_AUTH_USER_ID] = span.attributes[_ENDUSER_ID_ATTRIBUTE] + if _ENDUSER_PSEUDO_ID_ATTRIBUTE in span.attributes: + envelope.tags[ContextTagKeys.AI_USER_ID] = span.attributes[_ENDUSER_PSEUDO_ID_ATTRIBUTE] if _utils._is_any_synthetic_source(span.attributes): envelope.tags[ContextTagKeys.AI_OPERATION_SYNTHETIC_SOURCE] = "True" if span.parent and span.parent.span_id: 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 dc89cf40aeca..50ded1df9cf4 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 @@ -80,7 +80,26 @@ def setUpClass(cls): severity_text="WARNING", severity_number=SeverityNumber.WARN, body="Test message", - attributes={"test": "attribute", "ai.operation.name": "TestOperationName"}, + attributes={ + "test": "attribute", + "ai.operation.name": "TestOperationName", + }, + ), + resource=Resource.create(attributes={"asd": "test_resource"}), + instrumentation_scope=InstrumentationScope("test_name"), + ) + cls._log_data_user_fields = _logs.ReadWriteLogRecord( + LogRecord( + timestamp=1646865018558419456, + context=ctx, + severity_text="WARNING", + severity_number=SeverityNumber.WARN, + body="Test message", + attributes={ + "test": "attribute", + "enduser.id": "test-auth", + "enduser.pseudo.id": "test-user", + }, ), resource=Resource.create(attributes={"asd": "test_resource"}), instrumentation_scope=InstrumentationScope("test_name"), @@ -92,7 +111,10 @@ def setUpClass(cls): severity_text="WARNING", severity_number=SeverityNumber.WARN, body="", - attributes={"test": "attribute", "ai.operation.name": "TestOperationName"}, + attributes={ + "test": "attribute", + "ai.operation.name": "TestOperationName", + }, ), resource=Resource.create(attributes={"asd": "test_resource"}), instrumentation_scope=InstrumentationScope("test_name"), @@ -116,7 +138,10 @@ def setUpClass(cls): severity_text="WARNING", severity_number=SeverityNumber.WARN, body={"foo": {"bar": "baz", "qux": 42}}, - attributes={"test": "attribute", "ai.operation.name": "TestOperationName"}, + attributes={ + "test": "attribute", + "ai.operation.name": "TestOperationName", + }, ), resource=Resource.create(attributes={"asd": "test_resource"}), instrumentation_scope=InstrumentationScope("test_name"), @@ -246,7 +271,12 @@ def setUpClass(cls): severity_text="EXCEPTION", severity_number=SeverityNumber.FATAL, body="test exception", - attributes={"test": "attribute", EXCEPTION_TYPE: "", EXCEPTION_MESSAGE: "", EXCEPTION_STACKTRACE: ""}, + attributes={ + "test": "attribute", + EXCEPTION_TYPE: "", + EXCEPTION_MESSAGE: "", + EXCEPTION_STACKTRACE: "", + }, ), resource=Resource.create(attributes={"asd": "test_resource"}), instrumentation_scope=InstrumentationScope("test_name"), @@ -258,7 +288,12 @@ def setUpClass(cls): severity_text="EXCEPTION", severity_number=SeverityNumber.FATAL, body="", - attributes={"test": "attribute", EXCEPTION_TYPE: "", EXCEPTION_MESSAGE: "", EXCEPTION_STACKTRACE: ""}, + attributes={ + "test": "attribute", + EXCEPTION_TYPE: "", + EXCEPTION_MESSAGE: "", + EXCEPTION_STACKTRACE: "", + }, ), resource=Resource.create(attributes={"asd": "test_resource"}), instrumentation_scope=InstrumentationScope("test_name"), @@ -354,26 +389,44 @@ def test_log_to_envelope_partA(self): self.assertEqual(envelope.instrumentation_key, "1234abcd-5678-4efa-8abc-1234567890ab") self.assertIsNotNone(envelope.tags) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_DEVICE_ID), azure_monitor_context[ContextTagKeys.AI_DEVICE_ID] + envelope.tags.get(ContextTagKeys.AI_DEVICE_ID), + azure_monitor_context[ContextTagKeys.AI_DEVICE_ID], ) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_DEVICE_LOCALE), azure_monitor_context[ContextTagKeys.AI_DEVICE_LOCALE] + envelope.tags.get(ContextTagKeys.AI_DEVICE_LOCALE), + azure_monitor_context[ContextTagKeys.AI_DEVICE_LOCALE], ) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_DEVICE_TYPE), azure_monitor_context[ContextTagKeys.AI_DEVICE_TYPE] + envelope.tags.get(ContextTagKeys.AI_DEVICE_TYPE), + azure_monitor_context[ContextTagKeys.AI_DEVICE_TYPE], ) self.assertEqual( envelope.tags.get(ContextTagKeys.AI_INTERNAL_SDK_VERSION), azure_monitor_context[ContextTagKeys.AI_INTERNAL_SDK_VERSION], ) - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE), "testServiceNamespace.testServiceName") - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE_INSTANCE), "testServiceInstanceId") - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_INTERNAL_NODE_NAME), "testServiceInstanceId") + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE), + "testServiceNamespace.testServiceName", + ) + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE_INSTANCE), + "testServiceInstanceId", + ) + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_INTERNAL_NODE_NAME), + "testServiceInstanceId", + ) trace_id = self._log_data.log_record.trace_id - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_OPERATION_ID), "{:032x}".format(trace_id)) + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_OPERATION_ID), + "{:032x}".format(trace_id), + ) span_id = self._log_data.log_record.span_id - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_OPERATION_PARENT_ID), "{:016x}".format(span_id)) + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_OPERATION_PARENT_ID), + "{:016x}".format(span_id), + ) self._log_data.resource = old_resource self.assertEqual(envelope.tags.get(ContextTagKeys.AI_OPERATION_NAME), "TestOperationName") @@ -402,6 +455,15 @@ def test_log_to_envelope_log(self): self.assertEqual(envelope.data.base_data.severity_level, 2) self.assertEqual(envelope.data.base_data.properties["test"], "attribute") + def test_log_to_envelope_user_fields(self): + exporter = self._exporter + envelope = exporter._log_to_envelope(self._log_data_user_fields) + + self.assertEqual(envelope.tags.get(ContextTagKeys.AI_USER_AUTH_USER_ID), "test-auth") + self.assertEqual(envelope.tags.get(ContextTagKeys.AI_USER_ID), "test-user") + self.assertNotIn("enduser.id", envelope.data.base_data.properties) + self.assertNotIn("enduser.pseudo.id", envelope.data.base_data.properties) + def test_log_to_envelope_log_none(self): exporter = self._exporter envelope = exporter._log_to_envelope(self._log_data_none) @@ -429,7 +491,10 @@ def test_log_to_envelope_log_complex_body(self): envelope = exporter._log_to_envelope(self._log_data_complex_body) 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.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): @@ -438,7 +503,8 @@ def test_log_to_envelope_log_complex_body_not_serializeable(self): self.assertEqual(envelope.name, "Microsoft.ApplicationInsights.Message") self.assertEqual(envelope.data.base_type, "MessageData") self.assertEqual( - envelope.data.base_data.message, str(self._log_data_complex_body_not_serializeable.log_record.body) + envelope.data.base_data.message, + str(self._log_data_complex_body_not_serializeable.log_record.body), ) def test_log_to_envelope_exception_with_string_message(self): @@ -593,8 +659,14 @@ def test_log_to_envelope_synthetic_source(self): envelope = exporter._log_to_envelope(log_data) self.assertEqual(envelope.tags.get(ContextTagKeys.AI_OPERATION_SYNTHETIC_SOURCE), "True") - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE), "testServiceNamespace.testServiceName") - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE_INSTANCE), "testServiceInstanceId") + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE), + "testServiceNamespace.testServiceName", + ) + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE_INSTANCE), + "testServiceInstanceId", + ) def test_log_to_envelope_synthetic_load_always_on(self): exporter = self._exporter @@ -631,8 +703,14 @@ def test_log_to_envelope_synthetic_load_always_on(self): envelope = exporter._log_to_envelope(log_data) self.assertEqual(envelope.tags.get(ContextTagKeys.AI_OPERATION_SYNTHETIC_SOURCE), "True") - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE), "testServiceNamespace.testServiceName") - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE_INSTANCE), "testServiceInstanceId") + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE), + "testServiceNamespace.testServiceName", + ) + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE_INSTANCE), + "testServiceInstanceId", + ) class TestAzureLogExporterWithDisabledStorage(TestAzureLogExporter): 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 6f1d41e0565c..aa0d8002b13c 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 @@ -9,13 +9,10 @@ from unittest import mock # pylint: disable=import-error -from opentelemetry.trace import get_tracer_provider, set_tracer_provider +from opentelemetry.trace import set_tracer_provider from opentelemetry.sdk import trace, resources -from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import SpanExportResult -from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter -from opentelemetry.sdk.trace.export import SimpleSpanProcessor from opentelemetry.sdk.util.instrumentation import InstrumentationScope from opentelemetry.semconv.attributes.exception_attributes import ( EXCEPTION_ESCAPED, @@ -78,7 +75,6 @@ def tearDownClass(cls): def test_constructor(self): """Test the constructor.""" - tp = trace.TracerProvider() exporter = AzureMonitorTraceExporter( connection_string="InstrumentationKey=4321abcd-5678-4efa-8abc-1234567890ab", ) @@ -288,7 +284,8 @@ def test_span_to_envelope_partA(self): context=context, resource=resource, attributes={ - "enduser.id": "testId", + "enduser.id": "testAuthId", + "enduser.pseudo.id": "testUserId", "user_agent.synthetic.type": "bot", }, parent=context, @@ -300,26 +297,45 @@ def test_span_to_envelope_partA(self): self.assertEqual(envelope.instrumentation_key, "1234abcd-5678-4efa-8abc-1234567890ab") self.assertIsNotNone(envelope.tags) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_DEVICE_ID), azure_monitor_context[ContextTagKeys.AI_DEVICE_ID] + envelope.tags.get(ContextTagKeys.AI_DEVICE_ID), + azure_monitor_context[ContextTagKeys.AI_DEVICE_ID], ) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_DEVICE_LOCALE), azure_monitor_context[ContextTagKeys.AI_DEVICE_LOCALE] + envelope.tags.get(ContextTagKeys.AI_DEVICE_LOCALE), + azure_monitor_context[ContextTagKeys.AI_DEVICE_LOCALE], ) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_DEVICE_TYPE), azure_monitor_context[ContextTagKeys.AI_DEVICE_TYPE] + envelope.tags.get(ContextTagKeys.AI_DEVICE_TYPE), + azure_monitor_context[ContextTagKeys.AI_DEVICE_TYPE], ) self.assertEqual( envelope.tags.get(ContextTagKeys.AI_INTERNAL_SDK_VERSION), azure_monitor_context[ContextTagKeys.AI_INTERNAL_SDK_VERSION], ) - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE), "testServiceNamespace.testServiceName") - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE_INSTANCE), "testServiceInstanceId") - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_INTERNAL_NODE_NAME), "testServiceInstanceId") - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_OPERATION_ID), "{:032x}".format(context.trace_id)) - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_USER_ID), "testId") + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE), + "testServiceNamespace.testServiceName", + ) + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_CLOUD_ROLE_INSTANCE), + "testServiceInstanceId", + ) + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_INTERNAL_NODE_NAME), + "testServiceInstanceId", + ) + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_OPERATION_ID), + "{:032x}".format(context.trace_id), + ) + self.assertEqual(envelope.tags.get(ContextTagKeys.AI_USER_AUTH_USER_ID), "testAuthId") + self.assertEqual(envelope.tags.get(ContextTagKeys.AI_USER_ID), "testUserId") self.assertEqual(envelope.tags.get(ContextTagKeys.AI_OPERATION_SYNTHETIC_SOURCE), "True") - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_OPERATION_PARENT_ID), "{:016x}".format(context.span_id)) + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_OPERATION_PARENT_ID), + "{:016x}".format(context.span_id), + ) def test_span_to_envelope_partA_default(self): exporter = self._exporter @@ -452,7 +468,10 @@ def test_span_to_envelope_client_http(self): envelope = exporter._span_to_envelope(span) self.assertEqual(envelope.data.base_data.target, "www.example.com") - span._attributes = {"http.request.method": "GET", "gen_ai.system": "az.ai.inference"} + span._attributes = { + "http.request.method": "GET", + "gen_ai.system": "az.ai.inference", + } envelope = exporter._span_to_envelope(span) self.assertEqual(envelope.data.base_data.target, "az.ai.inference") self.assertEqual(envelope.data.base_data.name, "GET /") @@ -476,7 +495,10 @@ def test_span_to_envelope_client_http(self): "http.target": "/path/12314/?q=ddds#123", } envelope = exporter._span_to_envelope(span) - self.assertEqual(envelope.data.base_data.data, "https://www.wikipedia.org/path/12314/?q=ddds#123") + self.assertEqual( + envelope.data.base_data.data, + "https://www.wikipedia.org/path/12314/?q=ddds#123", + ) span._attributes = { "http.method": "GET", @@ -486,7 +508,10 @@ def test_span_to_envelope_client_http(self): "http.target": "/path/12314/?q=ddds#123", } envelope = exporter._span_to_envelope(span) - self.assertEqual(envelope.data.base_data.data, "https://example.com:8080/path/12314/?q=ddds#123") + self.assertEqual( + envelope.data.base_data.data, + "https://example.com:8080/path/12314/?q=ddds#123", + ) span._attributes = { "http.method": "GET", @@ -496,7 +521,10 @@ def test_span_to_envelope_client_http(self): "http.target": "/path/12314/?q=ddds#123", } envelope = exporter._span_to_envelope(span) - self.assertEqual(envelope.data.base_data.data, "https://192.168.0.1:8080/path/12314/?q=ddds#123") + self.assertEqual( + envelope.data.base_data.data, + "https://192.168.0.1:8080/path/12314/?q=ddds#123", + ) # Stable semconv span._attributes = { @@ -504,7 +532,10 @@ def test_span_to_envelope_client_http(self): "url.full": "https://www.wikipedia.org/path/12314/?q=ddds#124", } envelope = exporter._span_to_envelope(span) - self.assertEqual(envelope.data.base_data.data, "https://www.wikipedia.org/path/12314/?q=ddds#124") + self.assertEqual( + envelope.data.base_data.data, + "https://www.wikipedia.org/path/12314/?q=ddds#124", + ) # result_code span._attributes = { @@ -1188,7 +1219,10 @@ def test_span_envelope_server_http(self): self.assertEqual(envelope.tags[ContextTagKeys.AI_LOCATION_IP], "peer_ip") ## Stable http semconv - span._attributes = {"http.request.method": "GET", "client.address": "client_address"} + span._attributes = { + "http.request.method": "GET", + "client.address": "client_address", + } envelope = exporter._span_to_envelope(span) self.assertEqual(envelope.tags[ContextTagKeys.AI_LOCATION_IP], "client_address") @@ -1491,7 +1525,10 @@ def test_span_to_envelope_properties_std_metrics(self): span.end(end_time=end_time) envelope = exporter._span_to_envelope(span) self.assertEqual(len(envelope.data.base_data.properties), 1) - self.assertEqual(envelope.data.base_data.properties["_MS.ProcessedByMetricExtractors"], "True") + self.assertEqual( + envelope.data.base_data.properties["_MS.ProcessedByMetricExtractors"], + "True", + ) def test_span_events_to_envelopes_exception(self): exporter = self._exporter @@ -1529,21 +1566,28 @@ def test_span_events_to_envelopes_exception(self): self.assertEqual(envelope.instrumentation_key, "1234abcd-5678-4efa-8abc-1234567890ab") self.assertIsNotNone(envelope.tags) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_DEVICE_ID), azure_monitor_context[ContextTagKeys.AI_DEVICE_ID] + envelope.tags.get(ContextTagKeys.AI_DEVICE_ID), + azure_monitor_context[ContextTagKeys.AI_DEVICE_ID], ) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_DEVICE_LOCALE), azure_monitor_context[ContextTagKeys.AI_DEVICE_LOCALE] + envelope.tags.get(ContextTagKeys.AI_DEVICE_LOCALE), + azure_monitor_context[ContextTagKeys.AI_DEVICE_LOCALE], ) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_DEVICE_TYPE), azure_monitor_context[ContextTagKeys.AI_DEVICE_TYPE] + envelope.tags.get(ContextTagKeys.AI_DEVICE_TYPE), + azure_monitor_context[ContextTagKeys.AI_DEVICE_TYPE], ) self.assertEqual( envelope.tags.get(ContextTagKeys.AI_INTERNAL_SDK_VERSION), azure_monitor_context[ContextTagKeys.AI_INTERNAL_SDK_VERSION], ) - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_OPERATION_ID), "{:032x}".format(span.context.trace_id)) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_OPERATION_PARENT_ID), "{:016x}".format(span.context.span_id) + envelope.tags.get(ContextTagKeys.AI_OPERATION_ID), + "{:032x}".format(span.context.trace_id), + ) + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_OPERATION_PARENT_ID), + "{:016x}".format(span.context.span_id), ) self.assertEqual(envelope.time, "2019-12-04T21:18:36.027613Z") self.assertEqual(len(envelope.data.base_data.properties), 0) @@ -1551,7 +1595,10 @@ def test_span_events_to_envelopes_exception(self): self.assertEqual(envelope.data.base_data.exceptions[0].type_name, "ZeroDivisionError") self.assertEqual(envelope.data.base_data.exceptions[0].message, "zero division error") self.assertEqual(envelope.data.base_data.exceptions[0].has_full_stack, True) - self.assertEqual(envelope.data.base_data.exceptions[0].stack, "Traceback: ZeroDivisionError, division by zero") + self.assertEqual( + envelope.data.base_data.exceptions[0].stack, + "Traceback: ZeroDivisionError, division by zero", + ) self.assertEqual(envelope.data.base_type, "ExceptionData") def test_span_events_to_envelopes_message(self): @@ -1587,21 +1634,28 @@ def test_span_events_to_envelopes_message(self): self.assertEqual(envelope.instrumentation_key, "1234abcd-5678-4efa-8abc-1234567890ab") self.assertIsNotNone(envelope.tags) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_DEVICE_ID), azure_monitor_context[ContextTagKeys.AI_DEVICE_ID] + envelope.tags.get(ContextTagKeys.AI_DEVICE_ID), + azure_monitor_context[ContextTagKeys.AI_DEVICE_ID], ) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_DEVICE_LOCALE), azure_monitor_context[ContextTagKeys.AI_DEVICE_LOCALE] + envelope.tags.get(ContextTagKeys.AI_DEVICE_LOCALE), + azure_monitor_context[ContextTagKeys.AI_DEVICE_LOCALE], ) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_DEVICE_TYPE), azure_monitor_context[ContextTagKeys.AI_DEVICE_TYPE] + envelope.tags.get(ContextTagKeys.AI_DEVICE_TYPE), + azure_monitor_context[ContextTagKeys.AI_DEVICE_TYPE], ) self.assertEqual( envelope.tags.get(ContextTagKeys.AI_INTERNAL_SDK_VERSION), azure_monitor_context[ContextTagKeys.AI_INTERNAL_SDK_VERSION], ) - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_OPERATION_ID), "{:032x}".format(span.context.trace_id)) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_OPERATION_PARENT_ID), "{:016x}".format(span.context.span_id) + envelope.tags.get(ContextTagKeys.AI_OPERATION_ID), + "{:032x}".format(span.context.trace_id), + ) + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_OPERATION_PARENT_ID), + "{:016x}".format(span.context.span_id), ) self.assertEqual(envelope.time, "2019-12-04T21:18:36.027613Z") self.assertEqual(len(envelope.data.base_data.properties), 1) @@ -1647,21 +1701,28 @@ def test_span_events_to_envelopes_sample_rate(self): self.assertEqual(envelope.instrumentation_key, "1234abcd-5678-4efa-8abc-1234567890ab") self.assertIsNotNone(envelope.tags) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_DEVICE_ID), azure_monitor_context[ContextTagKeys.AI_DEVICE_ID] + envelope.tags.get(ContextTagKeys.AI_DEVICE_ID), + azure_monitor_context[ContextTagKeys.AI_DEVICE_ID], ) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_DEVICE_LOCALE), azure_monitor_context[ContextTagKeys.AI_DEVICE_LOCALE] + envelope.tags.get(ContextTagKeys.AI_DEVICE_LOCALE), + azure_monitor_context[ContextTagKeys.AI_DEVICE_LOCALE], ) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_DEVICE_TYPE), azure_monitor_context[ContextTagKeys.AI_DEVICE_TYPE] + envelope.tags.get(ContextTagKeys.AI_DEVICE_TYPE), + azure_monitor_context[ContextTagKeys.AI_DEVICE_TYPE], ) self.assertEqual( envelope.tags.get(ContextTagKeys.AI_INTERNAL_SDK_VERSION), azure_monitor_context[ContextTagKeys.AI_INTERNAL_SDK_VERSION], ) - self.assertEqual(envelope.tags.get(ContextTagKeys.AI_OPERATION_ID), "{:032x}".format(span.context.trace_id)) self.assertEqual( - envelope.tags.get(ContextTagKeys.AI_OPERATION_PARENT_ID), "{:016x}".format(span.context.span_id) + envelope.tags.get(ContextTagKeys.AI_OPERATION_ID), + "{:032x}".format(span.context.trace_id), + ) + self.assertEqual( + envelope.tags.get(ContextTagKeys.AI_OPERATION_PARENT_ID), + "{:016x}".format(span.context.span_id), ) self.assertEqual(envelope.time, "2019-12-04T21:18:36.027613Z") self.assertEqual(len(envelope.data.base_data.properties), 1) @@ -1850,14 +1911,11 @@ def test_check_instrumentation_span_azure_sdk_get_tracer(self, mock_get_tracer_p _check_instrumentation_span(azure_sdk_span) add.assert_called_once_with(_AZURE_AI_SDK_NAME) - not_azure_sdk_span = get_azure_sdk_tracer(library_name="not-azure-foo-bar").start_span(name="test") + other_tracer = self.get_tracer_provider().get_tracer("not-azure-foo-bar") + other_span = other_tracer.start_span(name="test") with mock.patch("azure.monitor.opentelemetry.exporter._utils.add_instrumentation") as add: - _check_instrumentation_span(not_azure_sdk_span) + _check_instrumentation_span(other_span) add.assert_not_called() def get_tracer_provider(self): - tracer_provider = TracerProvider() - span_exporter = InMemorySpanExporter() - processor = SimpleSpanProcessor(span_exporter) - tracer_provider.add_span_processor(processor) - return tracer_provider + return trace.TracerProvider()