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..c12dd6ecf --- /dev/null +++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java @@ -0,0 +1,206 @@ +package io.sentry.log4j2; + +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; +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.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 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, + final @Nullable Filter filter, + final @Nullable String dsn, + final @Nullable Level minimumBreadcrumbLevel, + final @Nullable Level minimumEventLevel, + final @Nullable ITransport transport, + final @NotNull IHub hub) { + super(name, filter, null, true, null); + this.dsn = dsn; + if (minimumBreadcrumbLevel != null) { + this.minimumBreadcrumbLevel = minimumBreadcrumbLevel; + } + if (minimumEventLevel != null) { + this.minimumEventLevel = minimumEventLevel; + } + this.transport = transport; + this.hub = hub; + } + + /** + * 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, HubAdapter.getInstance()); + } + + @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(final @NotNull LogEvent eventObject) { + if (eventObject.getLevel().isMoreSpecificThan(minimumEventLevel)) { + hub.captureEvent(createEvent(eventObject)); + } + if (eventObject.getLevel().isMoreSpecificThan(minimumBreadcrumbLevel)) { + hub.addBreadcrumb(createBreadcrumb(eventObject)); + } + } + + /** + * Creates {@link SentryEvent} from Log4j2 {@link LogEvent}. + * + * @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(final @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 contextData = + CollectionUtils.shallowCopy(loggingEvent.getContextData().toMap()); + if (!contextData.isEmpty()) { + event.getContexts().put("Context Data", contextData); + } + + return event; + } + + private @NotNull List toParams(final @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(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; + } else if (level.isMoreSpecificThan(Level.INFO)) { + return SentryLevel.INFO; + } else { + return SentryLevel.DEBUG; + } + } + + private @NotNull SdkVersion createSdkVersion(final @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; + } +} 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..f1c5bd1ce --- /dev/null +++ b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderTest.kt @@ -0,0 +1,319 @@ +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 +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.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 { + val transport = mock() + val loggerContext = LogManager.getContext() as LoggerContext + var minimumBreadcrumbLevel: Level? = null + var minimumEventLevel: Level? = null + + fun getSut(): ExtendedLogger { + loggerContext.start() + val config: Configuration = loggerContext.configuration + val appender = SentryAppender("sentry", null, "http://key@localhost/proj", minimumBreadcrumbLevel, minimumEventLevel, transport, HubAdapter.getInstance()) + 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() + + return LogManager.getContext().getLogger(SentryAppenderTest::class.java.name) + } + } + + private var fixture = Fixture() + + @AfterTest + fun `stop log4j2`() { + fixture.loggerContext.stop() + } + + @BeforeTest + fun `clear MDC`() { + ThreadContext.clearAll() + } + + @Test + fun `converts message`() { + fixture.minimumEventLevel = Level.DEBUG + val logger = fixture.getSut() + logger.debug("testing message conversion {}, {}", 1, 2) + + await.untilAsserted { + 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) + assertEquals("io.sentry.log4j2.SentryAppenderTest", it.logger) + }) + } + } + + @Test + fun `event date is in UTC`() { + fixture.minimumEventLevel = Level.DEBUG + val logger = fixture.getSut() + val utcTime = LocalDateTime.now(ZoneId.of("UTC")) + + logger.debug("testing event date") + + await.untilAsserted { + verify(fixture.transport).send(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.minimumEventLevel = Level.TRACE + val logger = fixture.getSut() + logger.trace("testing trace level") + + await.untilAsserted { + verify(fixture.transport).send(check { it: SentryEvent -> + assertEquals(SentryLevel.DEBUG, it.level) + }) + } + } + + @Test + fun `converts debug log level to Sentry level`() { + fixture.minimumEventLevel = Level.DEBUG + val logger = fixture.getSut() + logger.debug("testing debug level") + + await.untilAsserted { + verify(fixture.transport).send(check { it: SentryEvent -> + assertEquals(SentryLevel.DEBUG, it.level) + }) + } + } + + @Test + fun `converts info log level to Sentry level`() { + fixture.minimumEventLevel = Level.INFO + val logger = fixture.getSut() + logger.info("testing info level") + + await.untilAsserted { + verify(fixture.transport).send(check { it: SentryEvent -> + assertEquals(SentryLevel.INFO, it.level) + }) + } + } + + @Test + fun `converts warn log level to Sentry level`() { + fixture.minimumEventLevel = Level.WARN + val logger = fixture.getSut() + logger.warn("testing warn level") + + await.untilAsserted { + verify(fixture.transport).send(check { it: SentryEvent -> + assertEquals(SentryLevel.WARNING, it.level) + }) + } + } + + @Test + fun `converts error log level to Sentry level`() { + fixture.minimumEventLevel = Level.ERROR + val logger = fixture.getSut() + logger.error("testing error level") + + await.untilAsserted { + verify(fixture.transport).send(check { it: SentryEvent -> + assertEquals(SentryLevel.ERROR, it.level) + }) + } + } + + @Test + fun `converts fatal log level to Sentry level`() { + fixture.minimumEventLevel = Level.FATAL + val logger = fixture.getSut() + 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.minimumEventLevel = Level.WARN + val logger = fixture.getSut() + logger.warn("testing thread information") + + await.untilAsserted { + verify(fixture.transport).send(check { it: SentryEvent -> + assertNotNull(it.getExtra("thread_name")) + }) + } + } + + @Test + fun `sets tags from ThreadContext`() { + fixture.minimumEventLevel = Level.WARN + val logger = fixture.getSut() + ThreadContext.put("key", "value") + logger.warn("testing MDC tags") + + await.untilAsserted { + verify(fixture.transport).send(check { it: SentryEvent -> + assertEquals(mapOf("key" to "value"), it.contexts["Context Data"]) + }) + } + } + + @Test + fun `does not create MDC context when no MDC tags are set`() { + fixture.minimumEventLevel = Level.WARN + val logger = fixture.getSut() + logger.warn("testing without MDC tags") + + await.untilAsserted { + verify(fixture.transport).send(check { it: SentryEvent -> + assertFalse(it.contexts.containsKey("MDC")) + }) + } + } + + @Test + fun `attaches throwable`() { + fixture.minimumEventLevel = Level.WARN + val logger = fixture.getSut() + val throwable = RuntimeException("something went wrong") + logger.warn("testing throwable", throwable) + + await.untilAsserted { + verify(fixture.transport).send(check { it: SentryEvent -> + assertEquals(throwable, it.throwable) + }) + } + } + + @Test + fun `sets SDK version`() { + fixture.minimumEventLevel = Level.INFO + val logger = fixture.getSut() + logger.info("testing sdk version") + + await.untilAsserted { + 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) + 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.minimumEventLevel = Level.WARN + fixture.minimumBreadcrumbLevel = Level.DEBUG + val logger = fixture.getSut() + val utcTime = LocalDateTime.now(ZoneId.of("UTC")) + + 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 -> + 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.minimumEventLevel = Level.WARN + fixture.minimumBreadcrumbLevel = Level.INFO + val logger = fixture.getSut() + + 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 -> + assertEquals(1, it.breadcrumbs.size) + assertEquals("this should be a breadcrumb", it.breadcrumbs[0].message) + }) + } + } + + @Test + fun `attaches breadcrumbs for default appender configuration`() { + val logger = fixture.getSut() + + 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 -> + 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-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 = 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..6f1a4b79c --- /dev/null +++ b/sentry-samples/sentry-samples-log4j2/src/main/java/io/sentry/samples/log4j2/Main.java @@ -0,0 +1,30 @@ +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) { + // 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 + ThreadContext.put("userId", UUID.randomUUID().toString()); + + // 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"); + } 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..b7df67f12 --- /dev/null +++ b/sentry-samples/sentry-samples-log4j2/src/main/resources/log4j2.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + 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")