Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## 1.0.0b47 (Unreleased)

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
INSTRUMENTATION_KEY = "instrumentationkey"
# cspell:disable-next-line
AAD_AUDIENCE = "aadaudience"
APPLICATION_ID = "applicationid" # cspell:disable-line

# Validate UUID format
# Specs taken from https://tools.ietf.org/html/rfc4122
Expand Down Expand Up @@ -38,6 +39,7 @@ def __init__(self, connection_string: typing.Optional[str] = None) -> None:
self._connection_string = connection_string
self.aad_audience = ""
self.region = ""
self.application_id = ""
self._initialize()
self._validate_instrumentation_key()

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

# Extract application_id
self.application_id = code_cs.get(APPLICATION_ID) or env_cs.get(APPLICATION_ID) # type: ignore

def _extract_region(self) -> typing.Optional[str]:
"""Extract region from endpoint URL.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,4 +336,7 @@ class _RP_Names(Enum):
# Default message for messages(MessageData) with empty body
_DEFAULT_LOG_MESSAGE = "n/a"

# Resource attribute applicationId
_APPLICATION_ID_RESOURCE_KEY = "microsoft.applicationId"

# cSpell:disable
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
TelemetryItem,
)
from azure.monitor.opentelemetry.exporter._version import VERSION as ext_version
from azure.monitor.opentelemetry.exporter._connection_string_parser import ConnectionStringParser
from azure.monitor.opentelemetry.exporter._constants import (
_AKS_ARM_NAMESPACE_ID,
_DEFAULT_AAD_SCOPE,
Expand Down Expand Up @@ -438,3 +439,8 @@ def get_compute_type():

def _get_sha256_hash(input_str: str) -> str:
return hashlib.sha256(input_str.encode("utf-8")).hexdigest()


def _get_application_id(connection_string: Optional[str]) -> Optional[str]:
parsed_connection_string = ConnectionStringParser(connection_string)
return parsed_connection_string.application_id
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import json
import logging
from time import time_ns
from typing import no_type_check, Any, Dict, List, Sequence
from typing import no_type_check, Any, Dict, List, Sequence, Optional
from urllib.parse import urlparse

from opentelemetry.semconv.attributes.client_attributes import CLIENT_ADDRESS
Expand Down Expand Up @@ -38,6 +38,7 @@
_REQUEST_ENVELOPE_NAME,
_EXCEPTION_ENVELOPE_NAME,
_REMOTE_DEPENDENCY_ENVELOPE_NAME,
_APPLICATION_ID_RESOURCE_KEY,
)
from azure.monitor.opentelemetry.exporter import _utils
from azure.monitor.opentelemetry.exporter._generated.models import (
Expand Down Expand Up @@ -124,6 +125,7 @@ class AzureMonitorTraceExporter(BaseExporter, SpanExporter):
def __init__(self, **kwargs: Any):
self._tracer_provider = kwargs.pop("tracer_provider", None)
super().__init__(**kwargs)
self.application_id = _utils._get_application_id(self._connection_string)

def export(self, spans: Sequence[ReadableSpan], **_kwargs: Any) -> SpanExportResult:
"""Export span data.
Expand All @@ -134,12 +136,13 @@ def export(self, spans: Sequence[ReadableSpan], **_kwargs: Any) -> SpanExportRes
:rtype: ~opentelemetry.sdk.trace.export.SpanExportResult
"""
envelopes = []

if spans and self._should_collect_otel_resource_metric():
resource = None
try:
tracer_provider = self._tracer_provider or get_tracer_provider()
resource = tracer_provider.resource # type: ignore
envelopes.append(self._get_otel_resource_envelope(resource))
envelopes.append(self._get_otel_resource_envelope(resource, self.application_id))
except AttributeError as e:
_logger.exception("Failed to derive Resource from Tracer Provider: %s", e) # pylint: disable=C4769
for span in spans:
Expand All @@ -162,14 +165,18 @@ def shutdown(self) -> None:
self.storage.close()

# pylint: disable=protected-access
def _get_otel_resource_envelope(self, resource: Resource) -> TelemetryItem:
attributes: Dict[str, str] = {}
def _get_otel_resource_envelope(self, resource: Resource, application_id: Optional[str]) -> TelemetryItem:
attributes: Dict[str, Any] = {}
if resource:
attributes = resource.attributes
attributes = dict(resource.attributes)
envelope = _utils._create_telemetry_item(time_ns())
envelope.name = _METRIC_ENVELOPE_NAME
envelope.tags.update(_utils._populate_part_a_fields(resource)) # pylint: disable=W0212
envelope.instrumentation_key = self._instrumentation_key

if application_id and attributes.get(_APPLICATION_ID_RESOURCE_KEY) is None:
attributes[_APPLICATION_ID_RESOURCE_KEY] = application_id

data_point = MetricDataPoint(
name="_OTELRESOURCE_"[:1024],
value=0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
_APPLICATION_INSIGHTS_EVENT_MARKER_ATTRIBUTE,
_MICROSOFT_CUSTOM_EVENT_NAME,
_DEFAULT_LOG_MESSAGE,
_APPLICATION_ID_RESOURCE_KEY,
)
from azure.monitor.opentelemetry.exporter._generated.models import ContextTagKeys
from azure.monitor.opentelemetry.exporter._utils import (
Expand Down Expand Up @@ -128,7 +129,7 @@ def setUpClass(cls):
body=None,
attributes={"test": "attribute"},
),
resource=Resource.create(attributes={"asd": "test_resource"}),
resource=Resource.create(attributes={"asd": "test_resource", "microsoft.applicationId": "app_id"}),
instrumentation_scope=InstrumentationScope("test_name"),
)
cls._log_data_complex_body = _logs.ReadWriteLogRecord(
Expand Down Expand Up @@ -470,6 +471,7 @@ def test_log_to_envelope_log_none(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.data.base_data.properties.get(_APPLICATION_ID_RESOURCE_KEY), None)

def test_log_to_envelope_log_empty(self):
exporter = self._exporter
Expand Down Expand Up @@ -783,7 +785,6 @@ def test_get_severity_level(self):
for sev_num in SeverityNumber:
num = sev_num.value
level = _get_severity_level(sev_num)
print(num)
if num in range(0, 9):
self.assertEqual(level, 0)
elif num in range(9, 13):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,3 +357,21 @@ def test_region_extraction_alphanumeric_regions(self):
+ f";IngestionEndpoint=https://{endpoint_suffix}"
)
self.assertEqual(parser.region, expected_region)

def test_application_id_extraction_from_connection_string(self):
parser = ConnectionStringParser(
connection_string="InstrumentationKey="
+ self._valid_instrumentation_key
+ ";IngestionEndpoint=https://northeurope-999.in.applicationinsights.azure.com/"
+ ";LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=3cd3dd3f-64cc-4d7c-9303-8d69a4bb8558"
)
self.assertEqual(parser.application_id, "3cd3dd3f-64cc-4d7c-9303-8d69a4bb8558")

def test_application_id_extraction_from_no_application_id(self):
parser = ConnectionStringParser(
connection_string="InstrumentationKey="
+ self._valid_instrumentation_key
+ ";IngestionEndpoint=https://northeurope-999.in.applicationinsights.azure.com/"
+ ";LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/"
)
self.assertEqual(parser.application_id, None)
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
_AZURE_SDK_NAMESPACE_NAME,
_AZURE_SDK_OPENTELEMETRY_NAME,
_AZURE_AI_SDK_NAME,
_APPLICATION_ID_RESOURCE_KEY,
)
from azure.monitor.opentelemetry.exporter._generated.models import ContextTagKeys
from azure.monitor.opentelemetry.exporter._utils import azure_monitor_context
Expand Down Expand Up @@ -133,6 +134,7 @@ def test_export_failure(self):
transmit.return_value = ExportResult.FAILED_RETRYABLE
storage_mock = mock.Mock()
exporter.storage.put = storage_mock
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"
result = exporter.export([test_span])
self.assertEqual(result, SpanExportResult.FAILURE)
self.assertEqual(storage_mock.call_count, 1)
Expand Down Expand Up @@ -188,7 +190,7 @@ def test_export_with_tracer_provider(self):
"azure.monitor.opentelemetry.exporter.AzureMonitorTraceExporter._get_otel_resource_envelope"
) as resource_patch: # noqa: E501
result = exporter.export([test_span])
resource_patch.assert_called_once_with(mock_resource)
resource_patch.assert_called_once_with(mock_resource, None)
self.assertEqual(result, SpanExportResult.SUCCESS)
self.assertEqual(storage_mock.call_count, 1)

Expand Down Expand Up @@ -221,7 +223,7 @@ def test_export_with_tracer_provider_global(self):
"azure.monitor.opentelemetry.exporter.AzureMonitorTraceExporter._get_otel_resource_envelope"
) as resource_patch: # noqa: E501
result = exporter.export([test_span])
resource_patch.assert_called_once_with(mock_resource)
resource_patch.assert_called_once_with(mock_resource, None)
self.assertEqual(result, SpanExportResult.SUCCESS)
self.assertEqual(storage_mock.call_count, 1)

Expand Down Expand Up @@ -1768,7 +1770,7 @@ def test_export_otel_resource_metric(self, mock_get_tracer_provider):
mock_get_otel_resource_envelope.return_value = "test_envelope"
result = exporter.export([test_span])
self.assertEqual(result, SpanExportResult.SUCCESS)
mock_get_otel_resource_envelope.assert_called_once_with(test_resource)
mock_get_otel_resource_envelope.assert_called_once_with(test_resource, None)
envelopes = ["test_envelope", exporter._span_to_envelope(test_span)]
transmit.assert_called_once_with(envelopes)

Expand All @@ -1781,9 +1783,10 @@ def test_get_otel_resource_envelope(self):
"bool_test_key": False,
"float_test_key": 0.5,
"sequence_test_key": ["a", "b"],
"microsoft.applicationId": "test_app_id",
}
)
envelope = exporter._get_otel_resource_envelope(test_resource)
envelope = exporter._get_otel_resource_envelope(test_resource, "test_app_id")
metric_name = envelope.name
self.assertEqual(metric_name, "Microsoft.ApplicationInsights.Metric")
instrumentation_key = envelope.instrumentation_key
Expand Down
Loading