Skip to content

Commit cc9eccd

Browse files
committed
feat(java): Pass exception to symbolicate request
1 parent 853a7ce commit cc9eccd

File tree

3 files changed

+75
-0
lines changed

3 files changed

+75
-0
lines changed

src/sentry/lang/java/processing.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ def process_jvm_stacktraces(symbolicator: Symbolicator, data: Any) -> Any:
182182
stacktrace_infos = find_stacktraces_in_data(data)
183183
stacktraces = [
184184
{
185+
**(
186+
{"exception": {"type": sinfo.exception_type, "module": sinfo.exception_module}}
187+
if sinfo.get_exception()
188+
else {}
189+
),
185190
"frames": [
186191
_normalize_frame(frame, index)
187192
for index, frame in enumerate(sinfo.stacktrace.get("frames") or ())

src/sentry/stacktraces/processing.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class StacktraceInfo(NamedTuple):
2828
container: dict[str, Any]
2929
platforms: set[str]
3030
is_exception: bool
31+
exception_type: str | None
32+
exception_module: str | None
3133

3234
def __hash__(self) -> int:
3335
return id(self)
@@ -38,6 +40,14 @@ def __eq__(self, other: object) -> bool:
3840
def __ne__(self, other: object) -> bool:
3941
return self is not other
4042

43+
def get_exception(self) -> str | None:
44+
"""Returns the fully qualified exception name (module.type) or None if not an exception."""
45+
if self.exception_type is None:
46+
return None
47+
if self.exception_module:
48+
return f"{self.exception_module}.{self.exception_type}"
49+
return self.exception_type
50+
4151
def get_frames(self) -> Sequence[dict[str, Any]]:
4252
return _safe_get_frames(self.stacktrace)
4353

@@ -213,6 +223,10 @@ def _append_stacktrace(
213223
container: Any = None,
214224
# Whether or not the container is from `exception.values`
215225
is_exception: bool = False,
226+
# The exception type (e.g., "ValueError") if this stacktrace belongs to an exception
227+
exception_type: str | None = None,
228+
# The exception module (e.g., "__builtins__") if this stacktrace belongs to an exception
229+
exception_module: str | None = None,
216230
# Prevent skipping empty/null stacktraces from `exception.values` (other empty/null
217231
# stacktraces are always skipped)
218232
include_empty_exceptions: bool = False,
@@ -232,6 +246,8 @@ def _append_stacktrace(
232246
container=container,
233247
platforms=platforms,
234248
is_exception=is_exception,
249+
exception_type=exception_type,
250+
exception_module=exception_module,
235251
)
236252
)
237253

@@ -241,6 +257,8 @@ def _append_stacktrace(
241257
exc.get("stacktrace"),
242258
container=exc,
243259
is_exception=True,
260+
exception_type=exc.get("type"),
261+
exception_module=exc.get("module"),
244262
include_empty_exceptions=include_empty_exceptions,
245263
)
246264

tests/sentry/test_stacktraces.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ def test_stacktraces_basics(self) -> None:
3636
assert len(infos) == 1
3737
assert len(infos[0].stacktrace["frames"]) == 2
3838
assert infos[0].platforms == {"javascript", "native"}
39+
# Top-level stacktraces are not exceptions
40+
assert infos[0].is_exception is False
41+
assert infos[0].exception_type is None
42+
assert infos[0].exception_module is None
43+
assert infos[0].get_exception() is None
3944

4045
def test_stacktraces_exception(self) -> None:
4146
data: dict[str, Any] = {
@@ -69,6 +74,42 @@ def test_stacktraces_exception(self) -> None:
6974
infos = find_stacktraces_in_data(data)
7075
assert len(infos) == 1
7176
assert len(infos[0].stacktrace["frames"]) == 2
77+
# Exception stacktraces have type but no module in this case
78+
assert infos[0].is_exception is True
79+
assert infos[0].exception_type == "Error"
80+
assert infos[0].exception_module is None
81+
assert infos[0].get_exception() == "Error"
82+
83+
def test_stacktraces_exception_with_module(self) -> None:
84+
data: dict[str, Any] = {
85+
"message": "hello",
86+
"platform": "java",
87+
"exception": {
88+
"values": [
89+
{
90+
"type": "RuntimeException",
91+
"module": "java.lang",
92+
"stacktrace": {
93+
"frames": [
94+
{
95+
"function": "main",
96+
"module": "com.example.App",
97+
"filename": "App.java",
98+
"lineno": 10,
99+
},
100+
]
101+
},
102+
}
103+
]
104+
},
105+
}
106+
107+
infos = find_stacktraces_in_data(data)
108+
assert len(infos) == 1
109+
assert infos[0].is_exception is True
110+
assert infos[0].exception_type == "RuntimeException"
111+
assert infos[0].exception_module == "java.lang"
112+
assert infos[0].get_exception() == "java.lang.RuntimeException"
72113

73114
def test_stacktraces_threads(self) -> None:
74115
data: dict[str, Any] = {
@@ -102,6 +143,11 @@ def test_stacktraces_threads(self) -> None:
102143
infos = find_stacktraces_in_data(data)
103144
assert len(infos) == 1
104145
assert len(infos[0].stacktrace["frames"]) == 2
146+
# Thread stacktraces are not exceptions
147+
assert infos[0].is_exception is False
148+
assert infos[0].exception_type is None
149+
assert infos[0].exception_module is None
150+
assert infos[0].get_exception() is None
105151

106152
def test_find_stacktraces_skip_none(self) -> None:
107153
# This tests:
@@ -147,13 +193,19 @@ def test_find_stacktraces_skip_none(self) -> None:
147193
assert len(infos) == 4
148194
assert sum(1 for x in infos if x.stacktrace) == 3
149195
assert sum(1 for x in infos if x.is_exception) == 4
196+
# All exceptions have type "Error" and no module
197+
assert all(x.exception_type == "Error" for x in infos)
198+
assert all(x.exception_module is None for x in infos)
199+
assert all(x.get_exception() == "Error" for x in infos)
150200
# XXX: The null frame is still part of this stack trace!
151201
assert len(infos[3].stacktrace["frames"]) == 3
152202

153203
infos = find_stacktraces_in_data(data)
154204
assert len(infos) == 1
155205
# XXX: The null frame is still part of this stack trace!
156206
assert len(infos[0].stacktrace["frames"]) == 3
207+
assert infos[0].exception_type == "Error"
208+
assert infos[0].get_exception() == "Error"
157209

158210

159211
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)