8000 Move `subprocess` breadcrumbs from `maybe_create_breadcrumbs_from_spa… · getsentry/sentry-python@6e3778d · GitHub
[go: up one dir, main page]

Skip to content

Commit 6e3778d

Browse files
authored
Move subprocess breadcrumbs from maybe_create_breadcrumbs_from_span to integration. (#3637)
Move `subprocess` breadcrumbs from `maybe_create_breadcrumbs_from_span` into the stdlib integration and preserve the breadcrumb behavior in POTel.
1 parent acf7746 commit 6e3778d

File tree

4 files changed

+201
-8
lines changed

4 files changed

+201
-8
lines changed

sentry_sdk/integrations/stdlib.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
SENSITIVE_DATA_SUBSTITUTE,
1414
capture_internal_exceptions,
1515
ensure_integration_enabled,
16+
get_current_thread_meta,
1617
is_sentry_url,
1718
logger,
1819
safe_repr,
@@ -225,6 +226,24 @@ def sentry_patched_popen_init(self, *a, **kw):
225226
rv = old_popen_init(self, *a, **kw)
226227

227228
span.set_tag("subprocess.pid", self.pid)
229+
230+
with capture_internal_exceptions():
231+
thread_id, thread_name = get_current_thread_meta()
232+
breadcrumb_data = {
233+
"subprocess.pid": self.pid,
234+
"thread.id": thread_id,
235+
"thread.name": thread_name,
236+
}
237+
if cwd:
238+
breadcrumb_data["subprocess.cwd"] = cwd
239+
240+
sentry_sdk.add_breadcrumb(
241+
type="subprocess",
242+
category="subprocess",
243+
message=description,
244+
data=breadcrumb_data,
245+
)
246+
228247
return rv
229248

230249
subprocess.Popen.__init__ = sentry_patched_popen_init # type: ignore

sentry_sdk/tracing_utils.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,18 +157,17 @@ def record_sql_queries(
157157

158158
def maybe_create_breadcrumbs_from_span(scope, span):
159159
# type: (sentry_sdk.Scope, sentry_sdk.tracing.Span) -> None
160-
161160
if span.op == OP.DB_REDIS:
162161
scope.add 8000 _breadcrumb(
163-
message=span.description, type="redis", category="redis", data=span._tags
162+
message=span.description,
163+
type="redis",
164+
category="redis",
165+
data=span._tags,
164166
)
165167
elif span.op == OP.HTTP_CLIENT:
166-
scope.add_breadcrumb(type="http", category="httplib", data=span._data)
167-
elif span.op == "subprocess":
168168
scope.add_breadcrumb(
169-
type="subprocess",
170-
category="subprocess",
171-
message=span.description,
169+
type="http",
170+
category="httplib",
172171
data=span._data,
173172
)
174173

tests/integrations/stdlib/test_subprocess.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
import subprocess
44
import sys
55
from collections.abc import Mapping
6+
from unittest import mock
67

78
import pytest
89

9-
from sentry_sdk import capture_message, start_transaction
10+
from sentry_sdk import capture_exception, capture_message, start_transaction
1011
from sentry_sdk.integrations.stdlib import StdlibIntegration
1112
from tests.conftest import ApproxDict
1213

@@ -224,3 +225,37 @@ def test_subprocess_span_origin(sentry_init, capture_events):
224225

225226
assert event["spans"][2]["op"] == "subprocess.wait"
226227
assert event["spans"][2]["origin"] == "auto.subprocess.stdlib.subprocess"
228+
229+
230+
def test_subprocess_breadcrumb(sentry_init, capture_events):
231+
sentry_init()
232+
events = capture_events()
233+
234+
args = [
235+
sys.executable,
236+
"-c",
237+
"print('hello world')",
238+
]
239+
popen = subprocess.Popen(args)
240+
popen.communicate()
241+
popen.poll()
242+
243+
try:
244+
1 / 0
245+
except ZeroDivisionError as ex:
246+
capture_exception(ex)
247+
248+
(event,) = events
249+
breadcrumbs = event["breadcrumbs"]["values"]
250+
assert len(breadcrumbs) == 1
251+
252+
(crumb,) = breadcrumbs
253+
assert crumb["type"] == "subprocess"
254+
assert crumb["category"] == "subprocess"
255+
assert crumb["message"] == " ".join(args)
256+
assert crumb["timestamp"] == mock.ANY
257+
assert crumb["data"] == {
258+
"subprocess.pid": popen.pid,
259+
"thread.id": mock.ANY,
260+
"thread.name": mock.ANY,
261+
}

tests/test_breadcrumbs.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
from unittest import mock
2+
3+
import sentry_sdk
4+
from sentry_sdk.consts import OP
5+
6+
7+
def test_breadcrumbs(sentry_init, capture_events):
8+
"""
9+
This test illustrates how breadcrumbs are added to the error event when an error occurs
10+
"""
11+
sentry_init(
12+
traces_sample_rate=1.0,
13+
)
14+
events = capture_events()
15+
16+
add_breadcrumbs_kwargs = {
17+
"type": "navigation",
18+
"category": "unit_tests.breadcrumbs",
19+
"level": "fatal",
20+
"origin": "unit-tests",
21+
"data": {
22+
"string": "foobar",
23+
"number": 4.2,
24+
"array": [1, 2, 3],
25+
"dict": {"foo": "bar"},
26+
},
27+
}
28+
29+
with sentry_sdk.start_transaction(name="trx-breadcrumbs"):
30+
sentry_sdk.add_breadcrumb(message="breadcrumb0", **add_breadcrumbs_kwargs)
31+
32+
with sentry_sdk.start_span(name="span1", op="function"):
33+
sentry_sdk.add_breadcrumb(message="breadcrumb1", **add_breadcrumbs_kwargs)
34+
35+
with sentry_sdk.start_span(name="span2", op="function"):
36+
sentry_sdk.add_breadcrumb(
37+
message="breadcrumb2", **add_breadcrumbs_kwargs
38+
)
39+
40+
# Spans that create breadcrumbs automatically
41+
with sentry_sdk.start_span(name="span3", op=OP.DB_REDIS) as span3:
42+
span3.set_data("span3_data", "data on the redis span")
43+
span3.set_tag("span3_tag", "tag on the redis span")
44+
45+
with sentry_sdk.start_span(name="span4", op=OP.HTTP_CLIENT) as span4:
46+
span4.set_data("span4_data", "data on the http.client span")
47+
span4.set_tag("span4_tag", "tag on the http.client span")
48+
49+
with sentry_sdk.start_span(name="span5", op=OP.SUBPROCESS) as span5:
50+
span5.set_data("span5_data", "data on the subprocess span")
51+
span5.set_tag("span5_tag", "tag on the subprocess span")
52+
53+
with sentry_sdk.start_span(name="span6", op="function") as span6:
54+
# This data on the span is not added to custom breadcrumbs.
55+
# Data from the span is only added to automatic breadcrumbs shown above
56+
span6.set_data("span6_data", "data on span6")
57+
span6.set_tag("span6_tag", "tag on the span6")
58+
sentry_sdk.add_breadcrumb(
59+
message="breadcrumb6", **add_breadcrumbs_kwargs
60+
)
61+
62+
try:
63+
1 / 0
64+
except ZeroDivisionError as ex:
65+
sentry_sdk.capture_exception(ex)
66+
67+
(error,) = events
68+
69+
breadcrumbs = error["breadcrumbs"]["values"]
70+
71+
for crumb in breadcrumbs:
72+
print(crumb)
73+
74+
assert len(breadcrumbs) == 7
75+
76+
# Check for my custom breadcrumbs
77+
for i in range(0, 3):
78+
assert breadcrumbs[i]["message"] == f"breadcrumb{i}"
79+
assert breadcrumbs[i]["type"] == "navigation"
80+
assert breadcrumbs[i]["category"] == "unit_tests.breadcrumbs"
81+
assert breadcrumbs[i]["level"] == "fatal"
82+
assert breadcrumbs[i]["origin"] == "unit-tests"
83+
assert breadcrumbs[i]["data"] == {
84+
"string": "foobar",
85+
"number": 4.2,
86+
"array": [1, 2, 3],
87+
"dict": {"foo": "bar"},
88+
}
89+
assert breadcrumbs[i]["timestamp"] == mock.ANY
90+
91+
# Check automatic redis breadcrumbs
92+
assert breadcrumbs[3]["message"] == "span3"
93+
assert breadcrumbs[3]["type"] == "redis"
94+
assert breadcrumbs[3]["category"] == "redis"
95+
assert "level" not in breadcrumbs[3]
96+
assert "origin" not in breadcrumbs[3]
97+
assert breadcrumbs[3]["data"] == {
98+
"span3_tag": "tag on the redis span",
99+
}
100+
assert breadcrumbs[3]["timestamp"] == mock.ANY
101+
102+
# Check automatic http.client breadcrumbs
103+
assert "message" not in breadcrumbs[4]
104+
assert breadcrumbs[4]["type"] == "http"
105+
assert breadcrumbs[4]["category"] == "httplib"
106+
assert "level" not in breadcrumbs[4]
107+
assert "origin" not in breadcrumbs[4]
108+
assert breadcrumbs[4]["data"] == {
109+
"thread.id": mock.ANY,
110+
"thread.name": mock.ANY,
111+
"span4_data": "data on the http.client span",
112+
}
113+
assert breadcrumbs[4]["timestamp"] == mock.ANY
114+
115+
# Check automatic subprocess breadcrumbs
116+
assert breadcrumbs[5]["message"] == "span5"
117+
assert breadcrumbs[5]["type"] == "subprocess"
11 1241 8+
assert breadcrumbs[5]["category"] == "subprocess"
119+
assert "level" not in breadcrumbs[5]
120+
assert "origin" not in breadcrumbs[5]
121+
assert breadcrumbs[5]["data"] == {
122+
"thread.id": mock.ANY,
123+
"thread.name": mock.ANY,
124+
"span5_data": "data on the subprocess span",
125+
}
126+
assert breadcrumbs[5]["timestamp"] == mock.ANY
127+
128+
# Check for custom breadcrumbs on span6
129+
assert breadcrumbs[6]["message"] == "breadcrumb6"
130+
assert breadcrumbs[6]["type"] == "navigation"
131+
assert breadcrumbs[6]["category"] == "unit_tests.breadcrumbs"
132+
assert breadcrumbs[6]["level"] == "fatal"
133+
assert breadcrumbs[6]["origin"] == "unit-tests"
134+
assert breadcrumbs[6]["data"] == {
135+
"string": "foobar",
136+
"number": 4.2,
137+
"array": [1, 2, 3],
138+
"dict": {"foo": "bar"},
139+
}
140+
assert breadcrumbs[6]["timestamp"] == mock.ANY

0 commit comments

Comments
 (0)
0