Skip to content

Commit 9908891

Browse files
authored
Defensively import and vendor experimental components (Azure#41245)
1 parent 37c99f0 commit 9908891

File tree

4 files changed

+258
-128
lines changed

4 files changed

+258
-128
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313

1414
### Other Changes
1515

16+
- Defensively import and vendor experimental components from OpenTelemetry and azure.core + upgrade dependencies to latest OpenTelemetry
17+
([#41245](https://github.com/Azure/azure-sdk-for-python/pull/41245))
18+
1619
## 1.6.9 (2025-05-12)
1720

1821
### Features Added

sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py

Lines changed: 83 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,10 @@
77
from logging import getLogger, Formatter
88
from typing import Dict, List, cast
99

10-
from opentelemetry._events import _set_event_logger_provider
11-
from opentelemetry._logs import set_logger_provider
12-
from opentelemetry.instrumentation.dependencies import (
13-
get_dist_dependency_conflicts,
14-
)
1510
from opentelemetry.instrumentation.instrumentor import ( # type: ignore
1611
BaseInstrumentor,
1712
)
1813
from opentelemetry.metrics import set_meter_provider
19-
from opentelemetry.sdk._events import EventLoggerProvider
20-
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
21-
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
2214
from opentelemetry.sdk.metrics import MeterProvider
2315
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
2416
from opentelemetry.sdk.metrics.view import View
@@ -32,8 +24,6 @@
3224
entry_points,
3325
)
3426

35-
from azure.core.settings import settings
36-
from azure.core.tracing.ext.opentelemetry_span import OpenTelemetrySpan
3727
from azure.monitor.opentelemetry._constants import (
3828
_ALL_SUPPORTED_INSTRUMENTED_LIBRARIES,
3929
_AZURE_SDK_INSTRUMENTATION_NAME,
@@ -58,7 +48,6 @@
5848
)
5949
from azure.monitor.opentelemetry.exporter import ( # pylint: disable=import-error,no-name-in-module
6050
ApplicationInsightsSampler,
61-
AzureMonitorLogExporter,
6251
AzureMonitorMetricExporter,
6352
AzureMonitorTraceExporter,
6453
)
@@ -74,6 +63,9 @@
7463
_get_configurations,
7564
_is_instrumentation_enabled,
7665
)
66+
from azure.monitor.opentelemetry._utils.instrumentation import (
67+
get_dist_dependency_conflicts,
68+
)
7769

7870
_logger = getLogger(__name__)
7971

@@ -156,42 +148,91 @@ def _setup_tracing(configurations: Dict[str, ConfigurationValue]):
156148
)
157149
tracer_provider.add_span_processor(bsp)
158150
set_tracer_provider(tracer_provider)
151+
159152
if _is_instrumentation_enabled(configurations, _AZURE_SDK_INSTRUMENTATION_NAME):
160-
settings.tracing_implementation = OpenTelemetrySpan
153+
try:
154+
from azure.core.settings import settings
155+
from azure.core.tracing.ext.opentelemetry_span import OpenTelemetrySpan
156+
157+
settings.tracing_implementation = OpenTelemetrySpan
158+
except ImportError as ex:
159+
# This could possibly be due to breaking change in upstream OpenTelemetry
160+
# Advise user to upgrade to latest OpenTelemetry version
161+
_logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug
162+
"Exception occurred when importing Azure SDK Tracing." \
163+
"Please upgrade to the latest OpenTelemetry version: %s.",
164+
ex,
165+
)
166+
except Exception as ex: # pylint: disable=broad-except
167+
_logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug
168+
"Exception occurred when setting Azure SDK Tracing: %s.",
169+
ex,
170+
)
161171

162172

163173
def _setup_logging(configurations: Dict[str, ConfigurationValue]):
164-
resource: Resource = configurations[RESOURCE_ARG] # type: ignore
165-
logger_provider = LoggerProvider(resource=resource)
166-
if configurations.get(ENABLE_LIVE_METRICS_ARG):
167-
qlp = _QuickpulseLogRecordProcessor()
168-
logger_provider.add_log_record_processor(qlp)
169-
log_exporter = AzureMonitorLogExporter(**configurations)
170-
log_record_processor = BatchLogRecordProcessor(
171-
log_exporter,
172-
)
173-
logger_provider.add_log_record_processor(log_record_processor)
174-
set_logger_provider(logger_provider)
175-
logger_name: str = configurations[LOGGER_NAME_ARG] # type: ignore
176-
logging_formatter: Formatter = configurations[LOGGING_FORMATTER_ARG] # type: ignore
177-
logger = getLogger(logger_name)
178-
# Only add OpenTelemetry LoggingHandler if logger does not already have the handler
179-
# This is to prevent most duplicate logging telemetry
180-
if not any(isinstance(handler, LoggingHandler) for handler in logger.handlers):
181-
handler = LoggingHandler(logger_provider=logger_provider)
182-
if logging_formatter:
183-
try:
184-
handler.setFormatter(logging_formatter)
185-
except Exception as ex: # pylint: disable=broad-except
186-
_logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug
187-
"Exception occurred when adding logging Formatter: %s.",
188-
ex,
189-
)
190-
logger.addHandler(handler)
174+
# Setup logging
175+
# Use try catch while signal is experimental
176+
try:
177+
from opentelemetry._logs import set_logger_provider
178+
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
179+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
180+
181+
from azure.monitor.opentelemetry.exporter import ( # pylint: disable=import-error,no-name-in-module
182+
AzureMonitorLogExporter
183+
)
191184

192-
# Setup EventLoggerProvider
193-
event_provider = EventLoggerProvider(logger_provider)
194-
_set_event_logger_provider(event_provider, False)
185+
resource: Resource = configurations[RESOURCE_ARG] # type: ignore
186+
logger_provider = LoggerProvider(resource=resource)
187+
if configurations.get(ENABLE_LIVE_METRICS_ARG):
188+
qlp = _QuickpulseLogRecordProcessor()
189+
logger_provider.add_log_record_processor(qlp)
190+
log_exporter = AzureMonitorLogExporter(**configurations)
191+
log_record_processor = BatchLogRecordProcessor(
192+
log_exporter,
193+
)
194+
logger_provider.add_log_record_processor(log_record_processor)
195+
set_logger_provider(logger_provider)
196+
logger_name: str = configurations[LOGGER_NAME_ARG] # type: ignore
197+
logging_formatter: Formatter = configurations[LOGGING_FORMATTER_ARG] # type: ignore
198+
logger = getLogger(logger_name)
199+
# Only add OpenTelemetry LoggingHandler if logger does not already have the handler
200+
# This is to prevent most duplicate logging telemetry
201+
if not any(isinstance(handler, LoggingHandler) for handler in logger.handlers):
202+
handler = LoggingHandler(logger_provider=logger_provider)
203+
if logging_formatter:
204+
try:
205+
handler.setFormatter(logging_formatter)
206+
except Exception as ex: # pylint: disable=broad-except
207+
_logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug
208+
"Exception occurred when adding logging Formatter: %s.",
209+
ex,
210+
)
211+
logger.addHandler(handler)
212+
213+
# Setup Events
214+
try:
215+
from opentelemetry._events import _set_event_logger_provider
216+
from opentelemetry.sdk._events import EventLoggerProvider
217+
218+
event_provider = EventLoggerProvider(logger_provider)
219+
_set_event_logger_provider(event_provider, False)
220+
except ImportError as ex:
221+
# If the events is not available, we will not set it up.
222+
# This could possibly be due to breaking change in upstream OpenTelemetry
223+
# Advise user to upgrade to latest OpenTelemetry version
224+
_logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug
225+
"Exception occurred when setting up Events. Please upgrade to the latest OpenTelemetry version: %s.",
226+
ex,
227+
)
228+
except ImportError as ex:
229+
# If the events is not available, we will not set it up.
230+
# This could possibly be due to breaking change in upstream OpenTelemetry
231+
# Advise user to upgrade to latest OpenTelemetry version
232+
_logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug
233+
"Exception occurred when setting up Logging. Please upgrade to the latest OpenTelemetry version: %s.",
234+
ex,
235+
)
195236

196237

197238
def _setup_metrics(configurations: Dict[str, ConfigurationValue]):
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# -------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License in the project root for
4+
# license information.
5+
# --------------------------------------------------------------------------
6+
7+
from __future__ import annotations
8+
9+
from logging import getLogger
10+
from typing import Collection
11+
12+
from packaging.requirements import InvalidRequirement, Requirement
13+
14+
from opentelemetry.util._importlib_metadata import (
15+
Distribution,
16+
PackageNotFoundError,
17+
version,
18+
)
19+
20+
logger = getLogger(__name__)
21+
22+
# --------------------Instrumentation------------------------------
23+
24+
# The below classes/functions are vendored from upstream OpenTelemetry Python due to being experimental.
25+
# Due to possible breaking changes in the upstream code, we have decided to vendor this code
26+
27+
# TODO: Remove this vendored code once the upstream code is stable and released.
28+
# Original breaking PR: https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3202
29+
# Tracking issue: https://github.com/open-telemetry/opentelemetry-python-contrib/issues/3434
30+
31+
class DependencyConflict:
32+
required: str | None = None
33+
found: str | None = None
34+
35+
def __init__(self, required: str | None, found: str | None = None):
36+
self.required = required
37+
self.found = found
38+
39+
def __str__(self):
40+
return f'DependencyConflict: requested: "{self.required}" but found: "{self.found}"'
41+
42+
43+
def get_dist_dependency_conflicts(
44+
dist: Distribution,
45+
) -> DependencyConflict | None:
46+
instrumentation_deps = []
47+
extra = "extra"
48+
instruments = "instruments"
49+
instruments_marker = {extra: instruments}
50+
if dist.requires:
51+
for dep in dist.requires:
52+
if extra not in dep or instruments not in dep:
53+
continue
54+
55+
req = Requirement(dep)
56+
if req.marker.evaluate(instruments_marker): # type: ignore
57+
instrumentation_deps.append(req)
58+
59+
return get_dependency_conflicts(instrumentation_deps)
60+
61+
62+
def get_dependency_conflicts(
63+
deps: Collection[str | Requirement],
64+
) -> DependencyConflict | None:
65+
for dep in deps:
66+
if isinstance(dep, Requirement):
67+
req = dep
68+
else:
69+
try:
70+
req = Requirement(dep)
71+
except InvalidRequirement as exc:
72+
logger.warning(
73+
'error parsing dependency, reporting as a conflict: "%s" - %s',
74+
dep,
75+
exc,
76+
)
77+
return DependencyConflict(dep)
78+
79+
try:
80+
dist_version = version(req.name)
81+
except PackageNotFoundError:
82+
return DependencyConflict(dep) # type: ignore
83+
84+
if not req.specifier.contains(dist_version):
85+
return DependencyConflict(dep, f"{req.name} {dist_version}") # type: ignore
86+
return None

0 commit comments

Comments
 (0)