From 0cc3de457c3d4f3bc197a04621e8b5718745c350 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 12 Jun 2025 11:41:58 +0200 Subject: [PATCH 01/12] Add user id, username and email to log attributes --- .../main/java/io/sentry/logger/LoggerApi.java | 23 +++++++ .../main/java/io/sentry/protocol/User.java | 10 ++- sentry/src/test/java/io/sentry/ScopesTest.kt | 63 +++++++++++++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/sentry/src/main/java/io/sentry/logger/LoggerApi.java b/sentry/src/main/java/io/sentry/logger/LoggerApi.java index 71eccb4840..6a904e96c3 100644 --- a/sentry/src/main/java/io/sentry/logger/LoggerApi.java +++ b/sentry/src/main/java/io/sentry/logger/LoggerApi.java @@ -17,6 +17,7 @@ import io.sentry.SpanId; import io.sentry.protocol.SdkVersion; import io.sentry.protocol.SentryId; +import io.sentry.protocol.User; import io.sentry.util.Platform; import io.sentry.util.TracingUtils; import java.util.HashMap; @@ -213,6 +214,10 @@ private void captureLog( setServerName(attributes); } + if (scopes.getOptions().isSendDefaultPii()) { + setUser(attributes); + } + return attributes; } @@ -231,6 +236,24 @@ private void setServerName( } } + private void setUser(final @NotNull HashMap attributes) { + final @Nullable User user = scopes.getCombinedScopeView().getUser(); + if (user != null) { + final @Nullable String id = user.getId(); + if (id != null) { + attributes.put("user.id", new SentryLogEventAttributeValue("string", id)); + } + final @Nullable String username = user.getUsername(); + if (username != null) { + attributes.put("user.name", new SentryLogEventAttributeValue("string", username)); + } + final @Nullable String email = user.getEmail(); + if (email != null) { + attributes.put("user.email", new SentryLogEventAttributeValue("string", email)); + } + } + } + private @NotNull SentryAttributeType getType(final @Nullable Object arg) { if (arg instanceof Boolean) { return SentryAttributeType.BOOLEAN; diff --git a/sentry/src/main/java/io/sentry/protocol/User.java b/sentry/src/main/java/io/sentry/protocol/User.java index e19525eefc..0035f85c21 100644 --- a/sentry/src/main/java/io/sentry/protocol/User.java +++ b/sentry/src/main/java/io/sentry/protocol/User.java @@ -37,8 +37,10 @@ public final class User implements JsonUnknown, JsonSerializable { /** Remote IP address of the user. */ private @Nullable String ipAddress; - /** Human readable name. */ - private @Nullable String name; + /** + * @deprecated please use {@link User#username} Human readable name. + */ + @Deprecated private @Nullable String name; /** User geo location. */ private @Nullable Geo geo; @@ -215,7 +217,9 @@ public void setIpAddress(final @Nullable String ipAddress) { * Get human readable name. * * @return Human readable name + * @deprecated please use {@link User#getUsername()} */ + @Deprecated public @Nullable String getName() { return name; } @@ -224,7 +228,9 @@ public void setIpAddress(final @Nullable String ipAddress) { * Set human readable name. * * @param name Human readable name + * @deprecated please use {@link User#setUsername(String)} */ + @Deprecated public void setName(final @Nullable String name) { this.name = name; } diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index 49a9500216..f0dec629de 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -2851,6 +2851,69 @@ class ScopesTest { ) } + @Test + fun `adds user fields to log attributes if sendDefaultPii is true`() { + val (sut, mockClient) = getEnabledScopes { + it.logs.isEnabled = true + it.isSendDefaultPii = true + } + + sut.configureScope { scope -> + scope.user = User().also { + it.id = "usrid" + it.username = "usrname" + it.email = "user@sentry.io" + } + } + sut.logger().log(SentryLogLevel.WARN, "log message") + + verify(mockClient).captureLog( + check { + assertEquals("log message", it.body) + + val userId = it.attributes?.get("user.id")!! + assertEquals("usrid", userId.value) + assertEquals("string", userId.type) + + val userName = it.attributes?.get("user.name")!! + assertEquals("usrname", userName.value) + assertEquals("string", userName.type) + + val userEmail = it.attributes?.get("user.email")!! + assertEquals("user@sentry.io", userEmail.value) + assertEquals("string", userEmail.type) + }, + anyOrNull() + ) + } + + @Test + fun `does not add user fields to log attributes by default`() { + val (sut, mockClient) = getEnabledScopes { + it.logs.isEnabled = true + } + + sut.configureScope { scope -> + scope.user = User().also { + it.id = "usrid" + it.username = "usrname" + it.email = "user@sentry.io" + } + } + sut.logger().log(SentryLogLevel.WARN, "log message") + + verify(mockClient).captureLog( + check { + assertEquals("log message", it.body) + + assertNull(it.attributes?.get("user.id")) + assertNull(it.attributes?.get("user.name")) + assertNull(it.attributes?.get("user.email")) + }, + anyOrNull() + ) + } + //endregion @Test From cb1ebc81044e24bca0186cd6970af9bbcd570206 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 17 Jun 2025 09:11:42 +0200 Subject: [PATCH 02/12] wip --- sentry-logback/api/sentry-logback.api | 1 + .../io/sentry/logback/SentryAppender.java | 48 +++++++++++++++++++ sentry/src/test/java/io/sentry/ScopesTest.kt | 45 +++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/sentry-logback/api/sentry-logback.api b/sentry-logback/api/sentry-logback.api index 256e931e2a..642d104bb6 100644 --- a/sentry-logback/api/sentry-logback.api +++ b/sentry-logback/api/sentry-logback.api @@ -8,6 +8,7 @@ public class io/sentry/logback/SentryAppender : ch/qos/logback/core/Unsynchroniz public fun ()V protected fun append (Lch/qos/logback/classic/spi/ILoggingEvent;)V protected synthetic fun append (Ljava/lang/Object;)V + protected fun captureLog (Lch/qos/logback/classic/spi/ILoggingEvent;)V protected fun createBreadcrumb (Lch/qos/logback/classic/spi/ILoggingEvent;)Lio/sentry/Breadcrumb; protected fun createEvent (Lch/qos/logback/classic/spi/ILoggingEvent;)Lio/sentry/SentryEvent; public fun getMinimumBreadcrumbLevel ()Lch/qos/logback/classic/Level; diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index 7e96892d57..6810833311 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -19,6 +19,7 @@ import io.sentry.SentryEvent; import io.sentry.SentryIntegrationPackageStorage; import io.sentry.SentryLevel; +import io.sentry.SentryLogLevel; import io.sentry.SentryOptions; import io.sentry.exception.ExceptionMechanismException; import io.sentry.protocol.Mechanism; @@ -46,6 +47,7 @@ public class SentryAppender extends UnsynchronizedAppenderBase { private @Nullable ITransportFactory transportFactory; private @NotNull Level minimumBreadcrumbLevel = Level.INFO; private @NotNull Level minimumEventLevel = Level.ERROR; + private @NotNull Level minimumLevel = Level.INFO; private @Nullable Encoder encoder; static { @@ -78,6 +80,12 @@ public void start() { @Override protected void append(@NotNull ILoggingEvent eventObject) { + if (eventObject.getLevel().isGreaterOrEqual(minimumLevel)) { + final Hint hint = new Hint(); + hint.set(SENTRY_SYNTHETIC_EXCEPTION, eventObject); + + captureLog(eventObject); + } if (eventObject.getLevel().isGreaterOrEqual(minimumEventLevel)) { final Hint hint = new Hint(); hint.set(SENTRY_SYNTHETIC_EXCEPTION, eventObject); @@ -162,6 +170,26 @@ protected void append(@NotNull ILoggingEvent eventObject) { return event; } + /** + * Captures a Sentry log from Logback's {@link ILoggingEvent}. + * + * @param loggingEvent the logback event + */ + // for the Android compatibility we must use old Java Date class + @SuppressWarnings("JdkObsolete") + protected void captureLog(@NotNull ILoggingEvent loggingEvent) { + final @NotNull SentryLogLevel sentryLevel = toSentryLogLevel(loggingEvent.getLevel()); + + // // if encoder is set we treat message+params as PII as encoders may be used to mask/strip + // PII + // if (encoder == null || options.isSendDefaultPii()) { + // message.setMessage(loggingEvent.getMessage()); + // message.setParams(toParams(loggingEvent.getArgumentArray())); + // } + + Sentry.logger().log(sentryLevel, formatted(loggingEvent), loggingEvent.getArgumentArray()); + } + private String formatted(@NotNull ILoggingEvent loggingEvent) { if (encoder != null) { try { @@ -218,6 +246,26 @@ private String formatted(@NotNull ILoggingEvent loggingEvent) { } } + /** + * Transforms a {@link Level} into an {@link SentryLogLevel}. + * + * @param level original level as defined in log4j. + * @return log level used within sentry. + */ + private static @NotNull SentryLogLevel toSentryLogLevel(@NotNull Level level) { + if (level.isGreaterOrEqual(Level.ERROR)) { + return SentryLogLevel.ERROR; + } else if (level.isGreaterOrEqual(Level.WARN)) { + return SentryLogLevel.WARN; + } else if (level.isGreaterOrEqual(Level.INFO)) { + return SentryLogLevel.INFO; + } else if (level.isGreaterOrEqual(Level.DEBUG)) { + return SentryLogLevel.DEBUG; + } else { + return SentryLogLevel.TRACE; + } + } + private @NotNull SdkVersion createSdkVersion(@NotNull SentryOptions sentryOptions) { SdkVersion sdkVersion = sentryOptions.getSdkVersion(); diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index f0dec629de..9be7aeca8b 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -2887,6 +2887,51 @@ class ScopesTest { ) } + @Test + fun `missing user does not break attributes`() { + val (sut, mockClient) = getEnabledScopes { + it.logs.isEnabled = true + it.isSendDefaultPii = true + } + + sut.logger().log(SentryLogLevel.WARN, "log message") + + verify(mockClient).captureLog( + check { + assertEquals("log message", it.body) + + assertNull(it.attributes?.get("user.id")) + assertNull(it.attributes?.get("user.name")) + assertNull(it.attributes?.get("user.email")) + }, + anyOrNull() + ) + } + + @Test + fun `missing user fields do not break attributes`() { + val (sut, mockClient) = getEnabledScopes { + it.logs.isEnabled = true + it.isSendDefaultPii = true + } + + sut.configureScope { scope -> + scope.user = User() + } + sut.logger().log(SentryLogLevel.WARN, "log message") + + verify(mockClient).captureLog( + check { + assertEquals("log message", it.body) + + assertNull(it.attributes?.get("user.id")) + assertNull(it.attributes?.get("user.name")) + assertNull(it.attributes?.get("user.email")) + }, + anyOrNull() + ) + } + @Test fun `does not add user fields to log attributes by default`() { val (sut, mockClient) = getEnabledScopes { From be3965454d1bf8c1a02cfd984145bb3a1141cd57 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 18 Jun 2025 15:40:18 +0200 Subject: [PATCH 03/12] Check log event count before sending envelope --- .../src/main/java/io/sentry/logger/LoggerBatchProcessor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java b/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java index e83ab6d73d..08532b64ef 100644 --- a/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java +++ b/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java @@ -101,7 +101,9 @@ private void flushBatch() { } } while (!queue.isEmpty() && logEvents.size() < MAX_BATCH_SIZE); - client.captureBatchedLogEvents(new SentryLogEvents(logEvents)); + if (!logEvents.isEmpty()) { + client.captureBatchedLogEvents(new SentryLogEvents(logEvents)); + } } private class BatchRunnable implements Runnable { From 44512db89429ebc28e111f841cecbb9bf41f0ea9 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 18 Jun 2025 15:44:50 +0200 Subject: [PATCH 04/12] changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe4c599b6f..f8bd65da81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- No longer send out empty log envelopes ([#4497](https://github.com/getsentry/sentry-java/pull/4497)) + ## 8.14.0 ### Fixes From fb24e9c93efc8dbd2bae4ebf562ef2dde144eb4e Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 20 Jun 2025 07:20:39 +0200 Subject: [PATCH 05/12] Min event level config option; enable logs in logback sample --- sentry-logback/api/sentry-logback.api | 2 ++ .../main/java/io/sentry/logback/SentryAppender.java | 10 ++++++++++ .../src/main/resources/logback.xml | 5 +++++ 3 files changed, 17 insertions(+) diff --git a/sentry-logback/api/sentry-logback.api b/sentry-logback/api/sentry-logback.api index 642d104bb6..84a3c90d5c 100644 --- a/sentry-logback/api/sentry-logback.api +++ b/sentry-logback/api/sentry-logback.api @@ -13,9 +13,11 @@ public class io/sentry/logback/SentryAppender : ch/qos/logback/core/Unsynchroniz protected fun createEvent (Lch/qos/logback/classic/spi/ILoggingEvent;)Lio/sentry/SentryEvent; public fun getMinimumBreadcrumbLevel ()Lch/qos/logback/classic/Level; public fun getMinimumEventLevel ()Lch/qos/logback/classic/Level; + public fun getMinimumLevel ()Lch/qos/logback/classic/Level; public fun setEncoder (Lch/qos/logback/core/encoder/Encoder;)V public fun setMinimumBreadcrumbLevel (Lch/qos/logback/classic/Level;)V public fun setMinimumEventLevel (Lch/qos/logback/classic/Level;)V + public fun setMinimumLevel (Lch/qos/logback/classic/Level;)V public fun setOptions (Lio/sentry/SentryOptions;)V public fun start ()V } diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index 6810833311..7312dcc385 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -306,6 +306,16 @@ public void setMinimumEventLevel(final @Nullable Level minimumEventLevel) { return minimumEventLevel; } + public void setMinimumLevel(final @Nullable Level minimumLevel) { + if (minimumLevel != null) { + this.minimumLevel = minimumLevel; + } + } + + public @NotNull Level getMinimumLevel() { + return minimumLevel; + } + @ApiStatus.Internal void setTransportFactory(final @Nullable ITransportFactory transportFactory) { this.transportFactory = transportFactory; diff --git a/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml b/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml index 02bee862a3..8082af4483 100644 --- a/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml +++ b/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml @@ -13,12 +13,17 @@ https://502f25099c204a2fbf4cb16edc5975d1@o447951.ingest.sentry.io/5428563 userId requestId + + true + WARN DEBUG + + INFO From 2f052763848df3479dbaaf2d1af9a9441be76085 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 20 Jun 2025 08:33:52 +0200 Subject: [PATCH 06/12] Flush logs when client flushes --- sentry/api/sentry.api | 3 +++ sentry/src/main/java/io/sentry/SentryClient.java | 1 + .../main/java/io/sentry/logger/ILoggerBatchProcessor.java | 7 +++++++ .../main/java/io/sentry/logger/LoggerBatchProcessor.java | 5 +++++ .../java/io/sentry/logger/NoOpLoggerBatchProcessor.java | 5 +++++ 5 files changed, 21 insertions(+) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index c68bcbab7f..a96bade6a3 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -4817,6 +4817,7 @@ public abstract interface class io/sentry/logger/ILoggerApi { public abstract interface class io/sentry/logger/ILoggerBatchProcessor { public abstract fun add (Lio/sentry/SentryLogEvent;)V public abstract fun close (Z)V + public abstract fun flush (J)V } public final class io/sentry/logger/LoggerApi : io/sentry/logger/ILoggerApi { @@ -4838,6 +4839,7 @@ public final class io/sentry/logger/LoggerBatchProcessor : io/sentry/logger/ILog public fun (Lio/sentry/SentryOptions;Lio/sentry/ISentryClient;)V public fun add (Lio/sentry/SentryLogEvent;)V public fun close (Z)V + public fun flush (J)V } public final class io/sentry/logger/NoOpLoggerApi : io/sentry/logger/ILoggerApi { @@ -4856,6 +4858,7 @@ public final class io/sentry/logger/NoOpLoggerApi : io/sentry/logger/ILoggerApi public final class io/sentry/logger/NoOpLoggerBatchProcessor : io/sentry/logger/ILoggerBatchProcessor { public fun add (Lio/sentry/SentryLogEvent;)V public fun close (Z)V + public fun flush (J)V public static fun getInstance ()Lio/sentry/logger/NoOpLoggerBatchProcessor; } diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 617083b1a3..998f1c5d8d 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -1544,6 +1544,7 @@ public void close(final boolean isRestarting) { @Override public void flush(final long timeoutMillis) { + loggerBatchProcessor.flush(timeoutMillis); transport.flush(timeoutMillis); } diff --git a/sentry/src/main/java/io/sentry/logger/ILoggerBatchProcessor.java b/sentry/src/main/java/io/sentry/logger/ILoggerBatchProcessor.java index 9dbc7c883e..8d0fa154dc 100644 --- a/sentry/src/main/java/io/sentry/logger/ILoggerBatchProcessor.java +++ b/sentry/src/main/java/io/sentry/logger/ILoggerBatchProcessor.java @@ -7,4 +7,11 @@ public interface ILoggerBatchProcessor { void add(@NotNull SentryLogEvent event); void close(boolean isRestarting); + + /** + * Flushes log events. + * + * @param timeoutMillis time in milliseconds + */ + void flush(long timeoutMillis); } diff --git a/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java b/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java index 08532b64ef..4f048de730 100644 --- a/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java +++ b/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java @@ -75,6 +75,11 @@ private void maybeSchedule(boolean forceSchedule, boolean immediately) { } } + @Override + public void flush(long timeoutMillis) { + maybeSchedule(true, true); + } + private void flush() { flushInternal(); try (final @NotNull ISentryLifecycleToken ignored = scheduleLock.acquire()) { diff --git a/sentry/src/main/java/io/sentry/logger/NoOpLoggerBatchProcessor.java b/sentry/src/main/java/io/sentry/logger/NoOpLoggerBatchProcessor.java index 9568304444..372b9f6b67 100644 --- a/sentry/src/main/java/io/sentry/logger/NoOpLoggerBatchProcessor.java +++ b/sentry/src/main/java/io/sentry/logger/NoOpLoggerBatchProcessor.java @@ -24,4 +24,9 @@ public void add(@NotNull SentryLogEvent event) { public void close(final boolean isRestarting) { // do nothing } + + @Override + public void flush(long timeoutMillis) { + // do nothing + } } From 356f34692194d2b7e98b01e40308d5ad99f501b2 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 20 Jun 2025 14:07:08 +0200 Subject: [PATCH 07/12] Improve log flushing and add test --- .../io/sentry/logback/SentryAppenderTest.kt | 76 ++++++++++++++++++- .../src/main/kotlin/io/sentry/Assertions.kt | 14 ++++ .../sentry/logger/LoggerBatchProcessor.java | 15 ++++ 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt b/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt index c4971748b1..b7e0add955 100644 --- a/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt +++ b/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt @@ -14,8 +14,10 @@ import io.sentry.ITransportFactory import io.sentry.InitPriority import io.sentry.Sentry import io.sentry.SentryLevel +import io.sentry.SentryLogLevel import io.sentry.SentryOptions import io.sentry.checkEvent +import io.sentry.checkLogs import io.sentry.test.initForTest import io.sentry.transport.ITransport import org.mockito.kotlin.any @@ -40,7 +42,7 @@ import kotlin.test.assertNull import kotlin.test.assertTrue class SentryAppenderTest { - private class Fixture(dsn: String? = "http://key@localhost/proj", minimumBreadcrumbLevel: Level? = null, minimumEventLevel: Level? = null, contextTags: List? = null, encoder: Encoder? = null, sendDefaultPii: Boolean = false, options: SentryOptions = SentryOptions(), startLater: Boolean = false) { + private class Fixture(dsn: String? = "http://key@localhost/proj", minimumBreadcrumbLevel: Level? = null, minimumEventLevel: Level? = null, minimumLevel: Level? = null, contextTags: List? = null, encoder: Encoder? = null, sendDefaultPii: Boolean = false, enableLogs: Boolean = false, options: SentryOptions = SentryOptions(), startLater: Boolean = false) { val logger: Logger = LoggerFactory.getLogger(SentryAppenderTest::class.java) val loggerContext = LoggerFactory.getILoggerFactory() as LoggerContext val transportFactory = mock() @@ -54,10 +56,12 @@ class SentryAppenderTest { this.encoder = encoder options.dsn = dsn options.isSendDefaultPii = sendDefaultPii + options.logs.isEnabled = enableLogs contextTags?.forEach { options.addContextTag(it) } appender.setOptions(options) appender.setMinimumBreadcrumbLevel(minimumBreadcrumbLevel) appender.setMinimumEventLevel(minimumEventLevel) + appender.setMinimumLevel(minimumLevel) appender.context = loggerContext appender.setTransportFactory(transportFactory) encoder?.context = loggerContext @@ -312,6 +316,76 @@ class SentryAppenderTest { ) } + @Test + fun `converts trace log level to Sentry log level`() { + fixture = Fixture(minimumLevel = Level.TRACE, enableLogs = true) + fixture.logger.trace("testing trace level") + + Sentry.flush(1000) + + verify(fixture.transport).send( + checkLogs { logs -> + assertEquals(SentryLogLevel.TRACE, logs.items.first().level) + } + ) + } + + @Test + fun `converts debug log level to Sentry log level`() { + fixture = Fixture(minimumLevel = Level.DEBUG, enableLogs = true) + fixture.logger.debug("testing debug level") + + Sentry.flush(1000) + + verify(fixture.transport).send( + checkLogs { logs -> + assertEquals(SentryLogLevel.DEBUG, logs.items.first().level) + } + ) + } + + @Test + fun `converts info log level to Sentry log level`() { + fixture = Fixture(minimumLevel = Level.INFO, enableLogs = true) + fixture.logger.info("testing info level") + + Sentry.flush(1000) + + verify(fixture.transport).send( + checkLogs { logs -> + assertEquals(SentryLogLevel.INFO, logs.items.first().level) + } + ) + } + + @Test + fun `converts warn log level to Sentry log level`() { + fixture = Fixture(minimumLevel = Level.WARN, enableLogs = true) + fixture.logger.warn("testing warn level") + + Sentry.flush(1000) + + verify(fixture.transport).send( + checkLogs { logs -> + assertEquals(SentryLogLevel.WARN, logs.items.first().level) + } + ) + } + + @Test + fun `converts error log level to Sentry log level`() { + fixture = Fixture(minimumLevel = Level.ERROR, enableLogs = true) + fixture.logger.error("testing error level") + + Sentry.flush(1000) + + verify(fixture.transport).send( + checkLogs { logs -> + assertEquals(SentryLogLevel.ERROR, logs.items.first().level) + } + ) + } + @Test fun `attaches thread information`() { fixture = Fixture(minimumEventLevel = Level.WARN) diff --git a/sentry-test-support/src/main/kotlin/io/sentry/Assertions.kt b/sentry-test-support/src/main/kotlin/io/sentry/Assertions.kt index 870af1ba5a..deecaa8c65 100644 --- a/sentry-test-support/src/main/kotlin/io/sentry/Assertions.kt +++ b/sentry-test-support/src/main/kotlin/io/sentry/Assertions.kt @@ -18,6 +18,20 @@ fun checkEvent(predicate: (SentryEvent) -> Unit): SentryEnvelope { } } +/** + * Verifies is [SentryEnvelope] contains log events matching a predicate. + */ +fun checkLogs(predicate: (SentryLogEvents) -> Unit): SentryEnvelope { + return check { + val events: SentryLogEvents? = it.items.first().getLogs(JsonSerializer(SentryOptions.empty())) + if (events != null) { + predicate(events) + } else { + throw SkipError("event is null") + } + } +} + fun checkTransaction(predicate: (SentryTransaction) -> Unit): SentryEnvelope { return check { val transaction = it.items.first().getTransaction(JsonSerializer(SentryOptions.empty())) diff --git a/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java b/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java index 4f048de730..5aa03a7411 100644 --- a/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java +++ b/sentry/src/main/java/io/sentry/logger/LoggerBatchProcessor.java @@ -4,15 +4,18 @@ import io.sentry.ISentryExecutorService; import io.sentry.ISentryLifecycleToken; import io.sentry.SentryExecutorService; +import io.sentry.SentryLevel; import io.sentry.SentryLogEvent; import io.sentry.SentryLogEvents; import io.sentry.SentryOptions; +import io.sentry.transport.ReusableCountLatch; import io.sentry.util.AutoClosableReentrantLock; import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -30,6 +33,8 @@ public final class LoggerBatchProcessor implements ILoggerBatchProcessor { new AutoClosableReentrantLock(); private volatile boolean hasScheduled = false; + private final @NotNull ReusableCountLatch pendingCount = new ReusableCountLatch(); + public LoggerBatchProcessor( final @NotNull SentryOptions options, final @NotNull ISentryClient client) { this.options = options; @@ -40,6 +45,7 @@ public LoggerBatchProcessor( @Override public void add(final @NotNull SentryLogEvent logEvent) { + pendingCount.increment(); queue.offer(logEvent); maybeSchedule(false, false); } @@ -78,6 +84,12 @@ private void maybeSchedule(boolean forceSchedule, boolean immediately) { @Override public void flush(long timeoutMillis) { maybeSchedule(true, true); + try { + pendingCount.waitTillZero(timeoutMillis, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + options.getLogger().log(SentryLevel.ERROR, "Failed to flush log events", e); + Thread.currentThread().interrupt(); + } } private void flush() { @@ -108,6 +120,9 @@ private void flushBatch() { if (!logEvents.isEmpty()) { client.captureBatchedLogEvents(new SentryLogEvents(logEvents)); + for (int i = 0; i < logEvents.size(); i++) { + pendingCount.decrement(); + } } } From 7792597409ccb60e829c0fdc86657db1f829b79a Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 20 Jun 2025 15:08:04 +0200 Subject: [PATCH 08/12] review feedback --- .../src/main/java/io/sentry/logback/SentryAppender.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index 7312dcc385..67aa415cc1 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -80,10 +80,7 @@ public void start() { @Override protected void append(@NotNull ILoggingEvent eventObject) { - if (eventObject.getLevel().isGreaterOrEqual(minimumLevel)) { - final Hint hint = new Hint(); - hint.set(SENTRY_SYNTHETIC_EXCEPTION, eventObject); - + if (options.getLogs().isEnabled() && eventObject.getLevel().isGreaterOrEqual(minimumLevel)) { captureLog(eventObject); } if (eventObject.getLevel().isGreaterOrEqual(minimumEventLevel)) { From 44813618cd2bb8491569a7d2fe81d815461498bc Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 23 Jun 2025 09:32:23 +0200 Subject: [PATCH 09/12] Add logs to Spring Boot logback config --- .../api/sentry-spring-boot-jakarta.api | 2 ++ .../boot/jakarta/SentryLogbackInitializer.java | 3 +++ .../spring/boot/jakarta/SentryProperties.java | 15 +++++++++++++-- .../SentryLogbackAppenderAutoConfigurationTest.kt | 3 ++- sentry-spring-boot/api/sentry-spring-boot.api | 2 ++ .../spring/boot/SentryLogbackInitializer.java | 3 +++ .../io/sentry/spring/boot/SentryProperties.java | 15 +++++++++++++-- .../SentryLogbackAppenderAutoConfigurationTest.kt | 3 ++- 8 files changed, 40 insertions(+), 6 deletions(-) diff --git a/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api b/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api index cec2df120b..b0ef970d7d 100644 --- a/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api +++ b/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api @@ -55,11 +55,13 @@ public class io/sentry/spring/boot/jakarta/SentryProperties$Logging { public fun getLoggers ()Ljava/util/List; public fun getMinimumBreadcrumbLevel ()Lorg/slf4j/event/Level; public fun getMinimumEventLevel ()Lorg/slf4j/event/Level; + public fun getMinimumLevel ()Lorg/slf4j/event/Level; public fun isEnabled ()Z public fun setEnabled (Z)V public fun setLoggers (Ljava/util/List;)V public fun setMinimumBreadcrumbLevel (Lorg/slf4j/event/Level;)V public fun setMinimumEventLevel (Lorg/slf4j/event/Level;)V + public fun setMinimumLevel (Lorg/slf4j/event/Level;)V } public class io/sentry/spring/boot/jakarta/SentryProperties$Reactive { diff --git a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryLogbackInitializer.java b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryLogbackInitializer.java index 9c359ba7a7..be222eae1b 100644 --- a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryLogbackInitializer.java +++ b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryLogbackInitializer.java @@ -51,6 +51,9 @@ public void onApplicationEvent(final @NotNull ApplicationEvent event) { Optional.ofNullable(sentryProperties.getLogging().getMinimumEventLevel()) .map(slf4jLevel -> Level.toLevel(slf4jLevel.name())) .ifPresent(sentryAppender::setMinimumEventLevel); + Optional.ofNullable(sentryProperties.getLogging().getMinimumLevel()) + .map(slf4jLevel -> Level.toLevel(slf4jLevel.name())) + .ifPresent(sentryAppender::setMinimumLevel); sentryAppender.start(); logger.addAppender(sentryAppender); diff --git a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryProperties.java b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryProperties.java index b0778134bd..7813c2e551 100644 --- a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryProperties.java +++ b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryProperties.java @@ -129,12 +129,15 @@ public static class Logging { /** Enable/Disable logging auto-configuration. */ private boolean enabled = true; - /** Minimum logging level for recording breadcrumb. */ + /** Minimum logging level for recording breadcrumbs. */ private @Nullable Level minimumBreadcrumbLevel; - /** Minimum logging level for recording event. */ + /** Minimum logging level for recording events. */ private @Nullable Level minimumEventLevel; + /** Minimum logging level for recording log events. */ + private @Nullable Level minimumLevel; + /** List of loggers the SentryAppender should be added to. */ private @NotNull List loggers = Arrays.asList(org.slf4j.Logger.ROOT_LOGGER_NAME); @@ -162,6 +165,14 @@ public void setMinimumEventLevel(@Nullable Level minimumEventLevel) { this.minimumEventLevel = minimumEventLevel; } + public @Nullable Level getMinimumLevel() { + return minimumLevel; + } + + public void setMinimumLevel(@Nullable Level minimumLevel) { + this.minimumLevel = minimumLevel; + } + @NotNull public List getLoggers() { return loggers; diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryLogbackAppenderAutoConfigurationTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryLogbackAppenderAutoConfigurationTest.kt index 419bd5aa59..f766604102 100644 --- a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryLogbackAppenderAutoConfigurationTest.kt +++ b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryLogbackAppenderAutoConfigurationTest.kt @@ -71,7 +71,7 @@ class SentryLogbackAppenderAutoConfigurationTest { @Test fun `sets SentryAppender properties`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.logging.minimum-event-level=info", "sentry.logging.minimum-breadcrumb-level=debug") + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.logging.minimum-event-level=info", "sentry.logging.minimum-breadcrumb-level=debug", "sentry.logging.minimum-level=error") .run { val appenders = rootLogger.getAppenders(SentryAppender::class.java) assertThat(appenders).hasSize(1) @@ -79,6 +79,7 @@ class SentryLogbackAppenderAutoConfigurationTest { assertThat(sentryAppender.minimumBreadcrumbLevel).isEqualTo(Level.DEBUG) assertThat(sentryAppender.minimumEventLevel).isEqualTo(Level.INFO) + assertThat(sentryAppender.minimumLevel).isEqualTo(Level.ERROR) } } diff --git a/sentry-spring-boot/api/sentry-spring-boot.api b/sentry-spring-boot/api/sentry-spring-boot.api index b3ce0896e3..6bebb6ed09 100644 --- a/sentry-spring-boot/api/sentry-spring-boot.api +++ b/sentry-spring-boot/api/sentry-spring-boot.api @@ -51,11 +51,13 @@ public class io/sentry/spring/boot/SentryProperties$Logging { public fun getLoggers ()Ljava/util/List; public fun getMinimumBreadcrumbLevel ()Lorg/slf4j/event/Level; public fun getMinimumEventLevel ()Lorg/slf4j/event/Level; + public fun getMinimumLevel ()Lorg/slf4j/event/Level; public fun isEnabled ()Z public fun setEnabled (Z)V public fun setLoggers (Ljava/util/List;)V public fun setMinimumBreadcrumbLevel (Lorg/slf4j/event/Level;)V public fun setMinimumEventLevel (Lorg/slf4j/event/Level;)V + public fun setMinimumLevel (Lorg/slf4j/event/Level;)V } public class io/sentry/spring/boot/SentryWebfluxAutoConfiguration { diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryLogbackInitializer.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryLogbackInitializer.java index 37f3cc6126..94ba7b743f 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryLogbackInitializer.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryLogbackInitializer.java @@ -51,6 +51,9 @@ public void onApplicationEvent(final @NotNull ApplicationEvent event) { Optional.ofNullable(sentryProperties.getLogging().getMinimumEventLevel()) .map(slf4jLevel -> Level.toLevel(slf4jLevel.name())) .ifPresent(sentryAppender::setMinimumEventLevel); + Optional.ofNullable(sentryProperties.getLogging().getMinimumLevel()) + .map(slf4jLevel -> Level.toLevel(slf4jLevel.name())) + .ifPresent(sentryAppender::setMinimumLevel); sentryAppender.start(); logger.addAppender(sentryAppender); diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryProperties.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryProperties.java index e40700b6b6..f959fc930b 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryProperties.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryProperties.java @@ -103,12 +103,15 @@ public static class Logging { /** Enable/Disable logging auto-configuration. */ private boolean enabled = true; - /** Minimum logging level for recording breadcrumb. */ + /** Minimum logging level for recording breadcrumbs. */ private @Nullable Level minimumBreadcrumbLevel; - /** Minimum logging level for recording event. */ + /** Minimum logging level for recording events. */ private @Nullable Level minimumEventLevel; + /** Minimum logging level for recording log events. */ + private @Nullable Level minimumLevel; + /** List of loggers the SentryAppender should be added to. */ private @NotNull List loggers = Arrays.asList(org.slf4j.Logger.ROOT_LOGGER_NAME); @@ -136,6 +139,14 @@ public void setMinimumEventLevel(@Nullable Level minimumEventLevel) { this.minimumEventLevel = minimumEventLevel; } + public @Nullable Level getMinimumLevel() { + return minimumLevel; + } + + public void setMinimumLevel(@Nullable Level minimumLevel) { + this.minimumLevel = minimumLevel; + } + @NotNull public List getLoggers() { return loggers; diff --git a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryLogbackAppenderAutoConfigurationTest.kt b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryLogbackAppenderAutoConfigurationTest.kt index 3437f54d76..5934b7cc2a 100644 --- a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryLogbackAppenderAutoConfigurationTest.kt +++ b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryLogbackAppenderAutoConfigurationTest.kt @@ -71,7 +71,7 @@ class SentryLogbackAppenderAutoConfigurationTest { @Test fun `sets SentryAppender properties`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.logging.minimum-event-level=info", "sentry.logging.minimum-breadcrumb-level=debug") + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.logging.minimum-event-level=info", "sentry.logging.minimum-breadcrumb-level=debug", "sentry.logging.minimum-level=error") .run { val appenders = rootLogger.getAppenders(SentryAppender::class.java) assertThat(appenders).hasSize(1) @@ -79,6 +79,7 @@ class SentryLogbackAppenderAutoConfigurationTest { assertThat(sentryAppender.minimumBreadcrumbLevel).isEqualTo(Level.DEBUG) assertThat(sentryAppender.minimumEventLevel).isEqualTo(Level.INFO) + assertThat(sentryAppender.minimumLevel).isEqualTo(Level.ERROR) } } From d9c2a15799965f2d493de0bfdeee65f27f800380 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 23 Jun 2025 12:19:29 +0200 Subject: [PATCH 10/12] treat unformatted message and params as pii if logback encoder is present --- .../io/sentry/logback/SentryAppender.java | 23 +++++++++++++------ .../main/java/io/sentry/logger/LoggerApi.java | 8 ++++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index 67aa415cc1..dfc2465571 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -16,12 +16,15 @@ import io.sentry.InitPriority; import io.sentry.ScopesAdapter; import io.sentry.Sentry; +import io.sentry.SentryAttribute; +import io.sentry.SentryAttributes; import io.sentry.SentryEvent; import io.sentry.SentryIntegrationPackageStorage; import io.sentry.SentryLevel; import io.sentry.SentryLogLevel; import io.sentry.SentryOptions; import io.sentry.exception.ExceptionMechanismException; +import io.sentry.logger.SentryLogParameters; import io.sentry.protocol.Mechanism; import io.sentry.protocol.Message; import io.sentry.protocol.SdkVersion; @@ -177,14 +180,20 @@ protected void append(@NotNull ILoggingEvent eventObject) { protected void captureLog(@NotNull ILoggingEvent loggingEvent) { final @NotNull SentryLogLevel sentryLevel = toSentryLogLevel(loggingEvent.getLevel()); - // // if encoder is set we treat message+params as PII as encoders may be used to mask/strip - // PII - // if (encoder == null || options.isSendDefaultPii()) { - // message.setMessage(loggingEvent.getMessage()); - // message.setParams(toParams(loggingEvent.getArgumentArray())); - // } + @Nullable Object[] arguments = null; + final @NotNull SentryAttributes attributes = SentryAttributes.of(); - Sentry.logger().log(sentryLevel, formatted(loggingEvent), loggingEvent.getArgumentArray()); + // if encoder is set we treat message+params as PII as encoders may be used to mask/strip PII + if (encoder == null || options.isSendDefaultPii()) { + attributes.add( + SentryAttribute.stringAttribute("sentry.message.template", loggingEvent.getMessage())); + arguments = loggingEvent.getArgumentArray(); + } + + final @NotNull String formattedMessage = formatted(loggingEvent); + final @NotNull SentryLogParameters params = SentryLogParameters.create(attributes); + + Sentry.logger().log(sentryLevel, params, formattedMessage, arguments); } private String formatted(@NotNull ILoggingEvent loggingEvent) { diff --git a/sentry/src/main/java/io/sentry/logger/LoggerApi.java b/sentry/src/main/java/io/sentry/logger/LoggerApi.java index 1e8fef75a8..976b55de6e 100644 --- a/sentry/src/main/java/io/sentry/logger/LoggerApi.java +++ b/sentry/src/main/java/io/sentry/logger/LoggerApi.java @@ -184,9 +184,11 @@ private void captureLog( i++; } if (i > 0) { - attributes.put( - "sentry.message.template", - new SentryLogEventAttributeValue(SentryAttributeType.STRING, message)); + if (attributes.get("sentry.message.template") == null) { + attributes.put( + "sentry.message.template", + new SentryLogEventAttributeValue(SentryAttributeType.STRING, message)); + } } } From 89b6643b3e54eb6f702a6cc222be115c540b69e3 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 23 Jun 2025 12:55:34 +0200 Subject: [PATCH 11/12] add tests for pii handling --- .../io/sentry/logback/SentryAppenderTest.kt | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt b/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt index b7e0add955..617ca56616 100644 --- a/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt +++ b/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt @@ -386,6 +386,83 @@ class SentryAppenderTest { ) } + @Test + fun `sends formatted log message if no encoder`() { + fixture = Fixture(minimumLevel = Level.TRACE, enableLogs = true) + fixture.logger.trace("Testing {} level", "TRACE") + + Sentry.flush(1000) + + verify(fixture.transport).send( + checkLogs { logs -> + val log = logs.items.first() + assertEquals("Testing TRACE level", log.body) + val attributes = log.attributes!! + assertEquals("Testing {} level", attributes["sentry.message.template"]?.value) + assertEquals("TRACE", attributes["sentry.message.parameter.0"]?.value) + } + ) + } + + @Test + fun `does not send formatted log message if encoder is available but sendDefaultPii is off`() { + var encoder = PatternLayoutEncoder() + encoder.pattern = "encoderadded %msg" + fixture = Fixture(minimumLevel = Level.TRACE, enableLogs = true, encoder = encoder) + fixture.logger.trace("Testing {} level", "TRACE") + + Sentry.flush(1000) + + verify(fixture.transport).send( + checkLogs { logs -> + val log = logs.items.first() + assertEquals("encoderadded Testing TRACE level", log.body) + val attributes = log.attributes!! + assertNull(attributes["sentry.message.template"]) + assertNull(attributes["sentry.message.parameter.0"]) + } + ) + } + + @Test + fun `sends formatted log message if encoder is available and sendDefaultPii is on but encoder throws`() { + var encoder = ThrowingEncoder() + fixture = Fixture(minimumLevel = Level.TRACE, enableLogs = true, sendDefaultPii = true, encoder = encoder) + fixture.logger.trace("Testing {} level", "TRACE") + + Sentry.flush(1000) + + verify(fixture.transport).send( + checkLogs { logs -> + val log = logs.items.first() + assertEquals("Testing TRACE level", log.body) + val attributes = log.attributes!! + assertEquals("Testing {} level", attributes["sentry.message.template"]?.value) + assertEquals("TRACE", attributes["sentry.message.parameter.0"]?.value) + } + ) + } + + @Test + fun `sends formatted log message if encoder is available and sendDefaultPii is on`() { + var encoder = PatternLayoutEncoder() + encoder.pattern = "encoderadded %msg" + fixture = Fixture(minimumLevel = Level.TRACE, enableLogs = true, sendDefaultPii = true, encoder = encoder) + fixture.logger.trace("Testing {} level", "TRACE") + + Sentry.flush(1000) + + verify(fixture.transport).send( + checkLogs { logs -> + val log = logs.items.first() + assertEquals("encoderadded Testing TRACE level", log.body) + val attributes = log.attributes!! + assertEquals("Testing {} level", attributes["sentry.message.template"]?.value) + assertEquals("TRACE", attributes["sentry.message.parameter.0"]?.value) + } + ) + } + @Test fun `attaches thread information`() { fixture = Fixture(minimumEventLevel = Level.WARN) From a69d5ada9b506f4f2f203156ba7282285615fb78 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 25 Jun 2025 08:54:45 +0200 Subject: [PATCH 12/12] adapt pervious changelog entry --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88f39b4f0a..7f8e01ce61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,11 @@ INFO ``` + - For Spring Boot you may also enable it in `application.properties` / `application.yml`: + ```properties + sentry.logs.enabled=true + sentry.logging.minimum-level=error + ``` - If you manually initialize Sentry, you may also enable logs on `Sentry.init`: ```java Sentry.init(options -> {