From 23ec149a90700b96525f0aef9d74abe7fca78b1f Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 1 Sep 2020 09:19:19 +0200 Subject: [PATCH 1/5] Add Log4j2 integration. --- buildSrc/src/main/java/Config.kt | 5 + sentry-log4j2/build.gradle.kts | 103 +++++++ .../java/io/sentry/log4j2/SentryAppender.java | 220 ++++++++++++++ .../io/sentry/log4j2/SentryAppenderTest.kt | 287 ++++++++++++++++++ .../org.mockito.plugins.MockMaker | 1 + .../sentry-samples-log4j2/build.gradle.kts | 14 + .../java/io/sentry/samples/log4j2/Main.java | 26 ++ .../src/main/resources/log4j2.xml | 20 ++ .../samples/{logback => log4j2}/Main.java | 2 +- settings.gradle.kts | 2 + 10 files changed, 679 insertions(+), 1 deletion(-) create mode 100644 sentry-log4j2/build.gradle.kts create mode 100644 sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java create mode 100644 sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt create mode 100644 sentry-log4j2/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker create mode 100644 sentry-samples/sentry-samples-log4j2/build.gradle.kts create mode 100644 sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java create mode 100644 sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml rename sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/{logback => log4j2}/Main.java (95%) diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index 5eeb45c99..9246dbb3d 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -41,6 +41,10 @@ object Config { val logbackVersion = "1.2.3" val logbackClassic = "ch.qos.logback:logback-classic:$logbackVersion" + val log4j2Version = "2.13.3" + val log4j2Api = "org.apache.logging.log4j:log4j-api:$log4j2Version" + val log4j2Core = "org.apache.logging.log4j:log4j-core:$log4j2Version" + val springBootStarter = "org.springframework.boot:spring-boot-starter:$springBootVersion" val springWeb = "org.springframework:spring-webmvc" @@ -84,6 +88,7 @@ object Config { val SENTRY_JAVA_SDK_NAME = "sentry.java" val SENTRY_ANDROID_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.android" val SENTRY_LOGBACK_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.logback" + val SENTRY_LOG4J2_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.log4j2" val SENTRY_SPRING_BOOT_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot" val group = "io.sentry" val description = "SDK for sentry.io" diff --git a/sentry-log4j2/build.gradle.kts b/sentry-log4j2/build.gradle.kts new file mode 100644 index 000000000..f2c4408c7 --- /dev/null +++ b/sentry-log4j2/build.gradle.kts @@ -0,0 +1,103 @@ +import com.novoda.gradle.release.PublishExtension + +plugins { + `java-library` + kotlin("jvm") + jacoco + id(Config.QualityPlugins.errorProne) + id(Config.Deploy.novodaBintray) + id(Config.QualityPlugins.gradleVersions) + id(Config.BuildPlugins.buildConfig) version Config.BuildPlugins.buildConfigVersion +} + +configure { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +tasks.withType().configureEach { + kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString() +} + +dependencies { + api(project(":sentry-core")) + implementation(Config.Libs.log4j2Api) + implementation(Config.Libs.log4j2Core) + + compileOnly(Config.CompileOnly.nopen) + errorprone(Config.CompileOnly.nopenChecker) + errorprone(Config.CompileOnly.errorprone) + errorproneJavac(Config.CompileOnly.errorProneJavac8) + compileOnly(Config.CompileOnly.jetbrainsAnnotations) + + // tests + testImplementation(kotlin(Config.kotlinStdLib)) + testImplementation(Config.TestLibs.kotlinTestJunit) + testImplementation(Config.TestLibs.mockitoKotlin) + testImplementation(Config.TestLibs.awaitility) +} + +configure { + test { + java.srcDir("src/test/java") + } +} + +jacoco { + toolVersion = Config.QualityPlugins.jacocoVersion +} + +tasks.jacocoTestReport { + reports { + xml.isEnabled = true + html.isEnabled = false + } +} + +tasks { + jacocoTestCoverageVerification { + violationRules { + rule { limit { minimum = BigDecimal.valueOf(0.6) } } + } + } + check { + dependsOn(jacocoTestCoverageVerification) + dependsOn(jacocoTestReport) + } +} + +buildConfig { + useJavaOutput() + packageName("io.sentry.log4j2") + buildConfigField("String", "SENTRY_LOG4J2_SDK_NAME", "\"${Config.Sentry.SENTRY_LOG4J2_SDK_NAME}\"") + buildConfigField("String", "VERSION_NAME", "\"${project.version}\"") +} + +val generateBuildConfig by tasks +tasks.withType().configureEach { + dependsOn(generateBuildConfig) +} + +//TODO: move these blocks to parent gradle file, DRY +configure { + userOrg = Config.Sentry.userOrg + groupId = project.group.toString() + publishVersion = project.version.toString() + desc = Config.Sentry.description + website = Config.Sentry.website + repoName = Config.Sentry.repoName + setLicences(Config.Sentry.licence) + setLicenceUrls(Config.Sentry.licenceUrl) + issueTracker = Config.Sentry.issueTracker + repository = Config.Sentry.repository + sign = Config.Deploy.sign + artifactId = project.name + uploadName = "${project.group}:${project.name}" + devId = Config.Sentry.userOrg + devName = Config.Sentry.devName + devEmail = Config.Sentry.devEmail + scmConnection = Config.Sentry.scmConnection + scmDevConnection = Config.Sentry.scmDevConnection + scmUrl = Config.Sentry.scmUrl +} + diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java new file mode 100644 index 000000000..513113e47 --- /dev/null +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -0,0 +1,220 @@ +package io.sentry.log4j2; + +import io.sentry.core.Breadcrumb; +import io.sentry.core.DateUtils; +import io.sentry.core.Sentry; +import io.sentry.core.SentryEvent; +import io.sentry.core.SentryLevel; +import io.sentry.core.SentryOptions; +import io.sentry.core.protocol.Message; +import io.sentry.core.protocol.SdkVersion; +import io.sentry.core.transport.ITransport; +import io.sentry.core.util.CollectionUtils; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.impl.ThrowableProxy; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** Appender for Log4j2 in charge of sending the logged events to a Sentry server. */ +@Plugin(name = "Sentry", category = "Core", elementType = "appender", printObject = true) +public final class SentryAppender extends AbstractAppender { + private @Nullable String dsn; + private @Nullable ITransport transport; + private @NotNull Level minimumBreadcrumbLevel = Level.INFO; + private @NotNull Level minimumEventLevel = Level.ERROR; + + public SentryAppender( + String name, + Filter filter, + @Nullable String dsn, + @Nullable Level minimumBreadcrumbLevel, + @Nullable Level minimumEventLevel, + final @Nullable ITransport transport) { + super(name, filter, null, true, null); + this.dsn = dsn; + if (minimumBreadcrumbLevel != null) { + this.minimumBreadcrumbLevel = minimumBreadcrumbLevel; + } + if (minimumEventLevel != null) { + this.minimumEventLevel = minimumEventLevel; + } + this.transport = transport; + } + + /** + * Create a Sentry Appender. + * + * @param name The name of the Appender. + * @param filter The filter, if any, to use. + * @return The SentryAppender. + */ + @PluginFactory + public static SentryAppender createAppender( + @PluginAttribute("name") final String name, + @PluginAttribute("minimumBreadcrumbLevel") final Level minimumBreadcrumbLevel, + @PluginAttribute("minimumEventLevel") final Level minimumEventLevel, + @PluginAttribute("dsn") final String dsn, + @PluginElement("filter") final Filter filter) { + + if (name == null) { + LOGGER.error("No name provided for SentryAppender"); + return null; + } + return new SentryAppender(name, filter, dsn, minimumBreadcrumbLevel, minimumEventLevel, null); + } + + @Override + public void start() { + if (dsn != null) { + Sentry.init( + options -> { + options.setDsn(dsn); + options.setSentryClientName(BuildConfig.SENTRY_LOG4J2_SDK_NAME); + options.setSdkVersion(createSdkVersion(options)); + Optional.ofNullable(transport).ifPresent(options::setTransport); + }); + } + super.start(); + } + + @Override + public void append(LogEvent eventObject) { + if (eventObject.getLevel().isMoreSpecificThan(minimumEventLevel)) { + Sentry.captureEvent(createEvent(eventObject)); + } + if (eventObject.getLevel().isMoreSpecificThan(minimumBreadcrumbLevel)) { + Sentry.addBreadcrumb(createBreadcrumb(eventObject)); + } + } + + /** + * Creates {@link SentryEvent} from Log4j2 {@link LogEvent}. + * + * @param loggingEvent the log4j2 event + * @return the sentry event + */ + @SuppressWarnings("JdkObsolete") + final @NotNull SentryEvent createEvent(@NotNull LogEvent loggingEvent) { + final SentryEvent event = + new SentryEvent(DateUtils.getDateTime(new Date(loggingEvent.getTimeMillis()))); + final Message message = new Message(); + message.setMessage(loggingEvent.getMessage().getFormat()); + message.setFormatted(loggingEvent.getMessage().getFormattedMessage()); + message.setParams(toParams(loggingEvent.getMessage().getParameters())); + event.setMessage(message); + event.setLogger(loggingEvent.getLoggerName()); + event.setLevel(formatLevel(loggingEvent.getLevel())); + + final ThrowableProxy throwableInformation = loggingEvent.getThrownProxy(); + if (throwableInformation != null) { + event.setThrowable(throwableInformation.getThrowable()); + } + + if (loggingEvent.getThreadName() != null) { + event.setExtra("thread_name", loggingEvent.getThreadName()); + } + + final Map mdcProperties = + CollectionUtils.shallowCopy(loggingEvent.getContextData().toMap()); + if (!mdcProperties.isEmpty()) { + event.getContexts().put("MDC", mdcProperties); + } + + return event; + } + + private @NotNull List toParams(@Nullable Object[] arguments) { + if (arguments != null) { + return Arrays.stream(arguments) + .filter(Objects::nonNull) + .map(Object::toString) + .collect(Collectors.toList()); + } else { + return Collections.emptyList(); + } + } + + /** + * Creates {@link Breadcrumb} from log4j2 {@link LogEvent}. + * + * @param loggingEvent the log4j2 event + * @return the sentry breadcrumb + */ + private @NotNull Breadcrumb createBreadcrumb(final @NotNull LogEvent loggingEvent) { + final Breadcrumb breadcrumb = new Breadcrumb(); + breadcrumb.setLevel(formatLevel(loggingEvent.getLevel())); + breadcrumb.setCategory(loggingEvent.getLoggerName()); + breadcrumb.setMessage(loggingEvent.getMessage().getFormattedMessage()); + return breadcrumb; + } + + /** + * Transforms a {@link Level} into an {@link SentryLevel}. + * + * @param level original level as defined in log4j. + * @return log level used within sentry. + */ + private static @NotNull SentryLevel formatLevel(@NotNull Level level) { + if (level.isMoreSpecificThan(Level.ERROR)) { + return SentryLevel.ERROR; + } else if (level.isMoreSpecificThan(Level.WARN)) { + return SentryLevel.WARNING; + } else if (level.isMoreSpecificThan(Level.INFO)) { + return SentryLevel.INFO; + } else { + return SentryLevel.DEBUG; + } + } + + private @NotNull SdkVersion createSdkVersion(@NotNull SentryOptions sentryOptions) { + SdkVersion sdkVersion = sentryOptions.getSdkVersion(); + + if (sdkVersion == null) { + sdkVersion = new SdkVersion(); + } + + sdkVersion.setName(BuildConfig.SENTRY_LOG4J2_SDK_NAME); + final String version = BuildConfig.VERSION_NAME; + sdkVersion.setVersion(version); + sdkVersion.addPackage("maven:sentry-log4j2", version); + + return sdkVersion; + } + + public void setDsn(final @Nullable String dsn) { + this.dsn = dsn; + } + + 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-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt new file mode 100644 index 000000000..ff4a26a3b --- /dev/null +++ b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt @@ -0,0 +1,287 @@ +package io.sentry.log4j2 + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import io.sentry.core.SentryEvent +import io.sentry.core.SentryLevel +import io.sentry.core.transport.ITransport +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger +import org.apache.logging.log4j.ThreadContext +import org.apache.logging.log4j.core.LoggerContext +import org.apache.logging.log4j.core.config.AppenderRef +import org.apache.logging.log4j.core.config.Configuration +import org.apache.logging.log4j.core.config.LoggerConfig +import org.awaitility.kotlin.await + +class SentryAppenderTest { + private class Fixture(minimumBreadcrumbLevel: Level? = null, minimumEventLevel: Level? = null) { + val transport = mock() + val logger: Logger + val loggerContext = LogManager.getContext() as LoggerContext + + init { + loggerContext.start() + val config: Configuration = loggerContext.configuration + val appender = SentryAppender("sentry", null, "http://key@localhost/proj", minimumBreadcrumbLevel, minimumEventLevel, transport) + config.addAppender(appender) + + val ref = AppenderRef.createAppenderRef("sentry", null, null) + + val loggerConfig = LoggerConfig.createLogger(false, Level.TRACE, "sentry_logger", "true", arrayOf(ref), null, config, null) + loggerConfig.addAppender(appender, null, null) + config.addLogger(SentryAppenderTest::class.java.name, loggerConfig) + + loggerContext.updateLoggers(config) + + appender.start() + loggerContext.start() + + logger = LogManager.getContext().getLogger(SentryAppenderTest::class.java.name) + } + } + + private lateinit var fixture: Fixture + + @AfterTest + fun `stop log4j2`() { + fixture.loggerContext.stop() + } + + @BeforeTest + fun `clear MDC`() { + ThreadContext.clearAll() + } + + @Test + fun `converts message`() { + fixture = Fixture(minimumEventLevel = Level.DEBUG) + fixture.logger.debug("testing message conversion {}, {}", 1, 2) + + await.untilAsserted { + verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + assertEquals("testing message conversion 1, 2", it.message.formatted) + assertEquals("testing message conversion {}, {}", it.message.message) + assertEquals(listOf("1", "2"), it.message.params) + assertEquals("io.sentry.log4j2.SentryAppenderTest", it.logger) + }) + } + } + + @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") + + await.untilAsserted { + verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + val eventTime = Instant.ofEpochMilli(it.timestamp.time) + .atZone(ZoneId.systemDefault()) + .toLocalDateTime() + + assertTrue { eventTime.plusSeconds(1).isAfter(utcTime) } + assertTrue { eventTime.minusSeconds(1).isBefore(utcTime) } + }) + } + } + + @Test + fun `converts trace log level to Sentry level`() { + fixture = Fixture(minimumEventLevel = Level.TRACE) + fixture.logger.trace("testing trace level") + + await.untilAsserted { + verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + assertEquals(SentryLevel.DEBUG, it.level) + }) + } + } + + @Test + fun `converts debug log level to Sentry level`() { + fixture = Fixture(minimumEventLevel = Level.DEBUG) + fixture.logger.debug("testing debug level") + + await.untilAsserted { + verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + assertEquals(SentryLevel.DEBUG, it.level) + }) + } + } + + @Test + fun `converts info log level to Sentry level`() { + fixture = Fixture(minimumEventLevel = Level.INFO) + fixture.logger.info("testing info level") + + await.untilAsserted { + verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + assertEquals(SentryLevel.INFO, it.level) + }) + } + } + + @Test + fun `converts warn log level to Sentry level`() { + fixture = Fixture(minimumEventLevel = Level.WARN) + fixture.logger.warn("testing warn level") + + await.untilAsserted { + verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + assertEquals(SentryLevel.WARNING, it.level) + }) + } + } + + @Test + fun `converts error log level to Sentry level`() { + fixture = Fixture(minimumEventLevel = Level.ERROR) + fixture.logger.error("testing error level") + + await.untilAsserted { + verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + assertEquals(SentryLevel.ERROR, it.level) + }) + } + } + + @Test + fun `attaches thread information`() { + fixture = Fixture(minimumEventLevel = Level.WARN) + fixture.logger.warn("testing thread information") + + await.untilAsserted { + verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + assertNotNull(it.getExtra("thread_name")) + }) + } + } + + @Test + fun `sets tags from ThreadContext`() { + fixture = Fixture(minimumEventLevel = Level.WARN) + ThreadContext.put("key", "value") + fixture.logger.warn("testing MDC tags") + + await.untilAsserted { + verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + assertEquals(mapOf("key" to "value"), it.contexts["MDC"]) + }) + } + } + + @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 { + verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + assertFalse(it.contexts.containsKey("MDC")) + }) + } + } + + @Test + fun `attaches throwable`() { + fixture = Fixture(minimumEventLevel = Level.WARN) + val throwable = RuntimeException("something went wrong") + fixture.logger.warn("testing throwable", throwable) + + await.untilAsserted { + verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + assertEquals(throwable, it.throwable) + }) + } + } + + @Test + fun `sets SDK version`() { + fixture = Fixture(minimumEventLevel = Level.INFO) + fixture.logger.info("testing sdk version") + + await.untilAsserted { + verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + assertEquals(BuildConfig.SENTRY_LOG4J2_SDK_NAME, it.sdk.name) + assertEquals(BuildConfig.VERSION_NAME, it.sdk.version) + assertNotNull(it.sdk.packages) + assertTrue(it.sdk.packages!!.any { pkg -> + "maven:sentry-log4j2" == pkg.name && + BuildConfig.VERSION_NAME == pkg.version + }) + }) + } + } + + @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(com.nhaarman.mockitokotlin2.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.log4j2.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(com.nhaarman.mockitokotlin2.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(com.nhaarman.mockitokotlin2.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-log4j2/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/sentry-log4j2/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 000000000..1f0955d45 --- /dev/null +++ b/sentry-log4j2/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/sentry-samples/sentry-samples-log4j2/build.gradle.kts b/sentry-samples/sentry-samples-log4j2/build.gradle.kts new file mode 100644 index 000000000..69785a28e --- /dev/null +++ b/sentry-samples/sentry-samples-log4j2/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + java + id(Config.QualityPlugins.gradleVersions) +} + +configure { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +dependencies { + implementation(project(":sentry-log4j2")) + implementation(Config.Libs.log4j2Api) +} diff --git a/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java b/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java new file mode 100644 index 000000000..a3fe8a0ab --- /dev/null +++ b/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java @@ -0,0 +1,26 @@ +package io.sentry.samples.log4j2; + +import java.util.UUID; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; + +public class Main { + private static final Logger LOGGER = LogManager.getLogger(Main.class); + + public static void main(String[] args) { + LOGGER.debug("Hello Sentry!"); + + // MDC parameters are converted to Sentry Event tags + ThreadContext.put("userId", UUID.randomUUID().toString()); + + // logging arguments are converted to Sentry Event parameters + LOGGER.info("User has made a purchase of product: {}", 445); + + try { + throw new RuntimeException("Invalid productId=445"); + } catch (Exception e) { + LOGGER.error("Something went wrong", e); + } + } +} diff --git a/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml b/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml new file mode 100644 index 000000000..a4b801b98 --- /dev/null +++ b/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + diff --git a/sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/logback/Main.java b/sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/log4j2/Main.java similarity index 95% rename from sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/logback/Main.java rename to sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/log4j2/Main.java index 3de86d711..c36345d0b 100644 --- a/sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/logback/Main.java +++ b/sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/log4j2/Main.java @@ -1,4 +1,4 @@ -package io.sentry.samples.logback; +package io.sentry.samples.log4j2; import java.util.UUID; import org.slf4j.Logger; diff --git a/settings.gradle.kts b/settings.gradle.kts index 84aa5eec2..1a05fde78 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,10 +5,12 @@ include("sentry-android", "sentry-android-ndk", "sentry-android-core", "sentry-core", + "sentry-log4j2", "sentry-logback", "sentry-spring-boot-starter", "sentry-android-timber", "sentry-samples:sentry-samples-android", "sentry-samples:sentry-samples-console", + "sentry-samples:sentry-samples-log4j2", "sentry-samples:sentry-samples-logback", "sentry-samples:sentry-samples-spring-boot") From 786aa2e79c6aafb0d85edc3ca5c01e68d98d4159 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Wed, 2 Sep 2020 01:22:12 +0200 Subject: [PATCH 2/5] Polish. --- .../src/main/java/io/sentry/samples/log4j2/Main.java | 2 +- .../main/java/io/sentry/samples/{log4j2 => logback}/Main.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/{log4j2 => logback}/Main.java (95%) diff --git a/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java b/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java index a3fe8a0ab..a2fc30805 100644 --- a/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java +++ b/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java @@ -11,7 +11,7 @@ public class Main { public static void main(String[] args) { LOGGER.debug("Hello Sentry!"); - // MDC parameters are converted to Sentry Event tags + // ThreadContext parameters are converted to Sentry Event tags ThreadContext.put("userId", UUID.randomUUID().toString()); // logging arguments are converted to Sentry Event parameters diff --git a/sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/log4j2/Main.java b/sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/logback/Main.java similarity index 95% rename from sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/log4j2/Main.java rename to sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/logback/Main.java index c36345d0b..3de86d711 100644 --- a/sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/log4j2/Main.java +++ b/sentry-samples/sentry-samples-logback/src/main/java/io/sentry/samples/logback/Main.java @@ -1,4 +1,4 @@ -package io.sentry.samples.log4j2; +package io.sentry.samples.logback; import java.util.UUID; import org.slf4j.Logger; From 89ea80d8e37f0990e1c2f16dec3bbbc4ea849c12 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Wed, 2 Sep 2020 13:23:12 +0200 Subject: [PATCH 3/5] Polish. --- .../java/io/sentry/log4j2/SentryAppender.java | 27 ++++++------ .../io/sentry/log4j2/SentryAppenderTest.kt | 43 ++++++++++++------- .../io/sentry/logback/SentryAppender.java | 1 + 3 files changed, 44 insertions(+), 27 deletions(-) diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index 513113e47..1a03028b0 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -40,11 +40,11 @@ public final class SentryAppender extends AbstractAppender { private @NotNull Level minimumEventLevel = Level.ERROR; public SentryAppender( - String name, - Filter filter, - @Nullable String dsn, - @Nullable Level minimumBreadcrumbLevel, - @Nullable Level minimumEventLevel, + final @NotNull String name, + final @Nullable Filter filter, + final @Nullable String dsn, + final @Nullable Level minimumBreadcrumbLevel, + final @Nullable Level minimumEventLevel, final @Nullable ITransport transport) { super(name, filter, null, true, null); this.dsn = dsn; @@ -94,7 +94,7 @@ public void start() { } @Override - public void append(LogEvent eventObject) { + public void append(final @NotNull LogEvent eventObject) { if (eventObject.getLevel().isMoreSpecificThan(minimumEventLevel)) { Sentry.captureEvent(createEvent(eventObject)); } @@ -109,8 +109,9 @@ public void append(LogEvent eventObject) { * @param loggingEvent the log4j2 event * @return the sentry event */ + // for the Android compatibility we must use old Java Date class @SuppressWarnings("JdkObsolete") - final @NotNull SentryEvent createEvent(@NotNull LogEvent loggingEvent) { + final @NotNull SentryEvent createEvent(final @NotNull LogEvent loggingEvent) { final SentryEvent event = new SentryEvent(DateUtils.getDateTime(new Date(loggingEvent.getTimeMillis()))); final Message message = new Message(); @@ -139,7 +140,7 @@ public void append(LogEvent eventObject) { return event; } - private @NotNull List toParams(@Nullable Object[] arguments) { + private @NotNull List toParams(final @Nullable Object[] arguments) { if (arguments != null) { return Arrays.stream(arguments) .filter(Objects::nonNull) @@ -170,8 +171,10 @@ public void append(LogEvent eventObject) { * @param level original level as defined in log4j. * @return log level used within sentry. */ - private static @NotNull SentryLevel formatLevel(@NotNull Level level) { - if (level.isMoreSpecificThan(Level.ERROR)) { + private static @NotNull SentryLevel formatLevel(final @NotNull Level level) { + if (level.isMoreSpecificThan(Level.FATAL)) { + return SentryLevel.FATAL; + } else if (level.isMoreSpecificThan(Level.ERROR)) { return SentryLevel.ERROR; } else if (level.isMoreSpecificThan(Level.WARN)) { return SentryLevel.WARNING; @@ -182,7 +185,7 @@ public void append(LogEvent eventObject) { } } - private @NotNull SdkVersion createSdkVersion(@NotNull SentryOptions sentryOptions) { + private @NotNull SdkVersion createSdkVersion(final @NotNull SentryOptions sentryOptions) { SdkVersion sdkVersion = sentryOptions.getSdkVersion(); if (sdkVersion == null) { @@ -214,7 +217,7 @@ public void setMinimumEventLevel(final @Nullable Level minimumEventLevel) { } @ApiStatus.Internal - void setTransport(@Nullable ITransport transport) { + void setTransport(final @Nullable ITransport transport) { this.transport = transport; } } diff --git a/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt index ff4a26a3b..cae0915ad 100644 --- a/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt +++ b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt @@ -1,5 +1,6 @@ package io.sentry.log4j2 +import com.nhaarman.mockitokotlin2.check import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verify import io.sentry.core.SentryEvent @@ -70,7 +71,7 @@ class SentryAppenderTest { fixture.logger.debug("testing message conversion {}, {}", 1, 2) await.untilAsserted { - verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + verify(fixture.transport).send(check { it: SentryEvent -> assertEquals("testing message conversion 1, 2", it.message.formatted) assertEquals("testing message conversion {}, {}", it.message.message) assertEquals(listOf("1", "2"), it.message.params) @@ -87,7 +88,7 @@ class SentryAppenderTest { fixture.logger.debug("testing event date") await.untilAsserted { - verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + verify(fixture.transport).send(check { it: SentryEvent -> val eventTime = Instant.ofEpochMilli(it.timestamp.time) .atZone(ZoneId.systemDefault()) .toLocalDateTime() @@ -104,7 +105,7 @@ class SentryAppenderTest { fixture.logger.trace("testing trace level") await.untilAsserted { - verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + verify(fixture.transport).send(check { it: SentryEvent -> assertEquals(SentryLevel.DEBUG, it.level) }) } @@ -116,7 +117,7 @@ class SentryAppenderTest { fixture.logger.debug("testing debug level") await.untilAsserted { - verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + verify(fixture.transport).send(check { it: SentryEvent -> assertEquals(SentryLevel.DEBUG, it.level) }) } @@ -128,7 +129,7 @@ class SentryAppenderTest { fixture.logger.info("testing info level") await.untilAsserted { - verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + verify(fixture.transport).send(check { it: SentryEvent -> assertEquals(SentryLevel.INFO, it.level) }) } @@ -140,7 +141,7 @@ class SentryAppenderTest { fixture.logger.warn("testing warn level") await.untilAsserted { - verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + verify(fixture.transport).send(check { it: SentryEvent -> assertEquals(SentryLevel.WARNING, it.level) }) } @@ -152,19 +153,31 @@ class SentryAppenderTest { fixture.logger.error("testing error level") await.untilAsserted { - verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + verify(fixture.transport).send(check { it: SentryEvent -> assertEquals(SentryLevel.ERROR, it.level) }) } } + @Test + fun `converts fatal log level to Sentry level`() { + fixture = Fixture(minimumEventLevel = Level.FATAL) + fixture.logger.fatal("testing fatal level") + + await.untilAsserted { + verify(fixture.transport).send(check { it: SentryEvent -> + assertEquals(SentryLevel.FATAL, it.level) + }) + } + } + @Test fun `attaches thread information`() { fixture = Fixture(minimumEventLevel = Level.WARN) fixture.logger.warn("testing thread information") await.untilAsserted { - verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + verify(fixture.transport).send(check { it: SentryEvent -> assertNotNull(it.getExtra("thread_name")) }) } @@ -177,7 +190,7 @@ class SentryAppenderTest { fixture.logger.warn("testing MDC tags") await.untilAsserted { - verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + verify(fixture.transport).send(check { it: SentryEvent -> assertEquals(mapOf("key" to "value"), it.contexts["MDC"]) }) } @@ -189,7 +202,7 @@ class SentryAppenderTest { fixture.logger.warn("testing without MDC tags") await.untilAsserted { - verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + verify(fixture.transport).send(check { it: SentryEvent -> assertFalse(it.contexts.containsKey("MDC")) }) } @@ -202,7 +215,7 @@ class SentryAppenderTest { fixture.logger.warn("testing throwable", throwable) await.untilAsserted { - verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + verify(fixture.transport).send(check { it: SentryEvent -> assertEquals(throwable, it.throwable) }) } @@ -214,7 +227,7 @@ class SentryAppenderTest { fixture.logger.info("testing sdk version") await.untilAsserted { - verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + verify(fixture.transport).send(check { it: SentryEvent -> assertEquals(BuildConfig.SENTRY_LOG4J2_SDK_NAME, it.sdk.name) assertEquals(BuildConfig.VERSION_NAME, it.sdk.version) assertNotNull(it.sdk.packages) @@ -236,7 +249,7 @@ class SentryAppenderTest { fixture.logger.warn("testing message with breadcrumbs") await.untilAsserted { - verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + verify(fixture.transport).send(check { it: SentryEvent -> assertEquals(2, it.breadcrumbs.size) val breadcrumb = it.breadcrumbs[0] val breadcrumbTime = Instant.ofEpochMilli(it.timestamp.time) @@ -260,7 +273,7 @@ class SentryAppenderTest { fixture.logger.warn("testing message with breadcrumbs") await.untilAsserted { - verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + verify(fixture.transport).send(check { it: SentryEvent -> assertEquals(1, it.breadcrumbs.size) assertEquals("this should be a breadcrumb", it.breadcrumbs[0].message) }) @@ -277,7 +290,7 @@ class SentryAppenderTest { fixture.logger.error("this should be sent as the event") await.untilAsserted { - verify(fixture.transport).send(com.nhaarman.mockitokotlin2.check { it: SentryEvent -> + 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-logback/src/main/java/io/sentry/logback/SentryAppender.java b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java index fb2236b8e..35e69b829 100644 --- a/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java +++ b/sentry-logback/src/main/java/io/sentry/logback/SentryAppender.java @@ -60,6 +60,7 @@ protected void append(@NotNull ILoggingEvent eventObject) { * @param loggingEvent the logback event * @return the sentry event */ + // for the Android compatibility we must use old Java Date class @SuppressWarnings("JdkObsolete") final @NotNull SentryEvent createEvent(@NotNull ILoggingEvent loggingEvent) { final SentryEvent event = From bb3a361bfdb4d35463791d91a55fef9e2bdf65d2 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Wed, 2 Sep 2020 17:24:15 +0200 Subject: [PATCH 4/5] Polish. --- .../java/io/sentry/log4j2/SentryAppender.java | 39 ++++++------------- .../io/sentry/log4j2/SentryAppenderTest.kt | 3 +- .../java/io/sentry/samples/log4j2/Main.java | 2 + .../src/main/resources/log4j2.xml | 2 + 4 files changed, 17 insertions(+), 29 deletions(-) diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index 1a03028b0..916ccb9e4 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -2,6 +2,8 @@ import io.sentry.core.Breadcrumb; import io.sentry.core.DateUtils; +import io.sentry.core.HubAdapter; +import io.sentry.core.IHub; import io.sentry.core.Sentry; import io.sentry.core.SentryEvent; import io.sentry.core.SentryLevel; @@ -27,17 +29,17 @@ import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.impl.ThrowableProxy; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** Appender for Log4j2 in charge of sending the logged events to a Sentry server. */ @Plugin(name = "Sentry", category = "Core", elementType = "appender", printObject = true) public final class SentryAppender extends AbstractAppender { - private @Nullable String dsn; - private @Nullable ITransport transport; + private final @Nullable String dsn; + private final @Nullable ITransport transport; private @NotNull Level minimumBreadcrumbLevel = Level.INFO; private @NotNull Level minimumEventLevel = Level.ERROR; + private final @NotNull IHub hub; public SentryAppender( final @NotNull String name, @@ -45,7 +47,8 @@ public SentryAppender( final @Nullable String dsn, final @Nullable Level minimumBreadcrumbLevel, final @Nullable Level minimumEventLevel, - final @Nullable ITransport transport) { + final @Nullable ITransport transport, + final @NotNull IHub hub) { super(name, filter, null, true, null); this.dsn = dsn; if (minimumBreadcrumbLevel != null) { @@ -55,6 +58,7 @@ public SentryAppender( this.minimumEventLevel = minimumEventLevel; } this.transport = transport; + this.hub = hub; } /** @@ -76,7 +80,7 @@ public static SentryAppender createAppender( LOGGER.error("No name provided for SentryAppender"); return null; } - return new SentryAppender(name, filter, dsn, minimumBreadcrumbLevel, minimumEventLevel, null); + return new SentryAppender(name, filter, dsn, minimumBreadcrumbLevel, minimumEventLevel, null, HubAdapter.getInstance()); } @Override @@ -96,10 +100,10 @@ public void start() { @Override public void append(final @NotNull LogEvent eventObject) { if (eventObject.getLevel().isMoreSpecificThan(minimumEventLevel)) { - Sentry.captureEvent(createEvent(eventObject)); + hub.captureEvent(createEvent(eventObject)); } if (eventObject.getLevel().isMoreSpecificThan(minimumBreadcrumbLevel)) { - Sentry.addBreadcrumb(createBreadcrumb(eventObject)); + hub.addBreadcrumb(createBreadcrumb(eventObject)); } } @@ -199,25 +203,4 @@ public void append(final @NotNull LogEvent eventObject) { return sdkVersion; } - - public void setDsn(final @Nullable String dsn) { - this.dsn = dsn; - } - - 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(final @Nullable ITransport transport) { - this.transport = transport; - } } diff --git a/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt index cae0915ad..759e291ff 100644 --- a/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt +++ b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt @@ -3,6 +3,7 @@ package io.sentry.log4j2 import com.nhaarman.mockitokotlin2.check import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verify +import io.sentry.core.HubAdapter import io.sentry.core.SentryEvent import io.sentry.core.SentryLevel import io.sentry.core.transport.ITransport @@ -35,7 +36,7 @@ class SentryAppenderTest { init { loggerContext.start() val config: Configuration = loggerContext.configuration - val appender = SentryAppender("sentry", null, "http://key@localhost/proj", minimumBreadcrumbLevel, minimumEventLevel, transport) + val appender = SentryAppender("sentry", null, "http://key@localhost/proj", minimumBreadcrumbLevel, minimumEventLevel, transport, HubAdapter.getInstance()) config.addAppender(appender) val ref = AppenderRef.createAppenderRef("sentry", null, null) diff --git a/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java b/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java index a2fc30805..5f13237d3 100644 --- a/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java +++ b/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java @@ -9,6 +9,8 @@ public class Main { private static final Logger LOGGER = LogManager.getLogger(Main.class); public static void main(String[] args) { + // The SDK was initialized through the appender configuration because a DSN was set there. + // Update the DSN in log4j2.xml to see these events in your Sentry dashboard. LOGGER.debug("Hello Sentry!"); // ThreadContext parameters are converted to Sentry Event tags diff --git a/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml b/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml index a4b801b98..b8b2c8d81 100644 --- a/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml +++ b/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml @@ -5,6 +5,8 @@ + + Date: Wed, 2 Sep 2020 18:58:05 +0200 Subject: [PATCH 5/5] Polish. --- .../java/io/sentry/log4j2/SentryAppender.java | 6 +- .../io/sentry/log4j2/SentryAppenderTest.kt | 110 ++++++++++-------- .../java/io/sentry/samples/log4j2/Main.java | 2 + .../src/main/resources/log4j2.xml | 2 +- 4 files changed, 70 insertions(+), 50 deletions(-) diff --git a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java index 916ccb9e4..c12dd6ecf 100644 --- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -135,10 +135,10 @@ public void append(final @NotNull LogEvent eventObject) { event.setExtra("thread_name", loggingEvent.getThreadName()); } - final Map mdcProperties = + final Map contextData = CollectionUtils.shallowCopy(loggingEvent.getContextData().toMap()); - if (!mdcProperties.isEmpty()) { - event.getContexts().put("MDC", mdcProperties); + if (!contextData.isEmpty()) { + event.getContexts().put("Context Data", contextData); } return event; diff --git a/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt index 759e291ff..f1c5bd1ce 100644 --- a/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt +++ b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt @@ -19,21 +19,22 @@ import kotlin.test.assertNotNull import kotlin.test.assertTrue import org.apache.logging.log4j.Level import org.apache.logging.log4j.LogManager -import org.apache.logging.log4j.Logger import org.apache.logging.log4j.ThreadContext import org.apache.logging.log4j.core.LoggerContext import org.apache.logging.log4j.core.config.AppenderRef import org.apache.logging.log4j.core.config.Configuration import org.apache.logging.log4j.core.config.LoggerConfig +import org.apache.logging.log4j.spi.ExtendedLogger import org.awaitility.kotlin.await class SentryAppenderTest { - private class Fixture(minimumBreadcrumbLevel: Level? = null, minimumEventLevel: Level? = null) { + private class Fixture { val transport = mock() - val logger: Logger val loggerContext = LogManager.getContext() as LoggerContext + var minimumBreadcrumbLevel: Level? = null + var minimumEventLevel: Level? = null - init { + fun getSut(): ExtendedLogger { loggerContext.start() val config: Configuration = loggerContext.configuration val appender = SentryAppender("sentry", null, "http://key@localhost/proj", minimumBreadcrumbLevel, minimumEventLevel, transport, HubAdapter.getInstance()) @@ -50,11 +51,11 @@ class SentryAppenderTest { appender.start() loggerContext.start() - logger = LogManager.getContext().getLogger(SentryAppenderTest::class.java.name) + return LogManager.getContext().getLogger(SentryAppenderTest::class.java.name) } } - private lateinit var fixture: Fixture + private var fixture = Fixture() @AfterTest fun `stop log4j2`() { @@ -68,8 +69,9 @@ class SentryAppenderTest { @Test fun `converts message`() { - fixture = Fixture(minimumEventLevel = Level.DEBUG) - fixture.logger.debug("testing message conversion {}, {}", 1, 2) + fixture.minimumEventLevel = Level.DEBUG + val logger = fixture.getSut() + logger.debug("testing message conversion {}, {}", 1, 2) await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> @@ -83,10 +85,11 @@ class SentryAppenderTest { @Test fun `event date is in UTC`() { - fixture = Fixture(minimumEventLevel = Level.DEBUG) + fixture.minimumEventLevel = Level.DEBUG + val logger = fixture.getSut() val utcTime = LocalDateTime.now(ZoneId.of("UTC")) - fixture.logger.debug("testing event date") + logger.debug("testing event date") await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> @@ -102,8 +105,9 @@ class SentryAppenderTest { @Test fun `converts trace log level to Sentry level`() { - fixture = Fixture(minimumEventLevel = Level.TRACE) - fixture.logger.trace("testing trace level") + fixture.minimumEventLevel = Level.TRACE + val logger = fixture.getSut() + logger.trace("testing trace level") await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> @@ -114,8 +118,9 @@ class SentryAppenderTest { @Test fun `converts debug log level to Sentry level`() { - fixture = Fixture(minimumEventLevel = Level.DEBUG) - fixture.logger.debug("testing debug level") + fixture.minimumEventLevel = Level.DEBUG + val logger = fixture.getSut() + logger.debug("testing debug level") await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> @@ -126,8 +131,9 @@ class SentryAppenderTest { @Test fun `converts info log level to Sentry level`() { - fixture = Fixture(minimumEventLevel = Level.INFO) - fixture.logger.info("testing info level") + fixture.minimumEventLevel = Level.INFO + val logger = fixture.getSut() + logger.info("testing info level") await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> @@ -138,8 +144,9 @@ class SentryAppenderTest { @Test fun `converts warn log level to Sentry level`() { - fixture = Fixture(minimumEventLevel = Level.WARN) - fixture.logger.warn("testing warn level") + fixture.minimumEventLevel = Level.WARN + val logger = fixture.getSut() + logger.warn("testing warn level") await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> @@ -150,8 +157,9 @@ class SentryAppenderTest { @Test fun `converts error log level to Sentry level`() { - fixture = Fixture(minimumEventLevel = Level.ERROR) - fixture.logger.error("testing error level") + fixture.minimumEventLevel = Level.ERROR + val logger = fixture.getSut() + logger.error("testing error level") await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> @@ -162,8 +170,9 @@ class SentryAppenderTest { @Test fun `converts fatal log level to Sentry level`() { - fixture = Fixture(minimumEventLevel = Level.FATAL) - fixture.logger.fatal("testing fatal level") + fixture.minimumEventLevel = Level.FATAL + val logger = fixture.getSut() + logger.fatal("testing fatal level") await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> @@ -174,8 +183,9 @@ class SentryAppenderTest { @Test fun `attaches thread information`() { - fixture = Fixture(minimumEventLevel = Level.WARN) - fixture.logger.warn("testing thread information") + fixture.minimumEventLevel = Level.WARN + val logger = fixture.getSut() + logger.warn("testing thread information") await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> @@ -186,21 +196,23 @@ class SentryAppenderTest { @Test fun `sets tags from ThreadContext`() { - fixture = Fixture(minimumEventLevel = Level.WARN) + fixture.minimumEventLevel = Level.WARN + val logger = fixture.getSut() ThreadContext.put("key", "value") - fixture.logger.warn("testing MDC tags") + logger.warn("testing MDC tags") await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> - assertEquals(mapOf("key" to "value"), it.contexts["MDC"]) + assertEquals(mapOf("key" to "value"), it.contexts["Context Data"]) }) } } @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") + fixture.minimumEventLevel = Level.WARN + val logger = fixture.getSut() + logger.warn("testing without MDC tags") await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> @@ -211,9 +223,10 @@ class SentryAppenderTest { @Test fun `attaches throwable`() { - fixture = Fixture(minimumEventLevel = Level.WARN) + fixture.minimumEventLevel = Level.WARN + val logger = fixture.getSut() val throwable = RuntimeException("something went wrong") - fixture.logger.warn("testing throwable", throwable) + logger.warn("testing throwable", throwable) await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> @@ -224,8 +237,9 @@ class SentryAppenderTest { @Test fun `sets SDK version`() { - fixture = Fixture(minimumEventLevel = Level.INFO) - fixture.logger.info("testing sdk version") + fixture.minimumEventLevel = Level.INFO + val logger = fixture.getSut() + logger.info("testing sdk version") await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> @@ -242,12 +256,14 @@ class SentryAppenderTest { @Test fun `attaches breadcrumbs with level higher than minimumBreadcrumbLevel`() { - fixture = Fixture(minimumBreadcrumbLevel = Level.DEBUG, minimumEventLevel = Level.WARN) + fixture.minimumEventLevel = Level.WARN + fixture.minimumBreadcrumbLevel = Level.DEBUG + val logger = fixture.getSut() 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") + logger.debug("this should be a breadcrumb #1") + logger.info("this should be a breadcrumb #2") + logger.warn("testing message with breadcrumbs") await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> @@ -267,11 +283,13 @@ class SentryAppenderTest { @Test fun `does not attach breadcrumbs with level lower than minimumBreadcrumbLevel`() { - fixture = Fixture(minimumBreadcrumbLevel = Level.INFO, minimumEventLevel = Level.WARN) + fixture.minimumEventLevel = Level.WARN + fixture.minimumBreadcrumbLevel = Level.INFO + val logger = fixture.getSut() - fixture.logger.debug("this should NOT be a breadcrumb") - fixture.logger.info("this should be a breadcrumb") - fixture.logger.warn("testing message with breadcrumbs") + logger.debug("this should NOT be a breadcrumb") + logger.info("this should be a breadcrumb") + logger.warn("testing message with breadcrumbs") await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> @@ -283,12 +301,12 @@ class SentryAppenderTest { @Test fun `attaches breadcrumbs for default appender configuration`() { - fixture = Fixture() + val logger = fixture.getSut() - 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") + logger.debug("this should not be a breadcrumb as the level is lower than the minimum INFO") + logger.info("this should be a breadcrumb") + logger.warn("this should not be sent as the event but be a breadcrumb") + logger.error("this should be sent as the event") await.untilAsserted { verify(fixture.transport).send(check { it: SentryEvent -> diff --git a/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java b/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java index 5f13237d3..6f1a4b79c 100644 --- a/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java +++ b/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java @@ -18,6 +18,8 @@ public static void main(String[] args) { // logging arguments are converted to Sentry Event parameters LOGGER.info("User has made a purchase of product: {}", 445); + // because minimumEventLevel is set to WARN this raises an event + LOGGER.warn("Important warning"); try { throw new RuntimeException("Invalid productId=445"); diff --git a/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml b/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml index b8b2c8d81..b7df67f12 100644 --- a/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml +++ b/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml @@ -8,7 +8,7 @@