Skip to content

Commit bfa1b59

Browse files
authored
Support OpenTelemetry 1.39.0, drop support for earlier versions, stop using the OTel events API/SDK (#1562)
1 parent 6df0f5d commit bfa1b59

File tree

21 files changed

+221
-236
lines changed

21 files changed

+221
-236
lines changed

.github/workflows/daily_deps_test.yml

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,10 @@ jobs:
4545
pydantic-version: '2.11'
4646
otel-version: '1'
4747
# OpenTelemetry versions
48-
- python-version: '3.13'
49-
pydantic-version: 'main'
50-
otel-version: '1.37'
51-
- python-version: '3.13'
52-
pydantic-version: 'main'
53-
otel-version: '1.36'
54-
- python-version: '3.13'
55-
pydantic-version: 'main'
56-
otel-version: '1.35'
48+
# Uncomment when we support multiple versions of OTel again
49+
# - python-version: '3.13'
50+
# pydantic-version: 'main'
51+
# otel-version: '1.39'
5752
env:
5853
PYTHON: ${{ matrix.python-version }}
5954
steps:

.github/workflows/main.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,10 @@ jobs:
6868
- python-version: '3.12'
6969
pydantic-version: '2.4'
7070
otel-version: '1' # i.e. the latest
71-
- python-version: '3.13'
72-
pydantic-version: '2' # i.e. the latest
73-
otel-version: '1.35'
71+
# Uncomment when we support multiple versions of OTel again
72+
# - python-version: '3.13'
73+
# pydantic-version: '2' # i.e. the latest
74+
# otel-version: '1.39'
7475
env:
7576
PYTHON: ${{ matrix.python-version }}
7677
steps:

logfire/_internal/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,12 @@
33
We use the `_internal` module to discourage imports from outside the package,
44
and thereby avoid causing breaking changes when refactoring the package.
55
"""
6+
7+
from opentelemetry.sdk.resources import Resource
8+
9+
from logfire._internal.utils import platform_is_emscripten
10+
11+
if platform_is_emscripten(): # pragma: no cover
12+
# Resource.create starts a thread, which is not supported in Emscripten.
13+
# We have to patch it early like this because it gets called just by importing OTel logs modules.
14+
Resource.create = Resource # type: ignore

logfire/_internal/config.py

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,6 @@
115115
if TYPE_CHECKING:
116116
from typing import TextIO
117117

118-
from opentelemetry._events import EventLoggerProvider
119-
120118
from .main import Logfire
121119

122120

@@ -721,12 +719,6 @@ def __init__(
721719
# thus it "shuts down" when it's gc'ed
722720
self._meter_provider = ProxyMeterProvider(NoOpMeterProvider())
723721
self._logger_provider = ProxyLoggerProvider(NoOpLoggerProvider())
724-
try:
725-
from opentelemetry.sdk._events import EventLoggerProvider as SDKEventLoggerProvider
726-
727-
self._event_logger_provider = SDKEventLoggerProvider(self._logger_provider) # type: ignore
728-
except ImportError:
729-
self._event_logger_provider = None
730722
# This ensures that we only call OTEL's global set_tracer_provider once to avoid warnings.
731723
self._has_set_providers = False
732724
self._initialized = False
@@ -824,11 +816,7 @@ def _initialize(self) -> None:
824816
):
825817
otel_resource_attributes[RESOURCE_ATTRIBUTES_CODE_WORK_DIR] = os.getcwd()
826818

827-
if emscripten: # pragma: no cover
828-
# Resource.create creates a thread pool which fails in Pyodide / Emscripten
829-
resource = Resource(otel_resource_attributes)
830-
else:
831-
resource = Resource.create(otel_resource_attributes)
819+
resource = Resource.create(otel_resource_attributes)
832820

833821
# Set service instance ID to a random UUID if it hasn't been set already.
834822
# Setting it above would have also mostly worked and allowed overriding via OTEL_RESOURCE_ATTRIBUTES,
@@ -1111,12 +1099,7 @@ def fix_pid(): # pragma: no cover
11111099
)
11121100
logger_provider = SDKLoggerProvider(resource)
11131101
logger_provider.add_log_record_processor(root_log_processor)
1114-
1115-
if self._event_logger_provider:
1116-
# This also shuts down the underlying self._logger_provider
1117-
self._event_logger_provider.shutdown()
1118-
else:
1119-
self._logger_provider.shutdown()
1102+
self._logger_provider.shutdown()
11201103

11211104
self._logger_provider.set_provider(logger_provider)
11221105
self._logger_provider.set_min_level(self.min_level)
@@ -1126,10 +1109,6 @@ def fix_pid(): # pragma: no cover
11261109
trace.set_tracer_provider(self._tracer_provider)
11271110
set_meter_provider(self._meter_provider)
11281111
set_logger_provider(self._logger_provider)
1129-
if self._event_logger_provider:
1130-
from opentelemetry._events import set_event_logger_provider
1131-
1132-
set_event_logger_provider(self._event_logger_provider)
11331112

11341113
@atexit.register
11351114
def exit_open_spans(): # pragma: no cover
@@ -1190,8 +1169,6 @@ def force_flush(self, timeout_millis: int = 30_000) -> bool:
11901169
"""
11911170
self._meter_provider.force_flush(timeout_millis)
11921171
self._logger_provider.force_flush(timeout_millis)
1193-
if self._event_logger_provider:
1194-
self._event_logger_provider.force_flush(timeout_millis)
11951172
return self._tracer_provider.force_flush(timeout_millis)
11961173

11971174
def get_tracer_provider(self) -> ProxyTracerProvider:
@@ -1224,16 +1201,6 @@ def get_logger_provider(self) -> ProxyLoggerProvider:
12241201
"""
12251202
return self._logger_provider
12261203

1227-
def get_event_logger_provider(self) -> EventLoggerProvider | None:
1228-
"""Get an event logger provider from this `LogfireConfig`.
1229-
1230-
This is used internally and should not be called by users of the SDK.
1231-
1232-
Returns:
1233-
The event logger provider.
1234-
"""
1235-
return self._event_logger_provider
1236-
12371204
def warn_if_not_initialized(self, message: str):
12381205
ignore_no_config_env = os.getenv('LOGFIRE_IGNORE_NO_CONFIG', '')
12391206
ignore_no_config = ignore_no_config_env.lower() in ('1', 'true', 't') or self.ignore_no_config

logfire/_internal/exporters/console.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
from textwrap import indent as indent_text
1515
from typing import Any, Literal, TextIO, cast
1616

17-
from opentelemetry.sdk._logs import LogData, LogRecord
18-
from opentelemetry.sdk._logs.export import LogExporter, LogExportResult
17+
from opentelemetry._logs import LogRecord
18+
from opentelemetry.sdk._logs import ReadableLogRecord
19+
from opentelemetry.sdk._logs.export import LogRecordExporter, LogRecordExportResult
1920
from opentelemetry.sdk.trace import Event, ReadableSpan
2021
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
2122
from rich.columns import Columns
@@ -492,14 +493,14 @@ def _pending_span_parent(attributes: Mapping[str, object]) -> int | None:
492493

493494

494495
@dataclass
495-
class ConsoleLogExporter(LogExporter):
496+
class ConsoleLogExporter(LogRecordExporter):
496497
span_exporter: SimpleConsoleSpanExporter
497498

498-
def export(self, batch: Sequence[LogData]) -> LogExportResult: # type: ignore
499+
def export(self, batch: Sequence[ReadableLogRecord]) -> LogRecordExportResult:
499500
for log_data in batch:
500501
self.span_exporter.export_record(Record.from_log(log_data.log_record))
501502

502-
return LogExportResult.SUCCESS
503+
return LogRecordExportResult.SUCCESS
503504

504505
def shutdown(self):
505506
self.span_exporter.shutdown()
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from dataclasses import dataclass
22

3-
from opentelemetry.sdk._logs import LogData
3+
from opentelemetry.sdk._logs import ReadWriteLogRecord
44

55
import logfire
66
from logfire._internal.exporters.wrapper import WrapperLogProcessor
@@ -14,17 +14,17 @@ class CheckSuppressInstrumentationLogProcessorWrapper(WrapperLogProcessor):
1414
Placed at the root of the tree of processors.
1515
"""
1616

17-
def on_emit(self, log_data: LogData):
17+
def on_emit(self, log_record: ReadWriteLogRecord):
1818
if is_instrumentation_suppressed():
1919
return None
2020
with logfire.suppress_instrumentation():
21-
return super().on_emit(log_data)
21+
return super().on_emit(log_record)
2222

2323

2424
@dataclass
2525
class MainLogProcessorWrapper(WrapperLogProcessor):
2626
scrubber: BaseScrubber
2727

28-
def on_emit(self, log_data: LogData):
29-
log_data.log_record = self.scrubber.scrub_log(log_data.log_record)
30-
return super().on_emit(log_data)
28+
def on_emit(self, log_record: ReadWriteLogRecord):
29+
log_record.log_record = self.scrubber.scrub_log(log_record.log_record)
30+
return super().on_emit(log_record)

logfire/_internal/exporters/otlp.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414
import requests.exceptions
1515
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
16-
from opentelemetry.sdk._logs import LogData
17-
from opentelemetry.sdk._logs._internal.export import LogExportResult
16+
from opentelemetry.sdk._logs import ReadableLogRecord
17+
from opentelemetry.sdk._logs._internal.export import LogRecordExportResult
1818
from opentelemetry.sdk.trace import ReadableSpan
1919
from opentelemetry.sdk.trace.export import SpanExportResult
2020
from requests import Session
@@ -263,9 +263,9 @@ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
263263
class QuietLogExporter(WrapperLogExporter):
264264
"""A LogExporter that catches request exceptions to prevent OTEL from logging a huge traceback."""
265265

266-
def export(self, batch: Sequence[LogData]):
266+
def export(self, batch: Sequence[ReadableLogRecord]):
267267
try:
268268
return super().export(batch)
269269
except requests.exceptions.RequestException:
270270
# Rely on OTLPExporterHttpSession/DiskRetryer to log this kind of error periodically.
271-
return LogExportResult.FAILURE
271+
return LogRecordExportResult.FAILURE

logfire/_internal/exporters/test.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
from typing import Any, cast
1313

1414
from opentelemetry import trace
15-
from opentelemetry.sdk._logs import LogData
16-
from opentelemetry.sdk._logs._internal.export import LogExportResult
17-
from opentelemetry.sdk._logs.export import InMemoryLogExporter
15+
from opentelemetry.sdk._logs import ReadableLogRecord
16+
from opentelemetry.sdk._logs._internal.export import LogRecordExportResult
17+
from opentelemetry.sdk._logs.export import InMemoryLogRecordExporter
1818
from opentelemetry.sdk.trace import Event, ReadableSpan
1919
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
2020

@@ -178,7 +178,7 @@ def build_attributes(
178178
return attributes
179179

180180

181-
class TestLogExporter(InMemoryLogExporter):
181+
class TestLogExporter(InMemoryLogRecordExporter):
182182
"""A LogExporter that stores exported logs in a list for asserting in tests."""
183183

184184
# NOTE: Avoid test discovery by pytest.
@@ -188,7 +188,7 @@ def __init__(self, ns_timestamp_generator: typing.Callable[[], int]) -> None:
188188
super().__init__()
189189
self.ns_timestamp_generator = ns_timestamp_generator
190190

191-
def export(self, batch: typing.Sequence[LogData]) -> LogExportResult:
191+
def export(self, batch: typing.Sequence[ReadableLogRecord]) -> LogRecordExportResult:
192192
for log in batch:
193193
log.log_record.timestamp = self.ns_timestamp_generator()
194194
log.log_record.observed_timestamp = self.ns_timestamp_generator()
@@ -211,7 +211,7 @@ def exported_logs_as_dicts(
211211
parse_json_attributes=parse_json_attributes,
212212
)
213213

214-
def build_log(log_data: LogData) -> dict[str, Any]:
214+
def build_log(log_data: ReadableLogRecord) -> dict[str, Any]:
215215
log_record = log_data.log_record
216216
res = {
217217
'body': log_record.body,
@@ -226,12 +226,12 @@ def build_log(log_data: LogData) -> dict[str, Any]:
226226
}
227227

228228
if include_resources: # pragma: no branch
229-
resource_attributes = _build_attributes(log_record.resource.attributes)
229+
resource_attributes = _build_attributes(log_data.resource.attributes)
230230
res['resource'] = {
231231
'attributes': resource_attributes,
232232
}
233233

234-
if include_instrumentation_scope: # pragma: no branch
234+
if include_instrumentation_scope and log_data.instrumentation_scope: # pragma: no branch
235235
res['instrumentation_scope'] = log_data.instrumentation_scope.name
236236

237237
return res

logfire/_internal/exporters/wrapper.py

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

33
from collections.abc import Sequence
44
from dataclasses import dataclass
5-
from typing import Any, cast
5+
from typing import Any
66

77
from opentelemetry import context
8-
from opentelemetry.sdk._logs import LogData, LogRecordProcessor
9-
from opentelemetry.sdk._logs.export import LogExporter, LogExportResult
8+
from opentelemetry.sdk._logs import LogRecordProcessor, ReadableLogRecord, ReadWriteLogRecord
9+
from opentelemetry.sdk._logs.export import LogRecordExporter, LogRecordExportResult
1010
from opentelemetry.sdk.metrics.export import AggregationTemporality, MetricExporter, MetricExportResult, MetricsData
1111
from opentelemetry.sdk.metrics.view import Aggregation
1212
from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
@@ -75,13 +75,13 @@ def force_flush(self, timeout_millis: int = 30000) -> bool:
7575

7676

7777
@dataclass
78-
class WrapperLogExporter(LogExporter):
78+
class WrapperLogExporter(LogRecordExporter):
7979
"""A base class for LogExporters that wrap another exporter."""
8080

81-
exporter: LogExporter
81+
exporter: LogRecordExporter
8282

83-
def export(self, batch: Sequence[LogData]) -> LogExportResult: # type: ignore
84-
return cast(LogExportResult, self.exporter.export(batch))
83+
def export(self, batch: Sequence[ReadableLogRecord]) -> LogRecordExportResult:
84+
return self.exporter.export(batch)
8585

8686
def shutdown(self):
8787
return self.exporter.shutdown()
@@ -93,8 +93,8 @@ class WrapperLogProcessor(LogRecordProcessor):
9393

9494
processor: LogRecordProcessor
9595

96-
def on_emit(self, log_data: LogData) -> None:
97-
return self.processor.on_emit(log_data)
96+
def on_emit(self, log_record: ReadWriteLogRecord) -> None:
97+
return self.processor.on_emit(log_record)
9898

9999
def shutdown(self):
100100
return self.processor.shutdown()

logfire/_internal/integrations/pydantic_ai.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def instrument_pydantic_ai(
2929
for k, v in dict(
3030
tracer_provider=logfire_instance.config.get_tracer_provider(),
3131
meter_provider=logfire_instance.config.get_meter_provider(), # not in old versions
32-
event_logger_provider=logfire_instance.config.get_event_logger_provider(), # may be removed in the future
32+
logger_provider=logfire_instance.config.get_logger_provider(),
3333
).items()
3434
if k in expected_kwarg_names
3535
}

0 commit comments

Comments
 (0)