Skip to content

Commit 1d78eb0

Browse files
authored
feat: Add integration for parsing GNU-style backtrace dumps (#288)
* feat: Add integration for parsing GNU-style backtrace dumps * fix: Formatting * fix: py2 compat * fix: Set native platform
1 parent d719ce0 commit 1d78eb0

File tree

3 files changed

+316
-5
lines changed

3 files changed

+316
-5
lines changed

sentry_sdk/integrations/django/__init__.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,19 +141,23 @@ def sentry_patched_get_response(self, request):
141141
@add_global_event_processor
142142
def process_django_templates(event, hint):
143143
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
144-
if hint.get("exc_info", None) is None:
144+
exc_info = hint.get("exc_info", None)
145+
146+
if exc_info is None:
145147
return event
146148

147-
if "exception" not in event:
149+
exception = event.get("exception", None)
150+
151+
if exception is None:
148152
return event
149153

150-
exception = event["exception"]
154+
values = exception.get("values", None)
151155

152-
if "values" not in exception:
156+
if values is None:
153157
return event
154158

155159
for exception, (_, exc_value, _) in zip(
156-
exception["values"], walk_exception_chain(hint["exc_info"])
160+
values, walk_exception_chain(exc_info)
157161
):
158162
frame = get_template_frame_from_exception(exc_value)
159163
if frame is not None:
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import re
2+
3+
from sentry_sdk.hub import Hub
4+
from sentry_sdk.integrations import Integration
5+
from sentry_sdk.scope import add_global_event_processor
6+
from sentry_sdk.utils import walk_exception_chain, capture_internal_exceptions
7+
8+
if False:
9+
from typing import Any
10+
from typing import Dict
11+
12+
13+
MODULE_RE = r"[a-zA-Z0-9/._:\\-]+"
14+
TYPE_RE = r"[a-zA-Z0-9._:<>,-]+"
15+
HEXVAL_RE = r"[A-Fa-f0-9]+"
16+
17+
18+
FRAME_RE = r"""
19+
^(?P<index>\d+)\.\s
20+
(?P<package>{MODULE_RE})\(
21+
(?P<retval>{TYPE_RE}\ )?
22+
((?P<function>{TYPE_RE})
23+
(?P<args>\([^)]*\))?
24+
)?
25+
((?P<constoffset>\ const)?\+0x(?P<offset>{HEXVAL_RE}))?
26+
\)\s
27+
\[0x(?P<retaddr>{HEXVAL_RE})\]$
28+
""".format(
29+
MODULE_RE=MODULE_RE, HEXVAL_RE=HEXVAL_RE, TYPE_RE=TYPE_RE
30+
)
31+
32+
FRAME_RE = re.compile(FRAME_RE, re.MULTILINE | re.VERBOSE)
33+
34+
35+
class GnuBacktraceIntegration(Integration):
36+
identifier = "gnu_backtrace"
37+
38+
@staticmethod
39+
def setup_once():
40+
@add_global_event_processor
41+
def process_gnu_backtrace(event, hint):
42+
with capture_internal_exceptions():
43+
return _process_gnu_backtrace(event, hint)
44+
45+
46+
def _process_gnu_backtrace(event, hint):
47+
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
48+
if Hub.current.get_integration(GnuBacktraceIntegration) is None:
49+
return event
50+
51+
exc_info = hint.get("exc_info", None)
52+
53+
if exc_info is None:
54+
return event
55+
56+
exception = event.get("exception", None)
57+
58+
if exception is None:
59+
return event
60+
61+
values = exception.get("values", None)
62+
63+
if values is None:
64+
return event
65+
66+
for exception in values:
67+
frames = exception.get("stacktrace", {}).get("frames", [])
68+
if not frames:
69+
continue
70+
71+
msg = exception.get("value", None)
72+
if not msg:
73+
continue
74+
75+
additional_frames = []
76+
new_msg = []
77+
78+
for line in msg.splitlines():
79+
match = FRAME_RE.match(line)
80+
if match:
81+
additional_frames.append(
82+
(
83+
int(match.group("index")),
84+
{
85+
"package": match.group("package") or None,
86+
"function": match.group("function") or None,
87+
"platform": "native",
88+
},
89+
)
90+
)
91+
elif additional_frames and line.strip():
92+
# If we already started parsing a stacktrace, it must be at the
93+
# end of the message and must not contain random garbage lines
94+
# between the frames
95+
del additional_frames[:]
96+
break
97+
else:
98+
new_msg.append(line)
99+
100+
if additional_frames:
101+
additional_frames.sort(key=lambda x: -x[0])
102+
for _, frame in additional_frames:
103+
frames.append(frame)
104+
105+
new_msg.append("<stacktrace parsed and removed by GnuBacktraceIntegration>")
106+
exception["value"] = "\n".join(new_msg)
107+
108+
return event
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
from sentry_sdk import capture_exception
2+
from sentry_sdk.integrations.gnu_backtrace import GnuBacktraceIntegration
3+
4+
EXC_VALUE = r"""
5+
DB::Exception: Cannot parse datetime. Stack trace:
6+
7+
0. clickhouse-server(StackTrace::StackTrace()+0x16) [0x99d31a6]
8+
1. clickhouse-server(DB::Exception::Exception(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int)+0x22) [0x3089bb2]
9+
2. clickhouse-server(void DB::readDateTimeTextFallback<void>(long&, DB::ReadBuffer&, DateLUTImpl const&)+0x318) [0x99ffed8]
10+
3. clickhouse-server(DB::FunctionComparison<DB::NotEqualsOp, DB::NameNotEquals>::executeDateOrDateTimeOrEnumOrUUIDWithConstString(DB::Block&, unsigned long, DB::IColumn const*, DB::IColumn const*, std::shared_ptr<DB::IDataType const> const&, std::shared_ptr<DB::IDataType const> const&, bool, unsigned long)+0xbb3) [0x411dee3]
11+
4. clickhouse-server(DB::FunctionComparison<DB::NotEqualsOp, DB::NameNotEquals>::executeImpl(DB::Block&, std::vector<unsigned long, std::allocator<unsigned long> > const&, unsigned long, unsigned long)+0x576) [0x41ab006]
12+
5. clickhouse-server(DB::PreparedFunctionImpl::execute(DB::Block&, std::vector<unsigned long, std::allocator<unsigned long> > const&, unsigned long, unsigned long)+0x3e2) [0x7933492]
13+
6. clickhouse-server(DB::ExpressionAction::execute(DB::Block&, std::unordered_map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, unsigned long, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, unsigned long> > >&) const+0x61a) [0x7ae093a]
14+
7. clickhouse-server(DB::ExpressionActions::execute(DB::Block&) const+0xe6) [0x7ae1e06]
15+
8. clickhouse-server(DB::FilterBlockInputStream::FilterBlockInputStream(std::shared_ptr<DB::IBlockInputStream> const&, std::shared_ptr<DB::ExpressionActions> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool)+0x711) [0x79970d1]
16+
9. clickhouse-server() [0x75bd5a3]
17+
10. clickhouse-server(DB::InterpreterSelectQuery::executeImpl(DB::InterpreterSelectQuery::Pipeline&, std::shared_ptr<DB::IBlockInputStream> const&, bool)+0x11af) [0x75c68ff]
18+
11. clickhouse-server(DB::InterpreterSelectQuery::InterpreterSelectQuery(std::shared_ptr<DB::IAST> const&, DB::Context const&, std::shared_ptr<DB::IBlockInputStream> const&, std::shared_ptr<DB::IStorage> const&, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&, DB::QueryProcessingStage::Enum, unsigned long, bool)+0x5e6) [0x75c7516]
19+
12. clickhouse-server(DB::InterpreterSelectQuery::InterpreterSelectQuery(std::shared_ptr<DB::IAST> const&, DB::Context const&, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&, DB::QueryProcessingStage::Enum, unsigned long, bool)+0x56) [0x75c8276]
20+
13. clickhouse-server(DB::InterpreterSelectWithUnionQuery::InterpreterSelectWithUnionQuery(std::shared_ptr<DB::IAST> const&, DB::Context const&, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&, DB::QueryProcessingStage::Enum, unsigned long, bool)+0x7e7) [0x75d4067]
21+
14. clickhouse-server(DB::InterpreterFactory::get(std::shared_ptr<DB::IAST>&, DB::Context&, DB::QueryProcessingStage::Enum)+0x3a8) [0x75b0298]
22+
15. clickhouse-server() [0x7664c79]
23+
16. clickhouse-server(DB::executeQuery(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, DB::Context&, bool, DB::QueryProcessingStage::Enum)+0x8a) [0x76669fa]
24+
17. clickhouse-server(DB::TCPHandler::runImpl()+0x4b9) [0x30973c9]
25+
18. clickhouse-server(DB::TCPHandler::run()+0x2b) [0x30985ab]
26+
19. clickhouse-server(Poco::Net::TCPServerConnection::start()+0xf) [0x9b53e4f]
27+
20. clickhouse-server(Poco::Net::TCPServerDispatcher::run()+0x16a) [0x9b5422a]
28+
21. clickhouse-server(Poco::PooledThread::run()+0x77) [0x9c70f37]
29+
22. clickhouse-server(Poco::ThreadImpl::runnableEntry(void*)+0x38) [0x9c6caa8]
30+
23. clickhouse-server() [0xa3c68cf]
31+
24. /lib/x86_64-linux-gnu/libpthread.so.0(+0x8184) [0x7fe839d2d184]
32+
25. /lib/x86_64-linux-gnu/libc.so.6(clone+0x6d) [0x7fe83934803d]
33+
"""
34+
35+
36+
def test_basic(sentry_init, capture_events):
37+
sentry_init(integrations=[GnuBacktraceIntegration()])
38+
events = capture_events()
39+
40+
try:
41+
raise ValueError(EXC_VALUE)
42+
except ValueError:
43+
capture_exception()
44+
45+
event, = events
46+
import pdb
47+
48+
pdb.set_trace()
49+
exception, = event["exception"]["values"]
50+
51+
assert exception["value"] == (
52+
"\n"
53+
"DB::Exception: Cannot parse datetime. Stack trace:\n"
54+
"\n"
55+
"<stacktrace parsed and removed by GnuBacktraceIntegration>"
56+
)
57+
58+
assert exception["stacktrace"]["frames"][1:] == [
59+
{
60+
"function": "clone",
61+
"in_app": True,
62+
"package": "/lib/x86_64-linux-gnu/libc.so.6",
63+
"platform": "native",
64+
},
65+
{
66+
"in_app": True,
67+
"package": "/lib/x86_64-linux-gnu/libpthread.so.0",
68+
"platform": "native",
69+
},
70+
{"in_app": True, "package": "clickhouse-server", "platform": "native"},
71+
{
72+
"function": "Poco::ThreadImpl::runnableEntry",
73+
"in_app": True,
74+
"package": "clickhouse-server",
75+
"platform": "native",
76+
},
77+
{
78+
"function": "Poco::PooledThread::run",
79+
"in_app": True,
80+
"package": "clickhouse-server",
81+
"platform": "native",
82+
},
83+
{
84+
"function": "Poco::Net::TCPServerDispatcher::run",
85+
"in_app": True,
86+
"package": "clickhouse-server",
87+
"platform": "native",
88+
},
89+
{
90+
"function": "Poco::Net::TCPServerConnection::start",
91+
"in_app": True,
92+
"package": "clickhouse-server",
93+
"platform": "native",
94+
},
95+
{
96+
"function": "DB::TCPHandler::run",
97+
"in_app": True,
98+
"package": "clickhouse-server",
99+
"platform": "native",
100+
},
101+
{
102+
"function": "DB::TCPHandler::runImpl",
103+
"in_app": True,
104+
"package": "clickhouse-server",
105+
"platform": "native",
106+
},
107+
{
108+
"function": "DB::executeQuery",
109+
"in_app": True,
110+
"package": "clickhouse-server",
111+
"platform": "native",
112+
},
113+
{"in_app": True, "package": "clickhouse-server", "platform": "native"},
114+
{
115+
"function": "DB::InterpreterFactory::get",
116+
"in_app": True,
117+
"package": "clickhouse-server",
118+
"platform": "native",
119+
},
120+
{
121+
"function": "DB::InterpreterSelectWithUnionQuery::InterpreterSelectWithUnionQuery",
122+
"in_app": True,
123+
"package": "clickhouse-server",
124+
"platform": "native",
125+
},
126+
{
127+
"function": "DB::InterpreterSelectQuery::InterpreterSelectQuery",
128+
"in_app": True,
129+
"package": "clickhouse-server",
130+
"platform": "native",
131+
},
132+
{
133+
"function": "DB::InterpreterSelectQuery::InterpreterSelectQuery",
134+
"in_app": True,
135+
"package": "clickhouse-server",
136+
"platform": "native",
137+
},
138+
{
139+
"function": "DB::InterpreterSelectQuery::executeImpl",
140+
"in_app": True,
141+
"package": "clickhouse-server",
142+
"platform": "native",
143+
},
144+
{"in_app": True, "package": "clickhouse-server", "platform": "native"},
145+
{
146+
"function": "DB::FilterBlockInputStream::FilterBlockInputStream",
147+
"in_app": True,
148+
"package": "clickhouse-server",
149+
"platform": "native",
150+
},
151+
{
152+
"function": "DB::ExpressionActions::execute",
153+
"in_app": True,
154+
"package": "clickhouse-server",
155+
"platform": "native",
156+
},
157+
{
158+
"function": "DB::ExpressionAction::execute",
159+
"in_app": True,
160+
"package": "clickhouse-server",
161+
"platform": "native",
162+
},
163+
{
164+
"function": "DB::PreparedFunctionImpl::execute",
165+
"in_app": True,
166+
"package": "clickhouse-server",
167+
"platform": "native",
168+
},
169+
{
170+
"function": "DB::NameNotEquals>::executeImpl",
171+
"in_app": True,
172+
"package": "clickhouse-server",
173+
"platform": "native",
174+
},
175+
{
176+
"function": "DB::NameNotEquals>::executeDateOrDateTimeOrEnumOrUUIDWithConstString",
177+
"in_app": True,
178+
"package": "clickhouse-server",
179+
"platform": "native",
180+
},
181+
{
182+
"function": "DB::readDateTimeTextFallback<void>",
183+
"in_app": True,
184+
"package": "clickhouse-server",
185+
"platform": "native",
186+
},
187+
{
188+
"function": "DB::Exception::Exception",
189+
"in_app": True,
190+
"package": "clickhouse-server",
191+
"platform": "native",
192+
},
193+
{
194+
"function": "StackTrace::StackTrace",
195+
"in_app": True,
196+
"package": "clickhouse-server",
197+
"platform": "native",
198+
},
199+
]

0 commit comments

Comments
 (0)