Skip to content

Commit f86e4ce

Browse files
authored
Update to OTel 1.37.0, drop support for <1.35.0 (#1398)
1 parent 068bd01 commit f86e4ce

File tree

17 files changed

+351
-391
lines changed

17 files changed

+351
-391
lines changed

.github/workflows/daily_deps_test.yml

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,10 @@ jobs:
4747
# OpenTelemetry versions
4848
- python-version: '3.13'
4949
pydantic-version: 'main'
50-
otel-version: '1.33'
50+
otel-version: '1.36'
5151
- python-version: '3.13'
5252
pydantic-version: 'main'
53-
otel-version: '1.32'
54-
- python-version: '3.13'
55-
pydantic-version: 'main'
56-
otel-version: '1.31'
57-
- python-version: '3.13'
58-
pydantic-version: 'main'
59-
otel-version: '1.30'
53+
otel-version: '1.35'
6054
env:
6155
PYTHON: ${{ matrix.python-version }}
6256
steps:

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ jobs:
7070
otel-version: '1' # i.e. the latest
7171
- python-version: '3.13'
7272
pydantic-version: '2' # i.e. the latest
73-
otel-version: '1.30'
73+
otel-version: '1.35'
7474
env:
7575
PYTHON: ${{ matrix.python-version }}
7676
steps:

docs/integrations/system-metrics.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ logfire.instrument_system_metrics({
7878
'process.memory.usage': None,
7979
'process.memory.virtual': None,
8080
'process.thread.count': None,
81+
'cpython.gc.collected_objects': None,
82+
'cpython.gc.collections': None,
83+
'cpython.gc.uncollectable_objects': None,
8184
})
8285
```
8386

logfire/_internal/exporters/dynamic_batch.py

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@
88

99
from logfire._internal.exporters.wrapper import WrapperSpanProcessor
1010

11-
try:
12-
from opentelemetry.sdk._shared_internal import BatchProcessor
13-
except ImportError:
14-
BatchProcessor = None
15-
1611

1712
class DynamicBatchSpanProcessor(WrapperSpanProcessor):
1813
"""A wrapper around a BatchSpanProcessor that dynamically adjusts the schedule delay.
@@ -37,37 +32,18 @@ def on_end(self, span: ReadableSpan) -> None:
3732
self.schedule_delay_millis = self.final_delay
3833
super().on_end(span)
3934

40-
if BatchProcessor:
41-
42-
@property
43-
def batch_processor(self): # type: ignore
44-
return self.processor._batch_processor # type: ignore
45-
46-
@property
47-
def span_exporter(self) -> SpanExporter: # type: ignore
48-
return self.batch_processor._exporter # type: ignore
49-
50-
@property
51-
def schedule_delay_millis(self) -> float: # type: ignore
52-
return self.batch_processor._schedule_delay * 1000 # type: ignore
53-
54-
@schedule_delay_millis.setter
55-
def schedule_delay_millis(self, value: float): # type: ignore
56-
self.batch_processor._schedule_delay = value / 1000 # type: ignore
57-
else:
58-
59-
@property
60-
def batch_processor(self):
61-
return self.processor
35+
@property
36+
def batch_processor(self):
37+
return self.processor._batch_processor # type: ignore
6238

63-
@property
64-
def span_exporter(self) -> SpanExporter:
65-
return self.processor.span_exporter # type: ignore
39+
@property
40+
def span_exporter(self) -> SpanExporter:
41+
return self.batch_processor._exporter # type: ignore
6642

67-
@property
68-
def schedule_delay_millis(self) -> float:
69-
return self.processor.schedule_delay_millis # type: ignore
43+
@property
44+
def schedule_delay_millis(self) -> float:
45+
return self.batch_processor._schedule_delay * 1000 # type: ignore
7046

71-
@schedule_delay_millis.setter
72-
def schedule_delay_millis(self, value: float):
73-
self.processor.schedule_delay_millis = value # type: ignore
47+
@schedule_delay_millis.setter
48+
def schedule_delay_millis(self, value: float):
49+
self.batch_processor._schedule_delay = value / 1000 # type: ignore

logfire/_internal/exporters/logs.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ def on_emit(self, log_data: LogData):
2020
with logfire.suppress_instrumentation():
2121
return super().on_emit(log_data)
2222

23-
emit = on_emit
24-
2523

2624
@dataclass
2725
class MainLogProcessorWrapper(WrapperLogProcessor):
@@ -30,5 +28,3 @@ class MainLogProcessorWrapper(WrapperLogProcessor):
3028
def on_emit(self, log_data: LogData):
3129
log_data.log_record = self.scrubber.scrub_log(log_data.log_record)
3230
return super().on_emit(log_data)
33-
34-
emit = on_emit

logfire/_internal/exporters/processor_wrapper.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def on_end(self, span: ReadableSpan) -> None:
7979
_tweak_asgi_send_receive_spans(span_dict)
8080
_tweak_sqlalchemy_connect_spans(span_dict)
8181
_tweak_http_spans(span_dict)
82+
_tweak_fastapi_span(span_dict)
8283
_summarize_db_statement(span_dict)
8384
_set_error_level_and_status(span_dict)
8485
_transform_langchain_span(span_dict)
@@ -299,6 +300,34 @@ def _summarize_db_statement(span: ReadableSpanDict):
299300
span['attributes'] = {**attributes, ATTRIBUTES_MESSAGE_KEY: summary}
300301

301302

303+
def _tweak_fastapi_span(span: ReadableSpanDict):
304+
scope = span['instrumentation_scope']
305+
306+
if not (scope and scope.name == 'opentelemetry.instrumentation.fastapi'):
307+
return
308+
309+
# Our fastapi instrumentation records some exceptions directly on the request span.
310+
# These might be handled and not seen again, or they may bubble through and be recorded by the OTel middleware,
311+
# thus appearing twice on the same span.
312+
# We dedupe them here, keeping the latter event which has a fuller traceback.
313+
events = span['events']
314+
new_events: list[Event] = []
315+
# (type, message) keys of exceptions we've seen.
316+
seen_exceptions: set[tuple[Any, Any]] = set()
317+
# Go in reverse order to give the latter events precedence.
318+
for event in events[::-1]:
319+
attrs = event.attributes
320+
if not (event.name == 'exception' and attrs and 'exception.type' in attrs and 'exception.message' in attrs):
321+
new_events.append(event)
322+
continue
323+
key = (attrs['exception.type'], attrs['exception.message'])
324+
if key in seen_exceptions and attrs.get('recorded_by_logfire_fastapi'):
325+
continue
326+
seen_exceptions.add(key)
327+
new_events.append(event)
328+
span['events'] = new_events[::-1]
329+
330+
302331
def _transform_langchain_span(span: ReadableSpanDict):
303332
"""Transform spans generated by LangSmith to work better in the Logfire UI.
304333

logfire/_internal/exporters/wrapper.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -93,18 +93,8 @@ class WrapperLogProcessor(LogRecordProcessor):
9393

9494
processor: LogRecordProcessor
9595

96-
if hasattr(LogRecordProcessor, 'on_emit'):
97-
98-
def on_emit(self, log_data: LogData) -> None:
99-
return self.processor.on_emit(log_data)
100-
101-
emit = on_emit
102-
else:
103-
104-
def emit(self, log_data: LogData) -> None:
105-
return self.processor.emit(log_data) # type: ignore
106-
107-
on_emit = emit
96+
def on_emit(self, log_data: LogData) -> None:
97+
return self.processor.on_emit(log_data)
10898

10999
def shutdown(self):
110100
return self.processor.shutdown()

logfire/_internal/integrations/fastapi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ def set_timestamp(attribute_name: str):
186186
# Record the end timestamp before recording exceptions.
187187
set_timestamp('end_timestamp')
188188
except Exception as exc:
189-
root_span.record_exception(exc)
189+
root_span.record_exception(exc, attributes={'recorded_by_logfire_fastapi': True})
190190
raise
191191

192192
async def solve_dependencies(self, request: Request | WebSocket, original: Awaitable[Any]) -> Any:

logfire/_internal/integrations/system_metrics.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@
5353
'process.memory.virtual',
5454
'process.thread.count',
5555
'process.runtime.gc_count',
56+
'cpython.gc.collected_objects',
57+
'cpython.gc.collections',
58+
'cpython.gc.uncollectable_objects',
5659
]
5760
] = Literal[ # type: ignore # but pyright doesn't like it
5861
'system.cpu.simple_utilization',
@@ -80,6 +83,9 @@
8083
'process.memory.virtual',
8184
'process.thread.count',
8285
'process.runtime.gc_count',
86+
'cpython.gc.collected_objects',
87+
'cpython.gc.collections',
88+
'cpython.gc.uncollectable_objects',
8389
]
8490

8591
Config = dict[MetricName, Optional[Iterable[str]]]

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ classifiers = [
4545
"Framework :: OpenTelemetry :: Instrumentations",
4646
]
4747
dependencies = [
48-
"opentelemetry-sdk >= 1.21.0, < 1.37.0",
49-
"opentelemetry-exporter-otlp-proto-http >= 1.21.0, < 1.37.0",
48+
"opentelemetry-sdk >= 1.35.0, < 1.38.0",
49+
"opentelemetry-exporter-otlp-proto-http >= 1.35.0, < 1.38.0",
5050
"opentelemetry-instrumentation >= 0.41b0",
5151
"rich >= 13.4.2",
5252
"protobuf >= 4.23.4",

0 commit comments

Comments
 (0)