Skip to content

Commit 06b6d5f

Browse files
committed
Merge branch 'master' into ivana/move-asgi-to-span-first
2 parents a4dbe50 + 8203912 commit 06b6d5f

File tree

11 files changed

+662
-84
lines changed

11 files changed

+662
-84
lines changed

CHANGELOG.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,82 @@
11
# Changelog
22

3+
## 2.55.0
4+
5+
### New Features ✨
6+
7+
#### Anthropic
8+
9+
- Record finish reasons in AI monitoring spans by @ericapisani in [#5678](https://github.com/getsentry/sentry-python/pull/5678)
10+
- Emit `gen_ai.chat` spans for asynchronous `messages.stream()` by @alexander-alderman-webb in [#5572](https://github.com/getsentry/sentry-python/pull/5572)
11+
- Emit AI Client Spans for synchronous `messages.stream()` by @alexander-alderman-webb in [#5565](https://github.com/getsentry/sentry-python/pull/5565)
12+
- Set gen_ai.response.id span attribute by @ericapisani in [#5662](https://github.com/getsentry/sentry-python/pull/5662)
13+
- Add `gen_ai.system` attribute to spans by @ericapisani in [#5661](https://github.com/getsentry/sentry-python/pull/5661)
14+
15+
#### Pydantic Ai
16+
17+
- Support ImageUrl content type in span instrumentation by @ericapisani in [#5629](https://github.com/getsentry/sentry-python/pull/5629)
18+
- Add tool description to execute_tool spans by @ericapisani in [#5596](https://github.com/getsentry/sentry-python/pull/5596)
19+
20+
#### Other
21+
22+
- (crons) Add owner field to MonitorConfig by @julwhitney13 in [#5610](https://github.com/getsentry/sentry-python/pull/5610)
23+
- (otlp) Add collector_url option to OTLPIntegration by @sl0thentr0py in [#5603](https://github.com/getsentry/sentry-python/pull/5603)
24+
25+
### Bug Fixes 🐛
26+
27+
- (ai) Truncate list-based message content in AI monitoring by @ericapisani in [#5631](https://github.com/getsentry/sentry-python/pull/5631)
28+
- (anthropic) Close span on `GeneratorExit` by @alexander-alderman-webb in [#5643](https://github.com/getsentry/sentry-python/pull/5643)
29+
- (celery) Propagate user-set headers by @sentrivana in [#5581](https://github.com/getsentry/sentry-python/pull/5581)
30+
- (langchain) Wrap finish_reason in array for gen_ai span attribute by @ericapisani in [#5666](https://github.com/getsentry/sentry-python/pull/5666)
31+
- (logging) Fix deadlock in log batcher by @sentrivana in [#5684](https://github.com/getsentry/sentry-python/pull/5684)
32+
- (profiler) Prevent buffer race condition during rapid start/stop cycles by @ericapisani in [#5622](https://github.com/getsentry/sentry-python/pull/5622)
33+
- (utils) Avoid double serialization of strings in safe_serialize by @ericapisani in [#5587](https://github.com/getsentry/sentry-python/pull/5587)
34+
- Enable unused import ruff check and fix unused imports by @sentrivana in [#5652](https://github.com/getsentry/sentry-python/pull/5652)
35+
36+
### Documentation 📚
37+
38+
- (openai-agents) Remove inapplicable comment by @alexander-alderman-webb in [#5495](https://github.com/getsentry/sentry-python/pull/5495)
39+
- Add AGENTS.md by @sentrivana in [#5579](https://github.com/getsentry/sentry-python/pull/5579)
40+
- Add `set_attribute` example to changelog by @sentrivana in [#5578](https://github.com/getsentry/sentry-python/pull/5578)
41+
42+
### Internal Changes 🔧
43+
44+
#### Anthropic
45+
46+
- Check system and response ID attributes on spans created by `stream()` by @alexander-alderman-webb in [#5665](https://github.com/getsentry/sentry-python/pull/5665)
47+
- Skip accumulation logic for unexpected types in streamed response by @alexander-alderman-webb in [#5564](https://github.com/getsentry/sentry-python/pull/5564)
48+
- Factor out streamed result handling by @alexander-alderman-webb in [#5563](https://github.com/getsentry/sentry-python/pull/5563)
49+
- Stream valid JSON by @alexander-alderman-webb in [#5641](https://github.com/getsentry/sentry-python/pull/5641)
50+
- Stop mocking response iterator by @alexander-alderman-webb in [#5573](https://github.com/getsentry/sentry-python/pull/5573)
51+
52+
#### Openai Agents
53+
54+
- Do not fail on new tool fields by @alexander-alderman-webb in [#5625](https://github.com/getsentry/sentry-python/pull/5625)
55+
- Stop expecting a specific function name by @alexander-alderman-webb in [#5623](https://github.com/getsentry/sentry-python/pull/5623)
56+
- Set streaming header when library uses `with_streaming_response()` by @alexander-alderman-webb in [#5583](https://github.com/getsentry/sentry-python/pull/5583)
57+
- Replace mocks with `httpx` for streamed responses by @alexander-alderman-webb in [#5580](https://github.com/getsentry/sentry-python/pull/5580)
58+
- Replace mocks with `httpx` in non-MCP tool tests by @alexander-alderman-webb in [#5602](https://github.com/getsentry/sentry-python/pull/5602)
59+
- Replace mocks with `httpx` in MCP tool tests by @alexander-alderman-webb in [#5605](https://github.com/getsentry/sentry-python/pull/5605)
60+
- Replace mocks with `httpx` in handoff tests by @alexander-alderman-webb in [#5604](https://github.com/getsentry/sentry-python/pull/5604)
61+
- Replace mocks with `httpx` in API error test by @alexander-alderman-webb in [#5601](https://github.com/getsentry/sentry-python/pull/5601)
62+
- Replace mocks with `httpx` in non-error single-response tests by @alexander-alderman-webb in [#5600](https://github.com/getsentry/sentry-python/pull/5600)
63+
- Remove test for unreachable state by @alexander-alderman-webb in [#5584](https://github.com/getsentry/sentry-python/pull/5584)
64+
- Expect `namespace` tool field for new `openai` versions by @alexander-alderman-webb in [#5599](https://github.com/getsentry/sentry-python/pull/5599)
65+
66+
#### Other
67+
68+
- (graphene) Simplify span creation by @sentrivana in [#5648](https://github.com/getsentry/sentry-python/pull/5648)
69+
- (httpx) Resolve type checking failures by @alexander-alderman-webb in [#5626](https://github.com/getsentry/sentry-python/pull/5626)
70+
- (pyramid) Support alpha suffixes in version parsing by @alexander-alderman-webb in [#5618](https://github.com/getsentry/sentry-python/pull/5618)
71+
- (rust) Don't implement separate scope management by @sentrivana in [#5639](https://github.com/getsentry/sentry-python/pull/5639)
72+
- (strawberry) Simplify span creation by @sentrivana in [#5647](https://github.com/getsentry/sentry-python/pull/5647)
73+
- 🤖 Update test matrix with new releases (03/16) by @github-actions in [#5671](https://github.com/getsentry/sentry-python/pull/5671)
74+
- Remove custom warden action by @sentrivana in [#5653](https://github.com/getsentry/sentry-python/pull/5653)
75+
- Add `httpx` to linting requirements by @alexander-alderman-webb in [#5644](https://github.com/getsentry/sentry-python/pull/5644)
76+
- Remove CodeQL action by @sentrivana in [#5616](https://github.com/getsentry/sentry-python/pull/5616)
77+
- Normalize dots in package names in `populate_tox.py` by @alexander-alderman-webb in [#5574](https://github.com/getsentry/sentry-python/pull/5574)
78+
- Do not run actions on `potel-base` by @sentrivana in [#5614](https://github.com/getsentry/sentry-python/pull/5614)
79+
380
## 2.54.0
481

582
### New Features ✨

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
copyright = "2019-{}, Sentry Team and Contributors".format(datetime.now().year)
3232
author = "Sentry Team and Contributors"
3333

34-
release = "2.54.0"
34+
release = "2.55.0"
3535
version = ".".join(release.split(".")[:2]) # The short X.Y version.
3636

3737

requirements-testing.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ tomli;python_version<"3.11" # Only needed for pytest on Python < 3.11
44
pytest-cov
55
pytest-forked
66
pytest-localserver
7+
pytest-timeout
78
pytest-watch
89
jsonschema
910
executing

sentry_sdk/_batcher.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def __init__(
3131
self._record_lost_func = record_lost_func
3232
self._running = True
3333
self._lock = threading.Lock()
34+
self._active: "threading.local" = threading.local()
3435

3536
self._flush_event: "threading.Event" = threading.Event()
3637

@@ -70,23 +71,40 @@ def _ensure_thread(self) -> bool:
7071
return True
7172

7273
def _flush_loop(self) -> None:
74+
# Mark the flush-loop thread as active for its entire lifetime so
75+
# that any re-entrant add() triggered by GC warnings during wait(),
76+
# flush(), or Event operations is silently dropped instead of
77+
# deadlocking on internal locks.
78+
self._active.flag = True
7379
while self._running:
7480
self._flush_event.wait(self.FLUSH_WAIT_TIME + random.random())
7581
self._flush_event.clear()
7682
self._flush()
7783

7884
def add(self, item: "T") -> None:
79-
if not self._ensure_thread() or self._flusher is None:
85+
# Bail out if the current thread is already executing batcher code.
86+
# This prevents deadlocks when code running inside the batcher (e.g.
87+
# _add_to_envelope during flush, or _flush_event.wait/set) triggers
88+
# a GC-emitted warning that routes back through the logging
89+
# integration into add().
90+
if getattr(self._active, "flag", False):
8091
return None
8192

82-
with self._lock:
83-
if len(self._buffer) >= self.MAX_BEFORE_DROP:
84-
self._record_lost(item)
93+
self._active.flag = True
94+
try:
95+
if not self._ensure_thread() or self._flusher is None:
8596
return None
8697

87-
self._buffer.append(item)
88-
if len(self._buffer) >= self.MAX_BEFORE_FLUSH:
89-
self._flush_event.set()
98+
with self._lock:
99+
if len(self._buffer) >= self.MAX_BEFORE_DROP:
100+
self._record_lost(item)
101+
return None
102+
103+
self._buffer.append(item)
104+
if len(self._buffer) >= self.MAX_BEFORE_FLUSH:
105+
self._flush_event.set()
106+
finally:
107+
self._active.flag = False
90108

91109
def kill(self) -> None:
92110
if self._flusher is None:
@@ -97,7 +115,12 @@ def kill(self) -> None:
97115
self._flusher = None
98116

99117
def flush(self) -> None:
100-
self._flush()
118+
was_active = getattr(self._active, "flag", False)
119+
self._active.flag = True
120+
try:
121+
self._flush()
122+
finally:
123+
self._active.flag = was_active
101124

102125
def _add_to_envelope(self, envelope: "Envelope") -> None:
103126
envelope.add_item(

sentry_sdk/_span_batcher.py

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,36 +43,50 @@ def __init__(
4343
self._record_lost_func = record_lost_func
4444
self._running = True
4545
self._lock = threading.Lock()
46+
self._active: "threading.local" = threading.local()
4647

4748
self._flush_event: "threading.Event" = threading.Event()
4849

4950
self._flusher: "Optional[threading.Thread]" = None
5051
self._flusher_pid: "Optional[int]" = None
5152

5253
def add(self, span: "StreamedSpan") -> None:
53-
if not self._ensure_thread() or self._flusher is None:
54+
# Bail out if the current thread is already executing batcher code.
55+
# This prevents deadlocks when code running inside the batcher (e.g.
56+
# _add_to_envelope during flush, or _flush_event.wait/set) triggers
57+
# a GC-emitted warning that routes back through the logging
58+
# integration into add().
59+
if getattr(self._active, "flag", False):
5460
return None
5561

56-
with self._lock:
57-
size = len(self._span_buffer[span.trace_id])
58-
if size >= self.MAX_BEFORE_DROP:
59-
self._record_lost_func(
60-
reason="queue_overflow",
61-
data_category="span",
62-
quantity=1,
63-
)
64-
return None
65-
66-
self._span_buffer[span.trace_id].append(span)
67-
self._running_size[span.trace_id] += self._estimate_size(span)
62+
self._active.flag = True
6863

69-
if size + 1 >= self.MAX_BEFORE_FLUSH:
70-
self._flush_event.set()
71-
return
64+
try:
65+
if not self._ensure_thread() or self._flusher is None:
66+
return None
7267

73-
if self._running_size[span.trace_id] >= self.MAX_BYTES_BEFORE_FLUSH:
74-
self._flush_event.set()
75-
return
68+
with self._lock:
69+
size = len(self._span_buffer[span.trace_id])
70+
if size >= self.MAX_BEFORE_DROP:
71+
self._record_lost_func(
72+
reason="queue_overflow",
73+
data_category="span",
74+
quantity=1,
75+
)
76+
return None
77+
78+
self._span_buffer[span.trace_id].append(span)
79+
self._running_size[span.trace_id] += self._estimate_size(span)
80+
81+
if size + 1 >= self.MAX_BEFORE_FLUSH:
82+
self._flush_event.set()
83+
return
84+
85+
if self._running_size[span.trace_id] >= self.MAX_BYTES_BEFORE_FLUSH:
86+
self._flush_event.set()
87+
return
88+
finally:
89+
self._active.flag = False
7690

7791
@staticmethod
7892
def _estimate_size(item: "StreamedSpan") -> int:

sentry_sdk/consts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1491,4 +1491,4 @@ def _get_default_options() -> "dict[str, Any]":
14911491
del _get_default_options
14921492

14931493

1494-
VERSION = "2.54.0"
1494+
VERSION = "2.55.0"

0 commit comments

Comments
 (0)