Skip to content

Commit 394a34f

Browse files
Merge branch 'master' into webb/langchain/watched-span
2 parents 5b226f2 + e3c48e1 commit 394a34f

5 files changed

Lines changed: 162 additions & 553 deletions

File tree

sentry_sdk/integrations/huey.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@
3434
except ImportError:
3535
raise DidNotEnable("Huey is not installed")
3636

37+
try:
38+
from huey.api import chord as HueyChord
39+
from huey.api import group as HueyGroup
40+
except ImportError:
41+
HueyChord = None
42+
HueyGroup = None
43+
3744

3845
HUEY_CONTROL_FLOW_EXCEPTIONS = (CancelExecution, RetryTask, TaskLockedException)
3946

@@ -53,22 +60,37 @@ def patch_enqueue() -> None:
5360

5461
@ensure_integration_enabled(HueyIntegration, old_enqueue)
5562
def _sentry_enqueue(
56-
self: "Huey", task: "Task"
63+
self: "Huey", item: "Union[Task, HueyGroup, HueyChord]"
5764
) -> "Optional[Union[Result, ResultGroup]]":
65+
if HueyChord is not None and isinstance(item, HueyChord):
66+
span_name = "Huey Chord"
67+
elif HueyGroup is not None and isinstance(item, HueyGroup):
68+
span_name = "Huey Task Group"
69+
else:
70+
span_name = item.name
71+
5872
with sentry_sdk.start_span(
5973
op=OP.QUEUE_SUBMIT_HUEY,
60-
name=task.name,
74+
name=span_name,
6175
origin=HueyIntegration.origin,
6276
):
63-
if not isinstance(task, PeriodicTask):
77+
if (
78+
not isinstance(item, PeriodicTask)
79+
and not (HueyGroup is not None and isinstance(item, HueyGroup))
80+
and not (HueyChord is not None and isinstance(item, HueyChord))
81+
):
6482
# Attach trace propagation data to task kwargs. We do
6583
# not do this for periodic tasks, as these don't
6684
# really have an originating transaction.
67-
task.kwargs["sentry_headers"] = {
85+
# Additionally, we do not do this for Huey groups or chords, as enqueue will
86+
# recursively call this method for each task within the list, resulting
87+
# in the trace propagation data being attached to each task individually
88+
# (which we want)
89+
item.kwargs["sentry_headers"] = {
6890
BAGGAGE_HEADER_NAME: get_baggage(),
6991
SENTRY_TRACE_HEADER_NAME: get_traceparent(),
7092
}
71-
return old_enqueue(self, task)
93+
return old_enqueue(self, item)
7294

7395
Huey.enqueue = _sentry_enqueue
7496

sentry_sdk/integrations/openai_agents/spans/ai_client.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
from ..consts import SPAN_ORIGIN
77
from ..utils import (
8-
_create_mcp_execute_tool_spans,
98
_set_agent_data,
109
_set_input_data,
1110
_set_output_data,
@@ -55,7 +54,6 @@ def update_ai_client_span(
5554

5655
if hasattr(response, "output") and response.output:
5756
_set_output_data(span, response)
58-
_create_mcp_execute_tool_spans(span, response)
5957

6058
if response_model is not None:
6159
span.set_data(SPANDATA.GEN_AI_RESPONSE_MODEL, response_model)

sentry_sdk/integrations/openai_agents/utils.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
set_data_normalized,
1515
truncate_and_annotate_messages,
1616
)
17-
from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS
17+
from sentry_sdk.consts import SPANDATA
1818
from sentry_sdk.integrations import DidNotEnable
1919
from sentry_sdk.scope import should_send_default_pii
2020
from sentry_sdk.utils import event_from_exception, safe_serialize
@@ -215,25 +215,3 @@ def _set_output_data(span: "sentry_sdk.tracing.Span", result: "Any") -> None:
215215
set_data_normalized(
216216
span, SPANDATA.GEN_AI_RESPONSE_TEXT, output_messages["response"]
217217
)
218-
219-
220-
def _create_mcp_execute_tool_spans(
221-
span: "sentry_sdk.tracing.Span", result: "agents.Result"
222-
) -> None:
223-
for output in result.output:
224-
if output.__class__.__name__ == "McpCall":
225-
with sentry_sdk.start_span(
226-
op=OP.GEN_AI_EXECUTE_TOOL,
227-
name=f"execute_tool {output.name}",
228-
start_timestamp=span.start_timestamp,
229-
) as execute_tool_span:
230-
execute_tool_span.set_data(SPANDATA.GEN_AI_TOOL_NAME, output.name)
231-
if should_send_default_pii():
232-
execute_tool_span.set_data(
233-
SPANDATA.GEN_AI_TOOL_INPUT, output.arguments
234-
)
235-
execute_tool_span.set_data(
236-
SPANDATA.GEN_AI_TOOL_OUTPUT, output.output
237-
)
238-
if output.error:
239-
execute_tool_span.set_status(SPANSTATUS.INTERNAL_ERROR)

tests/integrations/huey/test_huey.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111

1212
HUEY_VERSION = parse_version(HUEY_VERSION)
1313

14+
try:
15+
from huey.api import chord, group
16+
except ImportError:
17+
chord = None
18+
group = None
19+
1420

1521
@pytest.fixture
1622
def init_huey(sentry_init):
@@ -222,3 +228,114 @@ def propagated_trace_task():
222228
(event,) = events
223229

224230
assert event["contexts"]["trace"]["origin"] == "auto.queue.huey"
231+
232+
233+
@pytest.mark.skipif(HUEY_VERSION < (3, 0), reason="group was added in 3.0")
234+
def test_huey_enqueue_group(init_huey, capture_events):
235+
huey = init_huey()
236+
237+
events = capture_events()
238+
239+
@huey.task()
240+
def task1():
241+
pass
242+
243+
@huey.task()
244+
def task2():
245+
pass
246+
247+
with start_transaction() as transaction:
248+
huey.enqueue(group([task1.s(), task2.s()]))
249+
250+
for _ in range(2):
251+
task = huey.dequeue()
252+
huey.execute(task)
253+
254+
assert len(events) == 3
255+
256+
# Assert enqueue spans were successfully recorded
257+
producer_event = events[0]
258+
assert producer_event["type"] == "transaction"
259+
assert producer_event["contexts"]["trace"]["trace_id"] == transaction.trace_id
260+
assert producer_event["contexts"]["trace"]["origin"] == "manual"
261+
262+
spans = producer_event["spans"]
263+
assert len(spans) == 3
264+
assert spans[0]["op"] == "queue.submit.huey"
265+
assert spans[0]["description"] == "Huey Task Group"
266+
assert spans[1]["op"] == "queue.submit.huey"
267+
assert spans[1]["description"] == "task1"
268+
assert spans[2]["op"] == "queue.submit.huey"
269+
assert spans[2]["description"] == "task2"
270+
271+
# Consumer transaction assertions (one per task)
272+
consumer_events = events[1:]
273+
for _, (consumer_event, expected_name) in enumerate(
274+
zip(consumer_events, ["task1", "task2"])
275+
):
276+
assert consumer_event["type"] == "transaction"
277+
assert consumer_event["transaction"] == expected_name
278+
assert consumer_event["transaction_info"] == {"source": "task"}
279+
assert consumer_event["contexts"]["trace"]["op"] == "queue.task.huey"
280+
assert consumer_event["contexts"]["trace"]["origin"] == "auto.queue.huey"
281+
assert consumer_event["contexts"]["trace"]["status"] == "ok"
282+
assert consumer_event["contexts"]["trace"]["trace_id"] == transaction.trace_id
283+
assert "huey_task_id" in consumer_event["tags"]
284+
assert consumer_event["tags"]["huey_task_retry"] is False
285+
286+
287+
@pytest.mark.skipif(HUEY_VERSION < (3, 0), reason="chord was added in 3.0")
288+
def test_huey_enqueue_chord(init_huey, capture_events):
289+
huey = init_huey()
290+
291+
events = capture_events()
292+
293+
@huey.task()
294+
def task1():
295+
pass
296+
297+
@huey.task()
298+
def task2(results):
299+
pass
300+
301+
with start_transaction() as transaction:
302+
huey.enqueue(chord([task1.s()], task2.s()))
303+
304+
for _ in range(2):
305+
task = huey.dequeue()
306+
huey.execute(task)
307+
308+
assert len(events) == 3
309+
310+
# Enqueue spans
311+
producer_event = events[0]
312+
assert producer_event["contexts"]["trace"]["trace_id"] == transaction.trace_id
313+
assert producer_event["contexts"]["trace"]["origin"] == "manual"
314+
315+
spans = producer_event["spans"]
316+
assert len(spans) == 2
317+
assert spans[0]["op"] == "queue.submit.huey"
318+
assert spans[0]["description"] == "Huey Chord"
319+
assert spans[1]["op"] == "queue.submit.huey"
320+
assert spans[1]["description"] == "task1"
321+
322+
task1_event = events[1]
323+
# Confirm the first task enqueued the chord callback
324+
task1_spans = task1_event["spans"]
325+
assert len(task1_spans) == 1
326+
assert task1_spans[0]["op"] == "queue.submit.huey"
327+
assert task1_spans[0]["description"] == "task2"
328+
329+
consumer_events = events[1:]
330+
for _, (consumer_event, expected_name) in enumerate(
331+
zip(consumer_events, ["task1", "task2"])
332+
):
333+
assert consumer_event["type"] == "transaction"
334+
assert consumer_event["transaction"] == expected_name
335+
assert consumer_event["transaction_info"] == {"source": "task"}
336+
assert consumer_event["contexts"]["trace"]["op"] == "queue.task.huey"
337+
assert consumer_event["contexts"]["trace"]["origin"] == "auto.queue.huey"
338+
assert consumer_event["contexts"]["trace"]["status"] == "ok"
339+
assert consumer_event["contexts"]["trace"]["trace_id"] == transaction.trace_id
340+
assert "huey_task_id" in consumer_event["tags"]
341+
assert consumer_event["tags"]["huey_task_retry"] is False

0 commit comments

Comments
 (0)