Skip to content

Commit cef4767

Browse files
authored
Add auto detection for application ID from connection string if not set (#44644)
* Add Auto-Detection of Application ID to Resource Attributes * Fix format * Add CHANGELOG * No need for value assigning statement * Modularize fetching appid logic and fix format * Fix format * Randomize conn string
1 parent ef87280 commit cef4767

File tree

8 files changed

+56
-11
lines changed

8 files changed

+56
-11
lines changed

sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## 1.0.0b47 (Unreleased)
44

55
### Features Added
6+
- Add auto detection for application ID from connection string if not set
7+
([#44644](https://github.com/Azure/azure-sdk-for-python/pull/44644))
68
- Add support for user id and authId
79
([#44662](https://github.com/Azure/azure-sdk-for-python/pull/44662))
810

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_connection_string_parser.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
INSTRUMENTATION_KEY = "instrumentationkey"
1010
# cspell:disable-next-line
1111
AAD_AUDIENCE = "aadaudience"
12+
APPLICATION_ID = "applicationid" # cspell:disable-line
1213

1314
# Validate UUID format
1415
# Specs taken from https://tools.ietf.org/html/rfc4122
@@ -38,6 +39,7 @@ def __init__(self, connection_string: typing.Optional[str] = None) -> None:
3839
self._connection_string = connection_string
3940
self.aad_audience = ""
4041
self.region = ""
42+
self.application_id = ""
4143
self._initialize()
4244
self._validate_instrumentation_key()
4345

@@ -73,6 +75,9 @@ def _initialize(self) -> None:
7375
# Extract region information
7476
self.region = self._extract_region() # type: ignore
7577

78+
# Extract application_id
79+
self.application_id = code_cs.get(APPLICATION_ID) or env_cs.get(APPLICATION_ID) # type: ignore
80+
7681
def _extract_region(self) -> typing.Optional[str]:
7782
"""Extract region from endpoint URL.
7883

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,4 +336,7 @@ class _RP_Names(Enum):
336336
# Default message for messages(MessageData) with empty body
337337
_DEFAULT_LOG_MESSAGE = "n/a"
338338

339+
# Resource attribute applicationId
340+
_APPLICATION_ID_RESOURCE_KEY = "microsoft.applicationId"
341+
339342
# cSpell:disable

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
TelemetryItem,
2424
)
2525
from azure.monitor.opentelemetry.exporter._version import VERSION as ext_version
26+
from azure.monitor.opentelemetry.exporter._connection_string_parser import ConnectionStringParser
2627
from azure.monitor.opentelemetry.exporter._constants import (
2728
_AKS_ARM_NAMESPACE_ID,
2829
_DEFAULT_AAD_SCOPE,
@@ -438,3 +439,8 @@ def get_compute_type():
438439

439440
def _get_sha256_hash(input_str: str) -> str:
440441
return hashlib.sha256(input_str.encode("utf-8")).hexdigest()
442+
443+
444+
def _get_application_id(connection_string: Optional[str]) -> Optional[str]:
445+
parsed_connection_string = ConnectionStringParser(connection_string)
446+
return parsed_connection_string.application_id

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import json
55
import logging
66
from time import time_ns
7-
from typing import no_type_check, Any, Dict, List, Sequence
7+
from typing import no_type_check, Any, Dict, List, Sequence, Optional
88
from urllib.parse import urlparse
99

1010
from opentelemetry.semconv.attributes.client_attributes import CLIENT_ADDRESS
@@ -38,6 +38,7 @@
3838
_REQUEST_ENVELOPE_NAME,
3939
_EXCEPTION_ENVELOPE_NAME,
4040
_REMOTE_DEPENDENCY_ENVELOPE_NAME,
41+
_APPLICATION_ID_RESOURCE_KEY,
4142
)
4243
from azure.monitor.opentelemetry.exporter import _utils
4344
from azure.monitor.opentelemetry.exporter._generated.models import (
@@ -124,6 +125,7 @@ class AzureMonitorTraceExporter(BaseExporter, SpanExporter):
124125
def __init__(self, **kwargs: Any):
125126
self._tracer_provider = kwargs.pop("tracer_provider", None)
126127
super().__init__(**kwargs)
128+
self.application_id = _utils._get_application_id(self._connection_string)
127129

128130
def export(self, spans: Sequence[ReadableSpan], **_kwargs: Any) -> SpanExportResult:
129131
"""Export span data.
@@ -134,12 +136,13 @@ def export(self, spans: Sequence[ReadableSpan], **_kwargs: Any) -> SpanExportRes
134136
:rtype: ~opentelemetry.sdk.trace.export.SpanExportResult
135137
"""
136138
envelopes = []
139+
137140
if spans and self._should_collect_otel_resource_metric():
138141
resource = None
139142
try:
140143
tracer_provider = self._tracer_provider or get_tracer_provider()
141144
resource = tracer_provider.resource # type: ignore
142-
envelopes.append(self._get_otel_resource_envelope(resource))
145+
envelopes.append(self._get_otel_resource_envelope(resource, self.application_id))
143146
except AttributeError as e:
144147
_logger.exception("Failed to derive Resource from Tracer Provider: %s", e) # pylint: disable=C4769
145148
for span in spans:
@@ -162,14 +165,18 @@ def shutdown(self) -> None:
162165
self.storage.close()
163166

164167
# pylint: disable=protected-access
165-
def _get_otel_resource_envelope(self, resource: Resource) -> TelemetryItem:
166-
attributes: Dict[str, str] = {}
168+
def _get_otel_resource_envelope(self, resource: Resource, application_id: Optional[str]) -> TelemetryItem:
169+
attributes: Dict[str, Any] = {}
167170
if resource:
168-
attributes = resource.attributes
171+
attributes = dict(resource.attributes)
169172
envelope = _utils._create_telemetry_item(time_ns())
170173
envelope.name = _METRIC_ENVELOPE_NAME
171174
envelope.tags.update(_utils._populate_part_a_fields(resource)) # pylint: disable=W0212
172175
envelope.instrumentation_key = self._instrumentation_key
176+
177+
if application_id and attributes.get(_APPLICATION_ID_RESOURCE_KEY) is None:
178+
attributes[_APPLICATION_ID_RESOURCE_KEY] = application_id
179+
173180
data_point = MetricDataPoint(
174181
name="_OTELRESOURCE_"[:1024],
175182
value=0,

sdk/monitor/azure-monitor-opentelemetry-exporter/tests/logs/test_logs.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
_APPLICATION_INSIGHTS_EVENT_MARKER_ATTRIBUTE,
3333
_MICROSOFT_CUSTOM_EVENT_NAME,
3434
_DEFAULT_LOG_MESSAGE,
35+
_APPLICATION_ID_RESOURCE_KEY,
3536
)
3637
from azure.monitor.opentelemetry.exporter._generated.models import ContextTagKeys
3738
from azure.monitor.opentelemetry.exporter._utils import (
@@ -128,7 +129,7 @@ def setUpClass(cls):
128129
body=None,
129130
attributes={"test": "attribute"},
130131
),
131-
resource=Resource.create(attributes={"asd": "test_resource"}),
132+
resource=Resource.create(attributes={"asd": "test_resource", "microsoft.applicationId": "app_id"}),
132133
instrumentation_scope=InstrumentationScope("test_name"),
133134
)
134135
cls._log_data_complex_body = _logs.ReadWriteLogRecord(
@@ -470,6 +471,7 @@ def test_log_to_envelope_log_none(self):
470471
self.assertEqual(envelope.name, "Microsoft.ApplicationInsights.Message")
471472
self.assertEqual(envelope.data.base_type, "MessageData")
472473
self.assertEqual(envelope.data.base_data.message, _DEFAULT_LOG_MESSAGE)
474+
self.assertEqual(envelope.data.base_data.properties.get(_APPLICATION_ID_RESOURCE_KEY), None)
473475

474476
def test_log_to_envelope_log_empty(self):
475477
exporter = self._exporter
@@ -783,7 +785,6 @@ def test_get_severity_level(self):
783785
for sev_num in SeverityNumber:
784786
num = sev_num.value
785787
level = _get_severity_level(sev_num)
786-
print(num)
787788
if num in range(0, 9):
788789
self.assertEqual(level, 0)
789790
elif num in range(9, 13):

sdk/monitor/azure-monitor-opentelemetry-exporter/tests/test_connection_string_parser.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,3 +357,21 @@ def test_region_extraction_alphanumeric_regions(self):
357357
+ f";IngestionEndpoint=https://{endpoint_suffix}"
358358
)
359359
self.assertEqual(parser.region, expected_region)
360+
361+
def test_application_id_extraction_from_connection_string(self):
362+
parser = ConnectionStringParser(
363+
connection_string="InstrumentationKey="
364+
+ self._valid_instrumentation_key
365+
+ ";IngestionEndpoint=https://northeurope-999.in.applicationinsights.azure.com/"
366+
+ ";LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=3cd3dd3f-64cc-4d7c-9303-8d69a4bb8558"
367+
)
368+
self.assertEqual(parser.application_id, "3cd3dd3f-64cc-4d7c-9303-8d69a4bb8558")
369+
370+
def test_application_id_extraction_from_no_application_id(self):
371+
parser = ConnectionStringParser(
372+
connection_string="InstrumentationKey="
373+
+ self._valid_instrumentation_key
374+
+ ";IngestionEndpoint=https://northeurope-999.in.applicationinsights.azure.com/"
375+
+ ";LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/"
376+
)
377+
self.assertEqual(parser.application_id, None)

sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_trace.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
_AZURE_SDK_NAMESPACE_NAME,
4242
_AZURE_SDK_OPENTELEMETRY_NAME,
4343
_AZURE_AI_SDK_NAME,
44+
_APPLICATION_ID_RESOURCE_KEY,
4445
)
4546
from azure.monitor.opentelemetry.exporter._generated.models import ContextTagKeys
4647
from azure.monitor.opentelemetry.exporter._utils import azure_monitor_context
@@ -133,6 +134,7 @@ def test_export_failure(self):
133134
transmit.return_value = ExportResult.FAILED_RETRYABLE
134135
storage_mock = mock.Mock()
135136
exporter.storage.put = storage_mock
137+
exporter._connection_string = "InstrumentationKey=4321abcd-5678-4efa-8abc-1234567890ab;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=4321abcd-5678-4efa-8abc-1234567890ab"
136138
result = exporter.export([test_span])
137139
self.assertEqual(result, SpanExportResult.FAILURE)
138140
self.assertEqual(storage_mock.call_count, 1)
@@ -188,7 +190,7 @@ def test_export_with_tracer_provider(self):
188190
"azure.monitor.opentelemetry.exporter.AzureMonitorTraceExporter._get_otel_resource_envelope"
189191
) as resource_patch: # noqa: E501
190192
result = exporter.export([test_span])
191-
resource_patch.assert_called_once_with(mock_resource)
193+
resource_patch.assert_called_once_with(mock_resource, None)
192194
self.assertEqual(result, SpanExportResult.SUCCESS)
193195
self.assertEqual(storage_mock.call_count, 1)
194196

@@ -221,7 +223,7 @@ def test_export_with_tracer_provider_global(self):
221223
"azure.monitor.opentelemetry.exporter.AzureMonitorTraceExporter._get_otel_resource_envelope"
222224
) as resource_patch: # noqa: E501
223225
result = exporter.export([test_span])
224-
resource_patch.assert_called_once_with(mock_resource)
226+
resource_patch.assert_called_once_with(mock_resource, None)
225227
self.assertEqual(result, SpanExportResult.SUCCESS)
226228
self.assertEqual(storage_mock.call_count, 1)
227229

@@ -1768,7 +1770,7 @@ def test_export_otel_resource_metric(self, mock_get_tracer_provider):
17681770
mock_get_otel_resource_envelope.return_value = "test_envelope"
17691771
result = exporter.export([test_span])
17701772
self.assertEqual(result, SpanExportResult.SUCCESS)
1771-
mock_get_otel_resource_envelope.assert_called_once_with(test_resource)
1773+
mock_get_otel_resource_envelope.assert_called_once_with(test_resource, None)
17721774
envelopes = ["test_envelope", exporter._span_to_envelope(test_span)]
17731775
transmit.assert_called_once_with(envelopes)
17741776

@@ -1781,9 +1783,10 @@ def test_get_otel_resource_envelope(self):
17811783
"bool_test_key": False,
17821784
"float_test_key": 0.5,
17831785
"sequence_test_key": ["a", "b"],
1786+
"microsoft.applicationId": "test_app_id",
17841787
}
17851788
)
1786-
envelope = exporter._get_otel_resource_envelope(test_resource)
1789+
envelope = exporter._get_otel_resource_envelope(test_resource, "test_app_id")
17871790
metric_name = envelope.name
17881791
self.assertEqual(metric_name, "Microsoft.ApplicationInsights.Metric")
17891792
instrumentation_key = envelope.instrumentation_key

0 commit comments

Comments
 (0)