Skip to content

Commit aefefa6

Browse files
authored
fix(android): merge tombstone and Native SDK event message. (#5095)
1 parent ff8eea4 commit aefefa6

File tree

3 files changed

+152
-0
lines changed

3 files changed

+152
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
- Add `installGroupsOverride` parameter to Build Distribution SDK for programmatic filtering, with support for configuration via properties file using `io.sentry.distribution.install-groups-override` ([#5066](https://github.com/getsentry/sentry-java/pull/5066))
88

9+
### Fixes
10+
11+
- When merging tombstones with Native SDK, use the tombstone message if the Native SDK didn't explicitly provide one. ([#5095](https://github.com/getsentry/sentry-java/pull/5095))
12+
913
### Dependencies
1014

1115
- Bump Native SDK from v0.12.4 to v0.12.6 ([#5071](https://github.com/getsentry/sentry-java/pull/5071))

sentry-android-core/src/main/java/io/sentry/android/core/TombstoneIntegration.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,14 @@ private void mergeNativeCrashes(
291291
if (mechanism != null) {
292292
mechanism.setType(NativeExceptionMechanism.TOMBSTONE_MERGED.getValue());
293293
}
294+
295+
// Don't overwrite existing messages in the native event
296+
if (nativeEvent.getMessage() == null
297+
|| nativeEvent.getMessage().getMessage() == null
298+
|| nativeEvent.getMessage().getMessage().isEmpty()) {
299+
nativeEvent.setMessage(tombstoneEvent.getMessage());
300+
}
301+
294302
nativeEvent.setExceptions(tombstoneExceptions);
295303
nativeEvent.setDebugMeta(tombstoneDebugMeta);
296304
nativeEvent.setThreads(tombstoneThreads);

sentry-android-core/src/test/java/io/sentry/android/core/TombstoneIntegrationTest.kt

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,146 @@ class TombstoneIntegrationTest : ApplicationExitIntegrationTestBase<TombstoneHin
186186
}
187187
}
188188

189+
@Test
190+
fun `when native event has no message, tombstone message is applied`() {
191+
val integration =
192+
fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp) { options ->
193+
val outboxDir = File(options.outboxPath!!)
194+
outboxDir.mkdirs()
195+
createNativeEnvelope(outboxDir, newTimestamp, messageJson = null)
196+
}
197+
198+
fixture.addAppExitInfo(timestamp = newTimestamp)
199+
integration.register(fixture.scopes, fixture.options)
200+
201+
verify(fixture.scopes)
202+
.captureEvent(
203+
check<SentryEvent> { event ->
204+
// Tombstone message should be applied
205+
assertNotNull(event.message)
206+
assertNotNull(event.message!!.formatted)
207+
// The message contains the signal info from the tombstone
208+
assertTrue(event.message!!.formatted!!.contains("Fatal signal"))
209+
},
210+
any<Hint>(),
211+
)
212+
}
213+
214+
@Test
215+
fun `when native event has message with null template, tombstone message is applied`() {
216+
val integration =
217+
fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp) { options ->
218+
val outboxDir = File(options.outboxPath!!)
219+
outboxDir.mkdirs()
220+
createNativeEnvelope(
221+
outboxDir,
222+
newTimestamp,
223+
messageJson = """{"formatted":"some formatted text"}""",
224+
)
225+
}
226+
227+
fixture.addAppExitInfo(timestamp = newTimestamp)
228+
integration.register(fixture.scopes, fixture.options)
229+
230+
verify(fixture.scopes)
231+
.captureEvent(
232+
check<SentryEvent> { event ->
233+
// Tombstone message should be applied
234+
assertNotNull(event.message)
235+
assertNotNull(event.message!!.formatted)
236+
assertTrue(event.message!!.formatted!!.contains("Fatal signal"))
237+
},
238+
any<Hint>(),
239+
)
240+
}
241+
242+
@Test
243+
fun `when native event has message with empty template, tombstone message is applied`() {
244+
val integration =
245+
fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp) { options ->
246+
val outboxDir = File(options.outboxPath!!)
247+
outboxDir.mkdirs()
248+
createNativeEnvelope(
249+
outboxDir,
250+
newTimestamp,
251+
messageJson = """{"message":"","formatted":"some formatted text"}""",
252+
)
253+
}
254+
255+
fixture.addAppExitInfo(timestamp = newTimestamp)
256+
integration.register(fixture.scopes, fixture.options)
257+
258+
verify(fixture.scopes)
259+
.captureEvent(
260+
check<SentryEvent> { event ->
261+
// Tombstone message should be applied
262+
assertNotNull(event.message)
263+
assertNotNull(event.message!!.formatted)
264+
assertTrue(event.message!!.formatted!!.contains("Fatal signal"))
265+
},
266+
any<Hint>(),
267+
)
268+
}
269+
270+
@Test
271+
fun `when native event has message with content, native message is preserved`() {
272+
val integration =
273+
fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp) { options ->
274+
val outboxDir = File(options.outboxPath!!)
275+
outboxDir.mkdirs()
276+
createNativeEnvelope(
277+
outboxDir,
278+
newTimestamp,
279+
messageJson =
280+
"""{"message":"Native SDK crash message","formatted":"The crash happened at 0xDEADBEEF"}""",
281+
)
282+
}
283+
284+
fixture.addAppExitInfo(timestamp = newTimestamp)
285+
integration.register(fixture.scopes, fixture.options)
286+
287+
verify(fixture.scopes)
288+
.captureEvent(
289+
check<SentryEvent> { event ->
290+
// Native SDK message should be preserved
291+
assertNotNull(event.message)
292+
assertEquals("Native SDK crash message", event.message!!.message)
293+
assertEquals("The crash happened at 0xDEADBEEF", event.message!!.formatted)
294+
},
295+
any<Hint>(),
296+
)
297+
}
298+
299+
/**
300+
* Creates a native envelope file with an optional message field.
301+
*
302+
* @param messageJson The JSON for the message field (e.g.,
303+
* `{"message":"text","formatted":"text"}`), or null to omit the message field entirely.
304+
*/
305+
private fun createNativeEnvelope(
306+
outboxDir: File,
307+
timestamp: Long,
308+
messageJson: String? = null,
309+
fileName: String = "native-envelope.envelope",
310+
): File {
311+
val isoTimestamp = DateUtils.getTimestamp(DateUtils.getDateTime(timestamp))
312+
val messageField = if (messageJson != null) ""","message":$messageJson""" else ""
313+
314+
val eventJson =
315+
"""{"event_id":"9ec79c33ec9942ab8353589fcb2e04dc","timestamp":"$isoTimestamp","platform":"native","level":"fatal"$messageField}"""
316+
val eventJsonSize = eventJson.toByteArray(Charsets.UTF_8).size
317+
318+
val envelopeContent =
319+
"""
320+
{"event_id":"9ec79c33ec9942ab8353589fcb2e04dc"}
321+
{"type":"event","length":$eventJsonSize,"content_type":"application/json"}
322+
$eventJson
323+
"""
324+
.trimIndent()
325+
326+
return File(outboxDir, fileName).apply { writeText(envelopeContent) }
327+
}
328+
189329
private fun createNativeEnvelopeWithAttachment(outboxDir: File, timestamp: Long): File {
190330
val isoTimestamp = DateUtils.getTimestamp(DateUtils.getDateTime(timestamp))
191331

0 commit comments

Comments
 (0)