Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- 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))

### Fixes

- 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))

### Dependencies

- Bump Native SDK from v0.12.4 to v0.12.6 ([#5071](https://github.com/getsentry/sentry-java/pull/5071))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,14 @@ private void mergeNativeCrashes(
if (mechanism != null) {
mechanism.setType(NativeExceptionMechanism.TOMBSTONE_MERGED.getValue());
}

// Don't overwrite existing messages in the native event
if (nativeEvent.getMessage() == null
|| nativeEvent.getMessage().getMessage() == null
|| nativeEvent.getMessage().getMessage().isEmpty()) {
nativeEvent.setMessage(tombstoneEvent.getMessage());
}

nativeEvent.setExceptions(tombstoneExceptions);
nativeEvent.setDebugMeta(tombstoneDebugMeta);
nativeEvent.setThreads(tombstoneThreads);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,146 @@ class TombstoneIntegrationTest : ApplicationExitIntegrationTestBase<TombstoneHin
}
}

@Test
fun `when native event has no message, tombstone message is applied`() {
val integration =
fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp) { options ->
val outboxDir = File(options.outboxPath!!)
outboxDir.mkdirs()
createNativeEnvelope(outboxDir, newTimestamp, messageJson = null)
}

fixture.addAppExitInfo(timestamp = newTimestamp)
integration.register(fixture.scopes, fixture.options)

verify(fixture.scopes)
.captureEvent(
check<SentryEvent> { event ->
// Tombstone message should be applied
assertNotNull(event.message)
assertNotNull(event.message!!.formatted)
// The message contains the signal info from the tombstone
assertTrue(event.message!!.formatted!!.contains("Fatal signal"))
},
any<Hint>(),
)
}

@Test
fun `when native event has message with null template, tombstone message is applied`() {
val integration =
fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp) { options ->
val outboxDir = File(options.outboxPath!!)
outboxDir.mkdirs()
createNativeEnvelope(
outboxDir,
newTimestamp,
messageJson = """{"formatted":"some formatted text"}""",
)
}

fixture.addAppExitInfo(timestamp = newTimestamp)
integration.register(fixture.scopes, fixture.options)

verify(fixture.scopes)
.captureEvent(
check<SentryEvent> { event ->
// Tombstone message should be applied
assertNotNull(event.message)
assertNotNull(event.message!!.formatted)
assertTrue(event.message!!.formatted!!.contains("Fatal signal"))
},
any<Hint>(),
)
}

@Test
fun `when native event has message with empty template, tombstone message is applied`() {
val integration =
fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp) { options ->
val outboxDir = File(options.outboxPath!!)
outboxDir.mkdirs()
createNativeEnvelope(
outboxDir,
newTimestamp,
messageJson = """{"message":"","formatted":"some formatted text"}""",
)
}

fixture.addAppExitInfo(timestamp = newTimestamp)
integration.register(fixture.scopes, fixture.options)

verify(fixture.scopes)
.captureEvent(
check<SentryEvent> { event ->
// Tombstone message should be applied
assertNotNull(event.message)
assertNotNull(event.message!!.formatted)
assertTrue(event.message!!.formatted!!.contains("Fatal signal"))
},
any<Hint>(),
)
}

@Test
fun `when native event has message with content, native message is preserved`() {
val integration =
fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp) { options ->
val outboxDir = File(options.outboxPath!!)
outboxDir.mkdirs()
createNativeEnvelope(
outboxDir,
newTimestamp,
messageJson =
"""{"message":"Native SDK crash message","formatted":"The crash happened at 0xDEADBEEF"}""",
)
}

fixture.addAppExitInfo(timestamp = newTimestamp)
integration.register(fixture.scopes, fixture.options)

verify(fixture.scopes)
.captureEvent(
check<SentryEvent> { event ->
// Native SDK message should be preserved
assertNotNull(event.message)
assertEquals("Native SDK crash message", event.message!!.message)
assertEquals("The crash happened at 0xDEADBEEF", event.message!!.formatted)
},
any<Hint>(),
)
}

/**
* Creates a native envelope file with an optional message field.
*
* @param messageJson The JSON for the message field (e.g.,
* `{"message":"text","formatted":"text"}`), or null to omit the message field entirely.
*/
private fun createNativeEnvelope(
outboxDir: File,
timestamp: Long,
messageJson: String? = null,
fileName: String = "native-envelope.envelope",
): File {
val isoTimestamp = DateUtils.getTimestamp(DateUtils.getDateTime(timestamp))
val messageField = if (messageJson != null) ""","message":$messageJson""" else ""

val eventJson =
"""{"event_id":"9ec79c33ec9942ab8353589fcb2e04dc","timestamp":"$isoTimestamp","platform":"native","level":"fatal"$messageField}"""
val eventJsonSize = eventJson.toByteArray(Charsets.UTF_8).size

val envelopeContent =
"""
{"event_id":"9ec79c33ec9942ab8353589fcb2e04dc"}
{"type":"event","length":$eventJsonSize,"content_type":"application/json"}
$eventJson
"""
.trimIndent()

return File(outboxDir, fileName).apply { writeText(envelopeContent) }
}

private fun createNativeEnvelopeWithAttachment(outboxDir: File, timestamp: Long): File {
val isoTimestamp = DateUtils.getTimestamp(DateUtils.getDateTime(timestamp))

Expand Down
Loading