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 5063a60bc..fb2236b8e 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -4,6 +4,7 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.ThrowableProxy; import ch.qos.logback.core.UnsynchronizedAppenderBase; +import io.sentry.core.Breadcrumb; import io.sentry.core.DateUtils; import io.sentry.core.Sentry; import io.sentry.core.SentryEvent; @@ -29,6 +30,8 @@ public final class SentryAppender extends UnsynchronizedAppenderBase { private @Nullable SentryOptions options; private @Nullable ITransport transport; + private @NotNull Level minimumBreadcrumbLevel = Level.INFO; + private @NotNull Level minimumEventLevel = Level.ERROR; @Override public void start() { @@ -43,7 +46,12 @@ public void start() { @Override protected void append(@NotNull ILoggingEvent eventObject) { - Sentry.captureEvent(createEvent(eventObject)); + if (eventObject.getLevel().isGreaterOrEqual(minimumEventLevel)) { + Sentry.captureEvent(createEvent(eventObject)); + } + if (eventObject.getLevel().isGreaterOrEqual(minimumBreadcrumbLevel)) { + Sentry.addBreadcrumb(createBreadcrumb(eventObject)); + } } /** @@ -93,6 +101,20 @@ protected void append(@NotNull ILoggingEvent eventObject) { } } + /** + * Creates {@link Breadcrumb} from Logback's {@link ILoggingEvent}. + * + * @param loggingEvent the logback event + * @return the sentry breadcrumb + */ + private @NotNull Breadcrumb createBreadcrumb(final @NotNull ILoggingEvent loggingEvent) { + final Breadcrumb breadcrumb = new Breadcrumb(); + breadcrumb.setLevel(formatLevel(loggingEvent.getLevel())); + breadcrumb.setCategory(loggingEvent.getLoggerName()); + breadcrumb.setMessage(loggingEvent.getFormattedMessage()); + return breadcrumb; + } + /** * Transforms a {@link Level} into an {@link SentryLevel}. * @@ -126,10 +148,22 @@ protected void append(@NotNull ILoggingEvent eventObject) { return sdkVersion; } - public void setOptions(SentryOptions options) { + public void setOptions(final @Nullable SentryOptions options) { this.options = options; } + public void setMinimumBreadcrumbLevel(final @Nullable Level minimumBreadcrumbLevel) { + if (minimumBreadcrumbLevel != null) { + this.minimumBreadcrumbLevel = minimumBreadcrumbLevel; + } + } + + public void setMinimumEventLevel(final @Nullable Level minimumEventLevel) { + if (minimumEventLevel != null) { + this.minimumEventLevel = minimumEventLevel; + } + } + @ApiStatus.Internal void setTransport(@Nullable ITransport transport) { this.transport = transport; 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 262e4b3d9..0eb02a366 100644 --- a/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt +++ b/sentry-logback/src/test/kotlin/io/sentry/logback/SentryAppenderTest.kt @@ -28,31 +28,30 @@ import org.slf4j.LoggerFactory import org.slf4j.MDC class SentryAppenderTest { - private class Fixture { + private class Fixture(minimumBreadcrumbLevel: Level? = null, minimumEventLevel: Level? = null) { val transport = mock() val logger: Logger = LoggerFactory.getLogger(SentryAppenderTest::class.java) val loggerContext = LoggerFactory.getILoggerFactory() as LoggerContext init { whenever(transport.send(any())).thenReturn(TransportResult.success()) - val appender = SentryAppender() val options = SentryOptions() options.dsn = "http://key@localhost/proj" appender.setOptions(options) + appender.setMinimumBreadcrumbLevel(minimumBreadcrumbLevel) + appender.setMinimumEventLevel(minimumEventLevel) appender.context = loggerContext appender.setTransport(transport) - val rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME) rootLogger.level = Level.TRACE rootLogger.addAppender(appender) - appender.start() loggerContext.start() } } - private val fixture = Fixture() + private lateinit var fixture: Fixture @AfterTest fun `stop logback`() { @@ -66,6 +65,7 @@ class SentryAppenderTest { @Test fun `converts message`() { + fixture = Fixture(minimumEventLevel = Level.DEBUG) fixture.logger.debug("testing message conversion {}, {}", 1, 2) await.untilAsserted { @@ -80,6 +80,7 @@ class SentryAppenderTest { @Test fun `event date is in UTC`() { + fixture = Fixture(minimumEventLevel = Level.DEBUG) val utcTime = LocalDateTime.now(ZoneId.of("UTC")) fixture.logger.debug("testing event date") @@ -98,6 +99,7 @@ class SentryAppenderTest { @Test fun `converts trace log level to Sentry level`() { + fixture = Fixture(minimumEventLevel = Level.TRACE) fixture.logger.trace("testing trace level") await.untilAsserted { @@ -109,6 +111,7 @@ class SentryAppenderTest { @Test fun `converts debug log level to Sentry level`() { + fixture = Fixture(minimumEventLevel = Level.DEBUG) fixture.logger.debug("testing debug level") await.untilAsserted { @@ -120,6 +123,7 @@ class SentryAppenderTest { @Test fun `converts info log level to Sentry level`() { + fixture = Fixture(minimumEventLevel = Level.INFO) fixture.logger.info("testing info level") await.untilAsserted { @@ -131,6 +135,7 @@ class SentryAppenderTest { @Test fun `converts warn log level to Sentry level`() { + fixture = Fixture(minimumEventLevel = Level.WARN) fixture.logger.warn("testing warn level") await.untilAsserted { @@ -142,6 +147,7 @@ class SentryAppenderTest { @Test fun `converts error log level to Sentry level`() { + fixture = Fixture(minimumEventLevel = Level.ERROR) fixture.logger.error("testing error level") await.untilAsserted { @@ -153,6 +159,7 @@ class SentryAppenderTest { @Test fun `attaches thread information`() { + fixture = Fixture(minimumEventLevel = Level.WARN) fixture.logger.warn("testing thread information") await.untilAsserted { @@ -164,6 +171,7 @@ class SentryAppenderTest { @Test fun `sets tags from MDC`() { + fixture = Fixture(minimumEventLevel = Level.WARN) MDC.put("key", "value") fixture.logger.warn("testing MDC tags") @@ -176,6 +184,7 @@ class SentryAppenderTest { @Test fun `does not create MDC context when no MDC tags are set`() { + fixture = Fixture(minimumEventLevel = Level.WARN) fixture.logger.warn("testing without MDC tags") await.untilAsserted { @@ -187,6 +196,7 @@ class SentryAppenderTest { @Test fun `attaches throwable`() { + fixture = Fixture(minimumEventLevel = Level.WARN) val throwable = RuntimeException("something went wrong") fixture.logger.warn("testing throwable", throwable) @@ -199,6 +209,7 @@ class SentryAppenderTest { @Test fun `sets SDK version`() { + fixture = Fixture(minimumEventLevel = Level.INFO) fixture.logger.info("testing sdk version") await.untilAsserted { @@ -213,4 +224,63 @@ class SentryAppenderTest { }) } } + + @Test + fun `attaches breadcrumbs with level higher than minimumBreadcrumbLevel`() { + fixture = Fixture(minimumBreadcrumbLevel = Level.DEBUG, minimumEventLevel = Level.WARN) + val utcTime = LocalDateTime.now(ZoneId.of("UTC")) + + fixture.logger.debug("this should be a breadcrumb #1") + fixture.logger.info("this should be a breadcrumb #2") + fixture.logger.warn("testing message with breadcrumbs") + + await.untilAsserted { + verify(fixture.transport).send(check { it: SentryEvent -> + assertEquals(2, it.breadcrumbs.size) + val breadcrumb = it.breadcrumbs[0] + val breadcrumbTime = Instant.ofEpochMilli(it.timestamp.time) + .atZone(ZoneId.systemDefault()) + .toLocalDateTime() + assertTrue { breadcrumbTime.plusSeconds(1).isAfter(utcTime) } + assertTrue { breadcrumbTime.minusSeconds(1).isBefore(utcTime) } + assertEquals("this should be a breadcrumb #1", breadcrumb.message) + assertEquals("io.sentry.logback.SentryAppenderTest", breadcrumb.category) + assertEquals(SentryLevel.DEBUG, breadcrumb.level) + }) + } + } + + @Test + fun `does not attach breadcrumbs with level lower than minimumBreadcrumbLevel`() { + fixture = Fixture(minimumBreadcrumbLevel = Level.INFO, minimumEventLevel = Level.WARN) + + fixture.logger.debug("this should NOT be a breadcrumb") + fixture.logger.info("this should be a breadcrumb") + fixture.logger.warn("testing message with breadcrumbs") + + await.untilAsserted { + verify(fixture.transport).send(check { it: SentryEvent -> + assertEquals(1, it.breadcrumbs.size) + assertEquals("this should be a breadcrumb", it.breadcrumbs[0].message) + }) + } + } + + @Test + fun `attaches breadcrumbs for default appender configuration`() { + fixture = Fixture() + + fixture.logger.debug("this should not be a breadcrumb as the level is lower than the minimum INFO") + fixture.logger.info("this should be a breadcrumb") + fixture.logger.warn("this should not be sent as the event but be a breadcrumb") + fixture.logger.error("this should be sent as the event") + + await.untilAsserted { + verify(fixture.transport).send(check { it: SentryEvent -> + assertEquals(2, it.breadcrumbs.size) + assertEquals("this should be a breadcrumb", it.breadcrumbs[0].message) + assertEquals("this should not be sent as the event but be a breadcrumb", it.breadcrumbs[1].message) + }) + } + } } 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 e0004e806..76de7f2f3 100644 --- a/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml +++ b/sentry-samples/sentry-samples-logback/src/main/resources/logback.xml @@ -11,13 +11,14 @@ https://f7f320d5c3a54709be7b28e0f2ca7081@sentry.io/1808954 - - - - - + + + WARN + + DEBUG +