Skip to content

Commit d88d394

Browse files
lmolkovaCopilotlzchen
authored
Distinguish Azure AI SDKs in statsbeats (#42016)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Leighton Chen <lechen@microsoft.com>
1 parent e7074f1 commit d88d394

File tree

7 files changed

+125
-11
lines changed

7 files changed

+125
-11
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ class _RP_Names(Enum):
194194
# Special constant for azure-sdk opentelemetry instrumentation
195195
_AZURE_SDK_OPENTELEMETRY_NAME = "azure-sdk-opentelemetry"
196196
_AZURE_SDK_NAMESPACE_NAME = "az.namespace"
197+
_AZURE_AI_SDK_NAME = "azure-ai-opentelemetry"
197198

198199
_BASE = 2
199200

@@ -253,6 +254,7 @@ class _RP_Names(Enum):
253254
"openai_v2",
254255
"vertexai",
255256
# Instrumentations below this line have not been added to statsbeat report yet
257+
_AZURE_AI_SDK_NAME
256258
]
257259

258260
_INSTRUMENTATIONS_BIT_MAP = {_INSTRUMENTATIONS_LIST[i]: _BASE**i for i in range(len(_INSTRUMENTATIONS_LIST))}

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_quickpulse/_processor.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,22 @@
99

1010
# pylint: disable=protected-access
1111
class _QuickpulseLogRecordProcessor(LogRecordProcessor):
12+
def __init__(self):
13+
super().__init__()
14+
self.call_on_emit = hasattr(super(), 'on_emit')
1215

13-
def emit(self, log_data: LogData) -> None: # type: ignore
16+
def on_emit(self, log_data: LogData) -> None: # type: ignore
1417
qpm = _QuickpulseManager._instance
1518
if qpm:
1619
qpm._record_log_record(log_data)
17-
super().emit(log_data) # type: ignore[safe-super]
20+
if self.call_on_emit:
21+
super().on_emit(log_data) # type: ignore[safe-super]
22+
else:
23+
# this method was removed in opentelemetry-sdk and replaced with on_emit
24+
super().emit(log_data) # type: ignore[safe-super,misc] # pylint: disable=no-member
25+
26+
def emit(self, log_data: LogData) -> None:
27+
self.on_emit(log_data)
1828

1929
def shutdown(self):
2030
pass

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ def _getlocale():
124124
# by continuing to use getdefaultlocale() even though it has been deprecated.
125125
# we ignore the deprecation warnings to reduce noise
126126
warnings.simplefilter("ignore", category=DeprecationWarning)
127+
# pylint: disable=deprecated-method
127128
return locale.getdefaultlocale()[0]
128129
except AttributeError:
129130
# locale.getlocal() has issues on Windows: https://github.com/python/cpython/issues/82986

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
_APPLICATIONINSIGHTS_OPENTELEMETRY_RESOURCE_METRIC_DISABLED,
2424
_AZURE_SDK_NAMESPACE_NAME,
2525
_AZURE_SDK_OPENTELEMETRY_NAME,
26+
_AZURE_AI_SDK_NAME,
2627
_INSTRUMENTATION_SUPPORTING_METRICS_LIST,
2728
_SAMPLE_RATE_KEY,
2829
_METRIC_ENVELOPE_NAME,
@@ -525,13 +526,18 @@ def _convert_span_events_to_envelopes(span: ReadableSpan) -> Sequence[TelemetryI
525526

526527

527528
def _check_instrumentation_span(span: ReadableSpan) -> None:
528-
# Special use-case for spans generated from azure-sdk services
529-
# Identified by having az.namespace as a span attribute
530-
if span.attributes and _AZURE_SDK_NAMESPACE_NAME in span.attributes:
531-
_utils.add_instrumentation(_AZURE_SDK_OPENTELEMETRY_NAME)
532-
return
533529
if span.instrumentation_scope is None:
534530
return
531+
532+
# Special use-case for spans generated from azure-sdk services
533+
# `azure-` or `azure.` is a prefix
534+
if span.instrumentation_scope.name.startswith("azure"):
535+
# spec-case for Azure AI SDKs - identified by `az.namespace` attribute
536+
if span.attributes and span.attributes.get(_AZURE_SDK_NAMESPACE_NAME) == "Microsoft.CognitiveServices":
537+
_utils.add_instrumentation(_AZURE_AI_SDK_NAME)
538+
else:
539+
_utils.add_instrumentation(_AZURE_SDK_OPENTELEMETRY_NAME)
540+
return
535541
# All instrumentation scope names from OpenTelemetry instrumentations have
536542
# `opentelemetry.instrumentation.` as a prefix
537543
if span.instrumentation_scope.name.startswith("opentelemetry.instrumentation."):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
-e ../../../tools/azure-sdk-tools
22
../../core/azure-core
3+
../../core/azure-core-tracing-opentelemetry
34
-e ../../identity/azure-identity
45
aiohttp>=3.0; python_version >= '3.7'

sdk/monitor/azure-monitor-opentelemetry-exporter/tests/quickpulse/test_processor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def tearDownClass(cls) -> None:
2323
def test_emit(self):
2424
processor = _QuickpulseLogRecordProcessor()
2525
log_data = mock.Mock()
26-
processor.emit(log_data)
26+
processor.on_emit(log_data)
2727
self.qpm._record_log_record.assert_called_once_with(log_data)
2828

2929

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

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
# pylint: disable=import-error
1212
from opentelemetry.trace import get_tracer_provider, set_tracer_provider
1313
from opentelemetry.sdk import trace, resources
14+
from opentelemetry.sdk.trace import TracerProvider
15+
1416
from opentelemetry.sdk.trace.export import SpanExportResult
17+
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
18+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
1519
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
1620
from opentelemetry.semconv.attributes.exception_attributes import (
1721
EXCEPTION_ESCAPED,
@@ -22,6 +26,14 @@
2226
from opentelemetry.trace import Link, SpanContext, SpanKind
2327
from opentelemetry.trace.status import Status, StatusCode
2428

29+
from azure.core.settings import settings
30+
from azure.core.tracing.ext.opentelemetry_span import OpenTelemetrySpan
31+
32+
try:
33+
from azure.core.instrumentation import get_tracer as get_azure_sdk_tracer
34+
except ImportError:
35+
# azure.core.instrumentation is not available in older versions of azure-core
36+
get_azure_sdk_tracer = None
2537
from azure.monitor.opentelemetry.exporter.export._base import ExportResult
2638
from azure.monitor.opentelemetry.exporter.export.trace._exporter import (
2739
AzureMonitorTraceExporter,
@@ -31,6 +43,7 @@
3143
from azure.monitor.opentelemetry.exporter._constants import (
3244
_AZURE_SDK_NAMESPACE_NAME,
3345
_AZURE_SDK_OPENTELEMETRY_NAME,
46+
_AZURE_AI_SDK_NAME,
3447
)
3548
from azure.monitor.opentelemetry.exporter._generated.models import ContextTagKeys
3649
from azure.monitor.opentelemetry.exporter._utils import azure_monitor_context
@@ -1128,7 +1141,7 @@ def test_span_envelope_server_http(self):
11281141
"url.path": "/path",
11291142
"url.query": "query",
11301143
"server.address": "www.example.org",
1131-
"server.port": "80"
1144+
"server.port": "80",
11321145
}
11331146
envelope = exporter._span_to_envelope(span)
11341147
self.assertEqual(envelope.data.base_data.url, "https://www.example.org:80/path?query")
@@ -1687,8 +1700,89 @@ def test_check_instrumentation_span_not_instrumentation(self):
16871700

16881701
def test_check_instrumentation_span_azure_sdk(self):
16891702
span = mock.Mock()
1690-
span.attributes = {_AZURE_SDK_NAMESPACE_NAME: "Microsoft.EventHub"}
1691-
span.instrumentation_scope.name = "__main__"
1703+
span.attributes = {}
1704+
span.instrumentation_scope.name = "azure.foo.bar.__init__"
1705+
16921706
with mock.patch("azure.monitor.opentelemetry.exporter._utils.add_instrumentation") as add:
16931707
_check_instrumentation_span(span)
16941708
add.assert_called_once_with(_AZURE_SDK_OPENTELEMETRY_NAME)
1709+
1710+
@mock.patch("opentelemetry.trace.get_tracer_provider")
1711+
def test_check_instrumentation_span_azure_sdk_otel_span(self, mock_get_tracer_provider):
1712+
mock_get_tracer_provider.return_value = self.get_tracer_provider()
1713+
1714+
with OpenTelemetrySpan() as azure_sdk_span:
1715+
with mock.patch("azure.monitor.opentelemetry.exporter._utils.add_instrumentation") as add:
1716+
_check_instrumentation_span(azure_sdk_span.span_instance)
1717+
add.assert_called_once_with(_AZURE_SDK_OPENTELEMETRY_NAME)
1718+
1719+
with mock.patch("azure.monitor.opentelemetry.exporter._utils.add_instrumentation") as add:
1720+
azure_sdk_span.add_attribute(_AZURE_SDK_NAMESPACE_NAME, "Microsoft.ServiceBus")
1721+
_check_instrumentation_span(azure_sdk_span.span_instance)
1722+
add.assert_called_once_with(_AZURE_SDK_OPENTELEMETRY_NAME)
1723+
1724+
with mock.patch("azure.monitor.opentelemetry.exporter._utils.add_instrumentation") as add:
1725+
azure_sdk_span.add_attribute(_AZURE_SDK_NAMESPACE_NAME, "Microsoft.CognitiveServices")
1726+
_check_instrumentation_span(azure_sdk_span.span_instance)
1727+
add.assert_called_once_with(_AZURE_AI_SDK_NAME)
1728+
1729+
@mock.patch("opentelemetry.trace.get_tracer_provider")
1730+
def test_check_instrumentation_span_azure_sdk_span_impl(self, mock_get_tracer_provider):
1731+
mock_get_tracer_provider.return_value = self.get_tracer_provider()
1732+
1733+
settings.tracing_implementation = "opentelemetry"
1734+
try:
1735+
azure_sdk_span = settings.tracing_implementation()(name="test")
1736+
1737+
with mock.patch("azure.monitor.opentelemetry.exporter._utils.add_instrumentation") as add:
1738+
_check_instrumentation_span(azure_sdk_span.span_instance)
1739+
add.assert_called_once_with(_AZURE_SDK_OPENTELEMETRY_NAME)
1740+
1741+
with mock.patch("azure.monitor.opentelemetry.exporter._utils.add_instrumentation") as add:
1742+
azure_sdk_span.add_attribute(_AZURE_SDK_NAMESPACE_NAME, "Microsoft.ServiceBus")
1743+
_check_instrumentation_span(azure_sdk_span.span_instance)
1744+
add.assert_called_once_with(_AZURE_SDK_OPENTELEMETRY_NAME)
1745+
1746+
with mock.patch("azure.monitor.opentelemetry.exporter._utils.add_instrumentation") as add:
1747+
azure_sdk_span.add_attribute(_AZURE_SDK_NAMESPACE_NAME, "Microsoft.CognitiveServices")
1748+
_check_instrumentation_span(azure_sdk_span.span_instance)
1749+
add.assert_called_once_with(_AZURE_AI_SDK_NAME)
1750+
1751+
finally:
1752+
settings.tracing_implementation = None
1753+
1754+
@mock.patch("opentelemetry.trace.get_tracer_provider")
1755+
def test_check_instrumentation_span_azure_sdk_get_tracer(self, mock_get_tracer_provider):
1756+
mock_get_tracer_provider.return_value = self.get_tracer_provider()
1757+
1758+
if not get_azure_sdk_tracer:
1759+
self.skipTest("azure.core.instrumentation is not available")
1760+
1761+
azure_sdk_tracer = get_azure_sdk_tracer(library_name="azure-foo-bar")
1762+
azure_sdk_span = azure_sdk_tracer.start_span(name="test")
1763+
1764+
with mock.patch("azure.monitor.opentelemetry.exporter._utils.add_instrumentation") as add:
1765+
_check_instrumentation_span(azure_sdk_span)
1766+
add.assert_called_once_with(_AZURE_SDK_OPENTELEMETRY_NAME)
1767+
1768+
with mock.patch("azure.monitor.opentelemetry.exporter._utils.add_instrumentation") as add:
1769+
azure_sdk_span.set_attribute(_AZURE_SDK_NAMESPACE_NAME, "Microsoft.ServiceBus")
1770+
_check_instrumentation_span(azure_sdk_span)
1771+
add.assert_called_once_with(_AZURE_SDK_OPENTELEMETRY_NAME)
1772+
1773+
with mock.patch("azure.monitor.opentelemetry.exporter._utils.add_instrumentation") as add:
1774+
azure_sdk_span.set_attribute(_AZURE_SDK_NAMESPACE_NAME, "Microsoft.CognitiveServices")
1775+
_check_instrumentation_span(azure_sdk_span)
1776+
add.assert_called_once_with(_AZURE_AI_SDK_NAME)
1777+
1778+
not_azure_sdk_span = get_azure_sdk_tracer(library_name="not-azure-foo-bar").start_span(name="test")
1779+
with mock.patch("azure.monitor.opentelemetry.exporter._utils.add_instrumentation") as add:
1780+
_check_instrumentation_span(not_azure_sdk_span)
1781+
add.assert_not_called()
1782+
1783+
def get_tracer_provider(self):
1784+
tracer_provider = TracerProvider()
1785+
span_exporter = InMemorySpanExporter()
1786+
processor = SimpleSpanProcessor(span_exporter)
1787+
tracer_provider.add_span_processor(processor)
1788+
return tracer_provider

0 commit comments

Comments
 (0)