Skip to content
This repository was archived by the owner on Aug 30, 2023. It is now read-only.
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
38 changes: 36 additions & 2 deletions sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,6 +30,8 @@
public final class SentryAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
private @Nullable SentryOptions options;
private @Nullable ITransport transport;
private @NotNull Level minimumBreadcrumbLevel = Level.INFO;
private @NotNull Level minimumEventLevel = Level.ERROR;

@Override
public void start() {
Expand All @@ -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));
}
}

/**
Expand Down Expand Up @@ -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}.
*
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ITransport>()
val logger: Logger = LoggerFactory.getLogger(SentryAppenderTest::class.java)
val loggerContext = LoggerFactory.getILoggerFactory() as LoggerContext

init {
whenever(transport.send(any<SentryEvent>())).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`() {
Expand All @@ -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 {
Expand All @@ -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")
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -153,6 +159,7 @@ class SentryAppenderTest {

@Test
fun `attaches thread information`() {
fixture = Fixture(minimumEventLevel = Level.WARN)
fixture.logger.warn("testing thread information")

await.untilAsserted {
Expand All @@ -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")

Expand All @@ -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 {
Expand All @@ -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)

Expand All @@ -199,6 +209,7 @@ class SentryAppenderTest {

@Test
fun `sets SDK version`() {
fixture = Fixture(minimumEventLevel = Level.INFO)
fixture.logger.info("testing sdk version")

await.untilAsserted {
Expand All @@ -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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One test case could make sure this doesn't end up in a crumb because min level is Info.

Actually a test case for default would be nice, and default would be Info or higher for Breadcrumb and Error or higher for event.

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)
})
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
<!-- NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in your Sentry project/dashboard -->
<dsn>https://[email protected]/1808954</dsn>
</options>

<!-- Optionally you can filter what statements are sent to Sentry independently from loggers configuration -->
<!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter">-->
<!-- <level>WARN</level>-->
<!-- </filter>-->
<!-- Demonstrates how to modify the minimum values -->
<!-- Default for Events is ERROR -->
<minimumEventLevel>WARN</minimumEventLevel>
<!-- Default for Breadcrumbs is INFO -->
<minimumBreadcrumbLevel>DEBUG</minimumBreadcrumbLevel>
</appender>

<!-- it's important to set logger level to equal or lower than minimumBreadcrumbLevel and minimumEventLevel -->
<root level="debug">
<appender-ref ref="sentry"/>
</root>
Expand Down