Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
### Features Added

### Breaking Changes
- Fix to accommodate breaking log changes from Otel
([#43626](https://github.com/Azure/azure-sdk-for-python/pull/43626))
- Pin OpenTelemetry versions to guard against upstream logging breaking changes
([#44220](https://github.com/Azure/azure-sdk-for-python/pull/44220))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from opentelemetry import metrics
from opentelemetry.metrics import CallbackOptions, Observation
from opentelemetry.sdk._logs import LogData
from opentelemetry.sdk._logs import ReadableLogRecord
from opentelemetry.sdk.trace import ReadableSpan
from opentelemetry.semconv.attributes.exception_attributes import (
EXCEPTION_MESSAGE,
Expand Down Expand Up @@ -632,13 +632,13 @@ def _record_span(self, span: ReadableSpan) -> None:
except Exception: # pylint: disable=broad-except
_logger.exception("Exception occurred while recording span.") # pylint: disable=C4769

def _record_log_record(self, log_data: LogData) -> None:
def _record_log_record(self, readable_log_record: ReadableLogRecord) -> None:
try:
# pylint: disable=global-statement
global _EXCEPTIONS_COUNT
if log_data.log_record:
if readable_log_record.log_record:
exc_type = None
log_record = log_data.log_record
log_record = readable_log_record.log_record
if log_record.attributes:
exc_type = log_record.attributes.get(EXCEPTION_TYPE)
exc_message = log_record.attributes.get(EXCEPTION_MESSAGE)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from opentelemetry.sdk._logs import LogData, LogRecordProcessor
from opentelemetry.sdk._logs import ReadableLogRecord, LogRecordProcessor
from opentelemetry.sdk.trace import ReadableSpan, SpanProcessor

from azure.monitor.opentelemetry.exporter._performance_counters._manager import _PerformanceCountersManager
Expand All @@ -13,18 +13,18 @@ def __init__(self):
super().__init__()
self.call_on_emit = hasattr(super(), 'on_emit')

def on_emit(self, log_data: LogData) -> None: # type: ignore
def on_emit(self, readable_log_record: ReadableLogRecord) -> None: # type: ignore # pylint: disable=arguments-renamed
pcm = _PerformanceCountersManager()
if pcm:
pcm._record_log_record(log_data)
pcm._record_log_record(readable_log_record)
if self.call_on_emit:
super().on_emit(log_data) # type: ignore[safe-super]
super().on_emit(readable_log_record) # type: ignore[safe-super]
else:
# this method was removed in opentelemetry-sdk and replaced with on_emit
super().emit(log_data) # type: ignore[safe-super,misc] # pylint: disable=no-member
super().emit(readable_log_record) # type: ignore[safe-super,misc] # pylint: disable=no-member

def emit(self, log_data: LogData) -> None:
self.on_emit(log_data)
def emit(self, readable_log_record: ReadableLogRecord) -> None:
self.on_emit(readable_log_record)

def shutdown(self):
pass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import psutil

from opentelemetry.sdk._logs import LogData
from opentelemetry.sdk._logs import ReadableLogRecord
from opentelemetry.sdk.metrics import MeterProvider, Meter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import ReadableSpan
Expand Down Expand Up @@ -353,7 +353,7 @@ def _record_span(self, span: ReadableSpan) -> None:
except Exception as e: # pylint: disable=broad-except
_logger.exception("Exception occurred while recording span: %s", e) # pylint: disable=C4769

def _record_log_record(self, log_data: LogData) -> None:
def _record_log_record(self, readable_log_record: ReadableLogRecord) -> None:
# Only record if in post state and manager is initialized
if not (_is_post_state() and self.is_initialized()):
return
Expand All @@ -364,9 +364,9 @@ def _record_log_record(self, log_data: LogData) -> None:
return

try:
if log_data.log_record:
if readable_log_record.log_record:
exc_type = None
log_record = log_data.log_record
log_record = readable_log_record.log_record
if log_record.attributes:
exc_type = log_record.attributes.get(SpanAttributes.EXCEPTION_TYPE)
exc_message = log_record.attributes.get(SpanAttributes.EXCEPTION_MESSAGE)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from opentelemetry.sdk._logs import LogData, LogRecordProcessor
from opentelemetry.sdk._logs import ReadableLogRecord, LogRecordProcessor
from opentelemetry.sdk.trace import ReadableSpan, SpanProcessor

from azure.monitor.opentelemetry.exporter._quickpulse._state import get_quickpulse_manager
Expand All @@ -13,18 +13,18 @@ def __init__(self):
super().__init__()
self.call_on_emit = hasattr(super(), 'on_emit')

def on_emit(self, log_data: LogData) -> None: # type: ignore
def on_emit(self, readable_log_record: ReadableLogRecord) -> None: # type: ignore # pylint: disable=arguments-renamed
qpm = get_quickpulse_manager()
if qpm:
qpm._record_log_record(log_data)
qpm._record_log_record(readable_log_record)
if self.call_on_emit:
super().on_emit(log_data) # type: ignore[safe-super]
super().on_emit(readable_log_record) # type: ignore[safe-super]
else:
# this method was removed in opentelemetry-sdk and replaced with on_emit
super().emit(log_data) # type: ignore[safe-super,misc] # pylint: disable=no-member
super().emit(readable_log_record) # type: ignore[safe-super,misc] # pylint: disable=no-member

def emit(self, log_data: LogData) -> None:
self.on_emit(log_data)
def emit(self, readable_log_record: ReadableLogRecord) -> None: # pylint: disable=arguments-renamed
self.on_emit(readable_log_record)

def shutdown(self):
pass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from dataclasses import dataclass, fields
from typing import Dict, no_type_check

from opentelemetry.sdk._logs import LogRecord
from opentelemetry.sdk.trace import Event, ReadableSpan
from opentelemetry._logs import LogRecord
from opentelemetry.sdk.trace import ReadableSpan
from opentelemetry.semconv._incubating.attributes import gen_ai_attributes
from opentelemetry.semconv.attributes.http_attributes import (
HTTP_REQUEST_METHOD,
Expand Down Expand Up @@ -177,7 +177,7 @@ def _from_log_record(log_record: LogRecord):

@staticmethod
@no_type_check
def _from_span_event(span_event: Event):
def _from_span_event(span_event: LogRecord):
return _ExceptionData(
message=str(span_event.attributes.get(SpanAttributes.EXCEPTION_MESSAGE, "")),
stack_trace=str(span_event.attributes.get(SpanAttributes.EXCEPTION_STACKTRACE, "")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
EXCEPTION_STACKTRACE,
EXCEPTION_TYPE,
)
from opentelemetry.sdk._logs import LogData
from opentelemetry.sdk._logs.export import LogExporter, LogExportResult
from opentelemetry.sdk._logs import ReadableLogRecord
from opentelemetry.sdk._logs.export import LogRecordExporter, LogRecordExportResult

from azure.monitor.opentelemetry.exporter import _utils
from azure.monitor.opentelemetry.exporter._constants import (
Expand Down Expand Up @@ -53,16 +53,16 @@
__all__ = ["AzureMonitorLogExporter"]


class AzureMonitorLogExporter(BaseExporter, LogExporter):
class AzureMonitorLogExporter(BaseExporter, LogRecordExporter):
"""Azure Monitor Log exporter for OpenTelemetry."""

def export(self, batch: Sequence[LogData], **kwargs: Any) -> LogExportResult: # pylint: disable=unused-argument
def export(self, batch: Sequence[ReadableLogRecord], **kwargs: Any) -> LogRecordExportResult: # pylint: disable=unused-argument
"""Export log data.

:param batch: OpenTelemetry LogData(s) to export.
:type batch: ~typing.Sequence[~opentelemetry._logs.LogData]
:param batch: OpenTelemetry ReadableLogRecord(s) to export.
:type batch: ~typing.Sequence[~opentelemetry._logs.ReadableLogRecord]
:return: The result of the export.
:rtype: ~opentelemetry.sdk._logs.export.LogData
:rtype: ~opentelemetry.sdk._logs.export.ReadableLogRecord
"""
envelopes = [self._log_to_envelope(log) for log in batch]
try:
Expand All @@ -81,8 +81,8 @@ def shutdown(self) -> None:
if self.storage:
self.storage.close()

def _log_to_envelope(self, log_data: LogData) -> TelemetryItem:
envelope = _convert_log_to_envelope(log_data)
def _log_to_envelope(self, readable_log_record: ReadableLogRecord) -> TelemetryItem:
envelope = _convert_log_to_envelope(readable_log_record)
envelope.instrumentation_key = self._instrumentation_key
return envelope

Expand All @@ -106,8 +106,8 @@ def from_connection_string(cls, conn_str: str, **kwargs: Any) -> "AzureMonitorLo
return cls(connection_string=conn_str, **kwargs)


def _log_data_is_event(log_data: LogData) -> bool:
log_record = log_data.log_record
def _log_data_is_event(readable_log_record: ReadableLogRecord) -> bool:
log_record = readable_log_record.log_record
is_event = None
if log_record.attributes:
is_event = log_record.attributes.get(_MICROSOFT_CUSTOM_EVENT_NAME) or \
Expand All @@ -117,11 +117,11 @@ def _log_data_is_event(log_data: LogData) -> bool:

# pylint: disable=protected-access
# pylint: disable=too-many-statements
def _convert_log_to_envelope(log_data: LogData) -> TelemetryItem:
log_record = log_data.log_record
def _convert_log_to_envelope(readable_log_record: ReadableLogRecord) -> TelemetryItem:
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(log_record.resource)) # type: ignore
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
)
Expand Down Expand Up @@ -177,7 +177,7 @@ def _convert_log_to_envelope(log_data: LogData) -> TelemetryItem:
exceptions=[exc_details],
)
envelope.data = MonitorBase(base_data=data, base_type="ExceptionData")
elif _log_data_is_event(log_data): # Event telemetry
elif _log_data_is_event(readable_log_record): # Event telemetry
_set_statsbeat_custom_events_feature()
envelope.name = "Microsoft.ApplicationInsights.Event"
event_name = ""
Expand Down Expand Up @@ -207,10 +207,10 @@ def _convert_log_to_envelope(log_data: LogData) -> TelemetryItem:
return envelope


def _get_log_export_result(result: ExportResult) -> LogExportResult:
def _get_log_export_result(result: ExportResult) -> LogRecordExportResult:
if result == ExportResult.SUCCESS:
return LogExportResult.SUCCESS
return LogExportResult.FAILURE
return LogRecordExportResult.SUCCESS
return LogRecordExportResult.FAILURE


# pylint: disable=line-too-long
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from typing import Optional, Dict, Any

from opentelemetry.sdk._logs import LogData
from opentelemetry.sdk._logs import ReadableLogRecord
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor, LogExporter
from opentelemetry.trace import get_current_span

Expand All @@ -26,24 +26,24 @@ def __init__(
self._options = options or {}
self._enable_trace_based_sampling_for_logs = self._options.get("enable_trace_based_sampling_for_logs")

def on_emit(self, log_data: LogData) -> None:
def on_emit(self, readable_log_record: ReadableLogRecord) -> None: # pylint: disable=arguments-renamed
# cspell: disable
""" Determines whether the logger should drop log records associated with unsampled traces.
If `trace_based_sampling` is `true`, log records associated with unsampled traces are dropped by the `Logger`.
A log record is considered associated with an unsampled trace if it has a valid `SpanId` and its
`TraceFlags` indicate that the trace is unsampled. A log record that isn't associated with a trace
context is not affected by this parameter and therefore bypasses trace based sampling filtering.

:param log_data: Contains the log record to be exported
:type log_data: LogData
:param readable_log_record: Contains the log record to be exported
:type readable_log_record: ReadableLogRecord
"""

# cspell: enable
if self._enable_trace_based_sampling_for_logs:
if hasattr(log_data, "log_record") and log_data.log_record is not None:
if hasattr(log_data.log_record, "context") and log_data.log_record.context is not None:
span = get_current_span(log_data.log_record.context)
if hasattr(readable_log_record, "log_record") and readable_log_record.log_record is not None:
if hasattr(readable_log_record.log_record, "context") and readable_log_record.log_record.context is not None: # pylint: disable=line-too-long
span = get_current_span(readable_log_record.log_record.context)
span_context = span.get_span_context()
if span_context.is_valid and not span_context.trace_flags.sampled:
return
super().on_emit(log_data)
super().on_emit(readable_log_record)
4 changes: 2 additions & 2 deletions sdk/monitor/azure-monitor-opentelemetry-exporter/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@
"azure-core<2.0.0,>=1.28.0",
"azure-identity~=1.17",
"msrest>=0.6.10",
"opentelemetry-api==1.38",
"opentelemetry-sdk==1.38",
"opentelemetry-api==1.39",
"opentelemetry-sdk==1.39",
"psutil>=5.9,<8",
],
entry_points={
Expand Down
Loading
Loading