diff --git a/CHANGELOG.md b/CHANGELOG.md
index a6600fda921..1acd50c0284 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@
import
```
+ - Add log4j2 spring boot 4 autoconfiguration ([#5403](https://github.com/getsentry/sentry-java/pull/5403))
### Fixes
diff --git a/sentry-log4j2/api/sentry-log4j2.api b/sentry-log4j2/api/sentry-log4j2.api
index 2a5d4bf7895..7eebea7136e 100644
--- a/sentry-log4j2/api/sentry-log4j2.api
+++ b/sentry-log4j2/api/sentry-log4j2.api
@@ -12,6 +12,9 @@ public class io/sentry/log4j2/SentryAppender : org/apache/logging/log4j/core/app
public static fun createAppender (Ljava/lang/String;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Lorg/apache/logging/log4j/Level;Ljava/lang/String;Ljava/lang/Boolean;Lorg/apache/logging/log4j/core/Filter;Ljava/lang/String;)Lio/sentry/log4j2/SentryAppender;
protected fun createBreadcrumb (Lorg/apache/logging/log4j/core/LogEvent;)Lio/sentry/Breadcrumb;
protected fun createEvent (Lorg/apache/logging/log4j/core/LogEvent;)Lio/sentry/SentryEvent;
+ public fun getMinimumBreadcrumbLevel ()Lorg/apache/logging/log4j/Level;
+ public fun getMinimumEventLevel ()Lorg/apache/logging/log4j/Level;
+ public fun getMinimumLevel ()Lorg/apache/logging/log4j/Level;
public fun start ()V
}
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 df0f9eeb2d2..9bc8e90bfe0 100644
--- a/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java
+++ b/sentry-log4j2/src/main/java/io/sentry/log4j2/SentryAppender.java
@@ -167,6 +167,18 @@ public void start() {
start(getOptionsConfiguration(null));
}
+ public @NotNull Level getMinimumBreadcrumbLevel() {
+ return minimumBreadcrumbLevel;
+ }
+
+ public @NotNull Level getMinimumEventLevel() {
+ return minimumEventLevel;
+ }
+
+ public @NotNull Level getMinimumLevel() {
+ return minimumLevel;
+ }
+
@NotNull
Sentry.OptionsConfiguration getOptionsConfiguration(
final @Nullable Sentry.OptionsConfiguration additionalOptionsConfiguration) {
diff --git a/sentry-samples/sentry-samples-spring-boot-4/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-4/build.gradle.kts
index 17ec5b2a45f..f30aa388344 100644
--- a/sentry-samples/sentry-samples-spring-boot-4/build.gradle.kts
+++ b/sentry-samples/sentry-samples-spring-boot-4/build.gradle.kts
@@ -56,6 +56,19 @@ dependencies {
implementation(projects.sentryQuartz)
implementation(projects.sentryAsyncProfiler)
+ implementation(projects.sentryLog4j2)
+ implementation(libs.log4j.api)
+ implementation(libs.log4j.core)
+
+ // Enable Log4j2 in Spring Boot
+ // implementation("org.springframework.boot:spring-boot-starter-log4j2")
+ // modules {
+ // module("org.springframework.boot:spring-boot-starter-logging") {
+ // replacedBy("org.springframework.boot:spring-boot-starter-log4j2", "Use Log4j2 instead of
+ // Logback")
+ // }
+ // }
+
// cache tracing
implementation(libs.springboot4.starter.cache)
implementation(libs.caffeine)
diff --git a/sentry-spring-boot-4/api/sentry-spring-boot-4.api b/sentry-spring-boot-4/api/sentry-spring-boot-4.api
index 4c8be990b85..f3a16d45f57 100644
--- a/sentry-spring-boot-4/api/sentry-spring-boot-4.api
+++ b/sentry-spring-boot-4/api/sentry-spring-boot-4.api
@@ -13,6 +13,17 @@ public class io/sentry/spring/boot4/SentryAutoConfiguration {
public fun ()V
}
+public class io/sentry/spring/boot4/SentryLog4j2AppenderAutoConfiguration {
+ public fun ()V
+ public fun sentryLog4j2Initializer (Lio/sentry/spring/boot4/SentryProperties;)Lio/sentry/spring/boot4/SentryLog4j2Initializer;
+}
+
+public class io/sentry/spring/boot4/SentryLog4j2Initializer : org/springframework/context/event/GenericApplicationListener {
+ public fun (Lio/sentry/spring/boot4/SentryProperties;)V
+ public fun onApplicationEvent (Lorg/springframework/context/ApplicationEvent;)V
+ public fun supportsEventType (Lorg/springframework/core/ResolvableType;)Z
+}
+
public class io/sentry/spring/boot4/SentryLogbackAppenderAutoConfiguration {
public fun ()V
public fun sentryLogbackInitializer (Lio/sentry/spring/boot4/SentryProperties;)Lio/sentry/spring/boot4/SentryLogbackInitializer;
diff --git a/sentry-spring-boot-4/build.gradle.kts b/sentry-spring-boot-4/build.gradle.kts
index 2a6634b257f..cc5de913f96 100644
--- a/sentry-spring-boot-4/build.gradle.kts
+++ b/sentry-spring-boot-4/build.gradle.kts
@@ -30,7 +30,10 @@ dependencies {
api(projects.sentry)
api(projects.sentrySpring7)
compileOnly(projects.sentryLogback)
+ compileOnly(projects.sentryLog4j2)
compileOnly(projects.sentryApacheHttpClient5)
+ compileOnly(libs.log4j.api)
+ compileOnly(libs.log4j.core)
compileOnly(platform(SpringBootPlugin.BOM_COORDINATES))
compileOnly(projects.sentryGraphql)
compileOnly(projects.sentryGraphql22)
@@ -65,7 +68,10 @@ dependencies {
// tests
testImplementation(projects.sentryLogback)
+ testImplementation(projects.sentryLog4j2)
testImplementation(projects.sentryApacheHttpClient5)
+ testImplementation(libs.log4j.api)
+ testImplementation(libs.log4j.core)
testImplementation(projects.sentryGraphql)
testImplementation(projects.sentryGraphql22)
testImplementation(projects.sentryKafka)
diff --git a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryLog4j2AppenderAutoConfiguration.java b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryLog4j2AppenderAutoConfiguration.java
new file mode 100644
index 00000000000..d7ebf82b222
--- /dev/null
+++ b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryLog4j2AppenderAutoConfiguration.java
@@ -0,0 +1,26 @@
+package io.sentry.spring.boot4;
+
+import com.jakewharton.nopen.annotation.Open;
+import io.sentry.log4j2.SentryAppender;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/** Auto-configures {@link SentryAppender}. */
+@Configuration(proxyBeanMethods = false)
+@Open
+@ConditionalOnClass({LoggerContext.class, SentryAppender.class})
+@ConditionalOnProperty(name = "sentry.logging.enabled", havingValue = "true", matchIfMissing = true)
+@ConditionalOnBean(SentryProperties.class)
+public class SentryLog4j2AppenderAutoConfiguration {
+
+ @Bean
+ public @NotNull SentryLog4j2Initializer sentryLog4j2Initializer(
+ final @NotNull SentryProperties sentryProperties) {
+ return new SentryLog4j2Initializer(sentryProperties);
+ }
+}
diff --git a/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryLog4j2Initializer.java b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryLog4j2Initializer.java
new file mode 100644
index 00000000000..efdf9dc255a
--- /dev/null
+++ b/sentry-spring-boot-4/src/main/java/io/sentry/spring/boot4/SentryLog4j2Initializer.java
@@ -0,0 +1,134 @@
+package io.sentry.spring.boot4;
+
+import com.jakewharton.nopen.annotation.Open;
+import io.sentry.ScopesAdapter;
+import io.sentry.log4j2.SentryAppender;
+import io.sentry.util.Objects;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.context.event.GenericApplicationListener;
+import org.springframework.core.ResolvableType;
+
+/** Registers {@link SentryAppender} after Spring context gets refreshed. */
+@Open
+public class SentryLog4j2Initializer implements GenericApplicationListener {
+ private static final Logger logger = LoggerFactory.getLogger(SentryLog4j2Initializer.class);
+ private static final String SENTRY_APPENDER_NAME = "SENTRY_APPENDER";
+
+ private final @NotNull SentryProperties sentryProperties;
+ private final @NotNull List loggers;
+ @Nullable private SentryAppender sentryAppender;
+
+ public SentryLog4j2Initializer(final @NotNull SentryProperties sentryProperties) {
+ this.sentryProperties = Objects.requireNonNull(sentryProperties, "properties are required");
+ loggers = sentryProperties.getLogging().getLoggers();
+ }
+
+ @Override
+ public boolean supportsEventType(final @NotNull ResolvableType eventType) {
+ return eventType.getRawClass() != null
+ && ContextRefreshedEvent.class.isAssignableFrom(eventType.getRawClass());
+ }
+
+ @Override
+ public void onApplicationEvent(final @NotNull ApplicationEvent event) {
+ final Object context = LogManager.getContext(false);
+ if (!(context instanceof LoggerContext)) {
+ logger.info(
+ "Sentry Log4j2 appender was not configured because Log4j2 Core is not the active logging backend. Log4j2 API calls may be routed through SLF4J.");
+ return;
+ }
+
+ final LoggerContext loggerContext = (LoggerContext) context;
+ final Configuration configuration = loggerContext.getConfiguration();
+
+ boolean changed = false;
+ for (final String loggerName : normalizeLoggerNames(loggers)) {
+ final LoggerConfig loggerConfig = getOrCreateLoggerConfig(configuration, loggerName);
+ if (!isSentryAppenderRegistered(loggerConfig)) {
+ loggerConfig.addAppender(getSentryAppender(configuration), null, null);
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ loggerContext.updateLoggers(configuration);
+ }
+ }
+
+ private @NotNull LoggerConfig getOrCreateLoggerConfig(
+ final @NotNull Configuration configuration, final @NotNull String loggerName) {
+ if (LogManager.ROOT_LOGGER_NAME.equals(loggerName)) {
+ return configuration.getRootLogger();
+ }
+
+ final LoggerConfig loggerConfig = configuration.getLoggerConfig(loggerName);
+ if (loggerName.equals(loggerConfig.getName())) {
+ return loggerConfig;
+ }
+
+ final LoggerConfig newLoggerConfig = new LoggerConfig(loggerName, null, true);
+ newLoggerConfig.setParent(loggerConfig);
+ configuration.addLogger(loggerName, newLoggerConfig);
+ return newLoggerConfig;
+ }
+
+ private @NotNull SentryAppender getSentryAppender(final @NotNull Configuration configuration) {
+ if (sentryAppender == null) {
+ sentryAppender =
+ new SentryAppender(
+ SENTRY_APPENDER_NAME,
+ null,
+ null,
+ toLog4jLevel(sentryProperties.getLogging().getMinimumBreadcrumbLevel()),
+ toLog4jLevel(sentryProperties.getLogging().getMinimumEventLevel()),
+ toLog4jLevel(sentryProperties.getLogging().getMinimumLevel()),
+ null,
+ null,
+ ScopesAdapter.getInstance(),
+ null);
+ sentryAppender.start();
+ configuration.addAppender(sentryAppender);
+ }
+ return sentryAppender;
+ }
+
+ private @NotNull Set normalizeLoggerNames(final @NotNull List loggerNames) {
+ final Set normalized = new LinkedHashSet<>();
+ for (final String loggerName : loggerNames) {
+ if (loggerName == null || loggerName.trim().isEmpty()) {
+ continue;
+ }
+ normalized.add(normalizeLoggerName(loggerName.trim()));
+ }
+ return normalized;
+ }
+
+ private boolean isSentryAppenderRegistered(final @NotNull LoggerConfig loggerConfig) {
+ return loggerConfig.getAppenders().values().stream()
+ .anyMatch(appender -> appender.getClass().equals(SentryAppender.class));
+ }
+
+ private @NotNull String normalizeLoggerName(final @NotNull String loggerName) {
+ if (org.slf4j.Logger.ROOT_LOGGER_NAME.equals(loggerName)) {
+ return LogManager.ROOT_LOGGER_NAME;
+ }
+ return loggerName;
+ }
+
+ private @Nullable Level toLog4jLevel(final @Nullable org.slf4j.event.Level slf4jLevel) {
+ return slf4jLevel == null ? null : Level.getLevel(slf4jLevel.name());
+ }
+}
diff --git a/sentry-spring-boot-4/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/sentry-spring-boot-4/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index a108fa2ca10..e3e7c2e467b 100644
--- a/sentry-spring-boot-4/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/sentry-spring-boot-4/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1,4 +1,5 @@
io.sentry.spring.boot4.SentryAutoConfiguration
io.sentry.spring.boot4.SentryProfilerAutoConfiguration
io.sentry.spring.boot4.SentryLogbackAppenderAutoConfiguration
+io.sentry.spring.boot4.SentryLog4j2AppenderAutoConfiguration
io.sentry.spring.boot4.SentryWebfluxAutoConfiguration
diff --git a/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryLog4j2AppenderAutoConfigurationTest.kt b/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryLog4j2AppenderAutoConfigurationTest.kt
new file mode 100644
index 00000000000..6fa961f25dc
--- /dev/null
+++ b/sentry-spring-boot-4/src/test/kotlin/io/sentry/spring/boot4/SentryLog4j2AppenderAutoConfigurationTest.kt
@@ -0,0 +1,281 @@
+package io.sentry.spring.boot4
+
+import ch.qos.logback.classic.Logger
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.core.read.ListAppender
+import io.sentry.ITransportFactory
+import io.sentry.NoOpTransportFactory
+import io.sentry.ScopesAdapter
+import io.sentry.log4j2.SentryAppender
+import kotlin.test.AfterTest
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import org.apache.logging.log4j.Level
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.core.Appender
+import org.apache.logging.log4j.core.LoggerContext
+import org.apache.logging.log4j.core.config.DefaultConfiguration
+import org.apache.logging.log4j.core.config.LoggerConfig
+import org.assertj.core.api.Assertions.assertThat
+import org.slf4j.LoggerFactory
+import org.springframework.boot.autoconfigure.AutoConfigurations
+import org.springframework.boot.test.context.FilteredClassLoader
+import org.springframework.boot.test.context.runner.ApplicationContextRunner
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+
+class SentryLog4j2AppenderAutoConfigurationTest {
+
+ private val baseContextRunner =
+ ApplicationContextRunner()
+ .withConfiguration(
+ AutoConfigurations.of(
+ SentryLog4j2AppenderAutoConfiguration::class.java,
+ SentryAutoConfiguration::class.java,
+ )
+ )
+ .withPropertyValues(
+ "sentry.shutdownTimeoutMillis=0",
+ "sentry.sessionFlushTimeoutMillis=0",
+ "sentry.flushTimeoutMillis=0",
+ "sentry.readTimeoutMillis=50",
+ "sentry.connectionTimeoutMillis=50",
+ "sentry.send-modules=false",
+ "sentry.attach-stacktrace=false",
+ "sentry.attach-threads=false",
+ "sentry.enable-backpressure-handling=false",
+ "sentry.enable-spotlight=false",
+ "sentry.debug=false",
+ "sentry.max-breadcrumbs=0",
+ )
+
+ private val contextRunner =
+ baseContextRunner
+ .withLog4j2CoreProvider()
+ .withUserConfiguration(NoOpTransportConfiguration::class.java)
+
+ private val dsnEnabledRunner =
+ baseContextRunner
+ .withLog4j2CoreProvider()
+ .withPropertyValues("sentry.dsn=http://key@localhost/proj")
+ .withUserConfiguration(NoOpTransportConfiguration::class.java)
+
+ // Hide the Log4j2 Core provider so LogManager uses the Log4j-to-SLF4J bridge.
+ private val log4j2BridgeDsnEnabledRunner =
+ baseContextRunner
+ .withClassLoader(FilteredClassLoader("org.apache.logging.log4j.core.impl.Log4jProvider"))
+ .withPropertyValues("sentry.dsn=http://key@localhost/proj")
+ .withUserConfiguration(NoOpTransportConfiguration::class.java)
+
+ private val originalLogManagerFactory = LogManager.getFactory()
+
+ private val loggerContext: LoggerContext
+ get() = LogManager.getContext(false) as LoggerContext
+
+ private val configuration
+ get() = loggerContext.configuration
+
+ private val rootLogger
+ get() = configuration.rootLogger
+
+ @BeforeTest
+ fun `reset Log4j2 context`() {
+ useLog4j2Core()
+ resetLog4j2Context()
+ }
+
+ @AfterTest
+ fun `restore Log4j2 context`() {
+ useLog4j2Core()
+ resetLog4j2Context()
+ LogManager.setFactory(originalLogManagerFactory)
+ }
+
+ @Test
+ fun `does not configure SentryAppender when auto-configuration dsn is not set`() {
+ contextRunner.run { assertThat(rootLogger.getAppenders(SentryAppender::class.java)).isEmpty() }
+ }
+
+ @Test
+ fun `configures SentryAppender`() {
+ dsnEnabledRunner.run {
+ assertThat(rootLogger.getAppenders(SentryAppender::class.java)).hasSize(1)
+ }
+ }
+
+ @Test
+ fun `configures SentryAppender for configured loggers`() {
+ dsnEnabledRunner
+ .withPropertyValues("sentry.logging.loggers[0]=foo.bar", "sentry.logging.loggers[1]=baz")
+ .run {
+ val fooBarLogger = configuration.getLoggerConfig("foo.bar")
+ val bazLogger = configuration.getLoggerConfig("baz")
+
+ assertThat(rootLogger.getAppenders(SentryAppender::class.java)).hasSize(0)
+ assertThat(fooBarLogger.getAppenders(SentryAppender::class.java)).hasSize(1)
+ assertThat(bazLogger.getAppenders(SentryAppender::class.java)).hasSize(1)
+ }
+ }
+
+ @Test
+ fun `configures SentryAppender for descendant logger with additivity disabled`() {
+ val loggerConfig = LoggerConfig("com.example", null, false)
+ configuration.addLogger("com.example", loggerConfig)
+ loggerContext.updateLoggers(configuration)
+
+ dsnEnabledRunner
+ .withPropertyValues("sentry.logging.loggers[0]=ROOT", "sentry.logging.loggers[1]=com.example")
+ .run {
+ assertThat(rootLogger.getAppenders(SentryAppender::class.java)).hasSize(1)
+ assertThat(
+ configuration.getLoggerConfig("com.example").getAppenders(SentryAppender::class.java)
+ )
+ .hasSize(1)
+ }
+ }
+
+ @Test
+ fun `configures SentryAppender for none of the loggers if so configured`() {
+ dsnEnabledRunner.withPropertyValues("sentry.logging.loggers=").run {
+ val fooBarLogger = configuration.getLoggerConfig("foo.bar")
+ val bazLogger = configuration.getLoggerConfig("baz")
+
+ assertThat(rootLogger.getAppenders(SentryAppender::class.java)).hasSize(0)
+ assertThat(fooBarLogger.getAppenders(SentryAppender::class.java)).hasSize(0)
+ assertThat(bazLogger.getAppenders(SentryAppender::class.java)).hasSize(0)
+ }
+ }
+
+ @Test
+ fun `does not overwrite Spring Boot Sentry options`() {
+ dsnEnabledRunner.withPropertyValues("sentry.environment=boot-env").run {
+ assertThat(rootLogger.getAppenders(SentryAppender::class.java)).hasSize(1)
+ assertThat(ScopesAdapter.getInstance().options.environment).isEqualTo("boot-env")
+ }
+ }
+
+ @Test
+ fun `sets SentryAppender properties`() {
+ dsnEnabledRunner
+ .withPropertyValues(
+ "sentry.logging.minimum-event-level=info",
+ "sentry.logging.minimum-breadcrumb-level=debug",
+ "sentry.logging.minimum-level=error",
+ )
+ .run {
+ val appenders = rootLogger.getAppenders(SentryAppender::class.java)
+ assertThat(appenders).hasSize(1)
+ val sentryAppender = appenders[0] as SentryAppender
+
+ assertThat(sentryAppender.minimumBreadcrumbLevel).isEqualTo(Level.DEBUG)
+ assertThat(sentryAppender.minimumEventLevel).isEqualTo(Level.INFO)
+ assertThat(sentryAppender.minimumLevel).isEqualTo(Level.ERROR)
+ }
+ }
+
+ @Test
+ fun `does not configure SentryAppender when logging is disabled`() {
+ contextRunner.withPropertyValues("sentry.logging.enabled=false").run {
+ assertThat(rootLogger.getAppenders(SentryAppender::class.java)).isEmpty()
+ }
+ }
+
+ @Test
+ fun `does not configure SentryAppender when appender is already configured`() {
+ val sentryAppender =
+ SentryAppender(
+ "customAppender",
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ io.sentry.ScopesAdapter.getInstance(),
+ null,
+ )
+ sentryAppender.start()
+ configuration.addAppender(sentryAppender)
+ rootLogger.addAppender(sentryAppender, null, null)
+ loggerContext.updateLoggers()
+
+ dsnEnabledRunner.run {
+ val appenders = rootLogger.getAppenders(SentryAppender::class.java)
+ assertThat(appenders).hasSize(1)
+ assertThat(appenders.first().name).isEqualTo("customAppender")
+ }
+ }
+
+ @Test
+ fun `does not configure SentryAppender when active Log4j2 context is not Log4j2 Core`() {
+ val logbackLogger = LoggerFactory.getLogger(SentryLog4j2Initializer::class.java) as Logger
+ val listAppender = ListAppender()
+ listAppender.start()
+ logbackLogger.addAppender(listAppender)
+
+ try {
+ useSlf4jBridge()
+ log4j2BridgeDsnEnabledRunner.run {
+ assertThat(listAppender.list.map { it.formattedMessage }).anyMatch {
+ it.contains("Sentry Log4j2 appender was not configured")
+ }
+ }
+ } finally {
+ logbackLogger.detachAppender(listAppender)
+ listAppender.stop()
+ }
+ }
+
+ @Test
+ fun `does not configure SentryAppender when log4j2 is not on the classpath`() {
+ baseContextRunner
+ .withPropertyValues("sentry.dsn=http://key@localhost/proj")
+ .withClassLoader(FilteredClassLoader(LoggerContext::class.java))
+ .run { assertThat(rootLogger.getAppenders(SentryAppender::class.java)).isEmpty() }
+ }
+
+ @Test
+ fun `does not configure SentryAppender when sentry-log4j2 module is not on the classpath`() {
+ baseContextRunner
+ .withPropertyValues("sentry.dsn=http://key@localhost/proj")
+ .withClassLoader(FilteredClassLoader(SentryAppender::class.java))
+ .run { assertThat(rootLogger.getAppenders(SentryAppender::class.java)).isEmpty() }
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ open class NoOpTransportConfiguration {
+
+ @Bean
+ open fun noOpTransportFactory(): ITransportFactory {
+ return NoOpTransportFactory.getInstance()
+ }
+ }
+}
+
+fun org.apache.logging.log4j.core.config.LoggerConfig.getAppenders(
+ clazz: Class
+): List {
+ return this.appenders.values.filter { it.javaClass == clazz }
+}
+
+private fun ApplicationContextRunner.withLog4j2CoreProvider(): ApplicationContextRunner =
+ // Hide the Log4j-to-SLF4J provider so LogManager uses Log4j2 Core in these tests.
+ withClassLoader(FilteredClassLoader("org.apache.logging.slf4j"))
+
+private fun useLog4j2Core() {
+ LogManager.setFactory(org.apache.logging.log4j.core.impl.Log4jContextFactory())
+}
+
+private fun useSlf4jBridge() {
+ val factory =
+ Class.forName("org.apache.logging.slf4j.SLF4JLoggerContextFactory")
+ .getDeclaredConstructor()
+ .newInstance() as org.apache.logging.log4j.spi.LoggerContextFactory
+ LogManager.setFactory(factory)
+}
+
+private fun resetLog4j2Context() {
+ val loggerContext = LogManager.getContext(false) as? LoggerContext ?: return
+ loggerContext.reconfigure(DefaultConfiguration())
+}