From 6f13618a88a07669b36b94f7cc5088ce45b1b22a Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 13 Aug 2026 11:41:39 +0200 Subject: [PATCH 1/2] fix(jul): Preserve null-message events and breadcrumbs Skip only Sentry Logs conversion when a valid JUL record has no message, allowing its event and breadcrumb capture paths to continue. Co-Authored-By: Claude --- .../java/io/sentry/jul/SentryHandler.java | 11 +++++--- .../kotlin/io/sentry/jul/SentryHandlerTest.kt | 25 +++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java b/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java index c606bdf88b6..bb1cce04445 100644 --- a/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java @@ -150,10 +150,15 @@ protected void captureLog(@NotNull LogRecord loggingEvent) { final @Nullable Object[] arguments = loggingEvent.getParameters(); final @NotNull SentryAttributes attributes = SentryAttributes.of(); - @NotNull String message = loggingEvent.getMessage(); + final @Nullable String messageTemplate = loggingEvent.getMessage(); + if (messageTemplate == null) { + return; + } + + @NotNull String message = messageTemplate; if (loggingEvent.getResourceBundle() != null - && loggingEvent.getResourceBundle().containsKey(loggingEvent.getMessage())) { - message = loggingEvent.getResourceBundle().getString(loggingEvent.getMessage()); + && loggingEvent.getResourceBundle().containsKey(messageTemplate)) { + message = loggingEvent.getResourceBundle().getString(messageTemplate); } final @NotNull String formattedMessage = maybeFormatted(arguments, message); diff --git a/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt b/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt index 2cd10a81cd4..29dbaeb3688 100644 --- a/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt +++ b/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt @@ -485,6 +485,31 @@ class SentryHandlerTest { verify(fixture.transport, never()).send(checkLogs {}) } + @Test + fun `captures null message as event and breadcrumb when logs are enabled`() { + fixture = + Fixture( + minimumBreadcrumbLevel = Level.INFO, + minimumEventLevel = Level.SEVERE, + enableLogs = true, + ) + + fixture.logger.info(null as String?) + fixture.logger.severe(null as String?) + Sentry.flush(10) + + verify(fixture.transport) + .send( + checkEvent { event -> + assertNull(event.message?.message) + assertEquals(1, event.breadcrumbs?.size) + assertNull(event.breadcrumbs?.single()?.message) + }, + anyOrNull(), + ) + verify(fixture.transport, never()).send(checkLogs {}) + } + @Test fun `converts finest log level to Sentry log level`() { fixture = Fixture(minimumLevel = Level.FINEST) From 8a070f8ba9a10b529106550da4264f8f95e61c6b Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 30 Sep 2026 16:18:08 +0200 Subject: [PATCH 2/2] fix(jul): Handle null messages with resource bundles Skip resource-bundle lookup and message formatting when a JUL record has no message. Preserve event and breadcrumb capture instead of aborting with a null-pointer exception. Co-Authored-By: Claude --- .../java/io/sentry/jul/SentryHandler.java | 14 +++++--- .../kotlin/io/sentry/jul/SentryHandlerTest.kt | 36 +++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java b/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java index bb1cce04445..685402e27bd 100644 --- a/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java +++ b/sentry-jul/src/main/java/io/sentry/jul/SentryHandler.java @@ -28,6 +28,7 @@ import java.util.Date; import java.util.List; import java.util.Map; +import java.util.ResourceBundle; import java.util.logging.ErrorManager; import java.util.logging.Filter; import java.util.logging.Handler; @@ -295,13 +296,16 @@ SentryEvent createEvent(final @NotNull LogRecord record) { final Message sentryMessage = new Message(); sentryMessage.setParams(toParams(record.getParameters())); - String message = record.getMessage(); - if (record.getResourceBundle() != null - && record.getResourceBundle().containsKey(record.getMessage())) { - message = record.getResourceBundle().getString(record.getMessage()); + final @Nullable String messageTemplate = record.getMessage(); + final @Nullable ResourceBundle resourceBundle = record.getResourceBundle(); + @Nullable String message = messageTemplate; + if (messageTemplate != null + && resourceBundle != null + && resourceBundle.containsKey(messageTemplate)) { + message = resourceBundle.getString(messageTemplate); } sentryMessage.setMessage(message); - if (record.getParameters() != null) { + if (message != null && record.getParameters() != null) { try { sentryMessage.setFormatted(formatMessage(message, record.getParameters())); } catch (RuntimeException e) { diff --git a/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt b/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt index 29dbaeb3688..028c6759727 100644 --- a/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt +++ b/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerTest.kt @@ -16,7 +16,9 @@ import io.sentry.transport.ITransport import java.time.Instant import java.time.LocalDateTime import java.time.ZoneId +import java.util.ListResourceBundle import java.util.logging.Level +import java.util.logging.LogRecord import java.util.logging.Logger import kotlin.test.AfterTest import kotlin.test.BeforeTest @@ -510,6 +512,40 @@ class SentryHandlerTest { verify(fixture.transport, never()).send(checkLogs {}) } + @Test + fun `captures null message as event and breadcrumb when resource bundle is set`() { + fixture = + Fixture( + minimumBreadcrumbLevel = Level.INFO, + minimumEventLevel = Level.SEVERE, + enableLogs = true, + ) + val resourceBundle = + object : ListResourceBundle() { + override fun getContents(): Array> = + arrayOf(arrayOf("message", "localized message")) + } + + fixture.handler.publish( + LogRecord(Level.INFO, null).apply { this.resourceBundle = resourceBundle } + ) + fixture.handler.publish( + LogRecord(Level.SEVERE, null).apply { this.resourceBundle = resourceBundle } + ) + Sentry.flush(10) + + verify(fixture.transport) + .send( + checkEvent { event -> + assertNull(event.message?.message) + assertEquals(1, event.breadcrumbs?.size) + assertNull(event.breadcrumbs?.single()?.message) + }, + anyOrNull(), + ) + verify(fixture.transport, never()).send(checkLogs {}) + } + @Test fun `converts finest log level to Sentry log level`() { fixture = Fixture(minimumLevel = Level.FINEST)