diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java index bdd21bd4ef2..f115e2ae378 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java @@ -50,8 +50,19 @@ final class DisruptorUtil { static final boolean ASYNC_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL = PropertiesUtil.getProperties() .getBooleanProperty("AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull", true); - static final int DISRUPTOR_MAJOR_VERSION = - LoaderUtil.isClassAvailable("com.lmax.disruptor.SequenceReportingEventHandler") ? 3 : 4; + static final int DISRUPTOR_MAJOR_VERSION = detectDisruptorMajorVersion(); + + // TODO: replace with LoaderUtil.isClassAvailable() when TCCL is removed + // See: https://github.com/apache/logging-log4j2/issues/3706 + private static int detectDisruptorMajorVersion() { + try { + Class.forName( + "com.lmax.disruptor.SequenceReportingEventHandler", true, DisruptorUtil.class.getClassLoader()); + return 3; + } catch (final ClassNotFoundException e) { + return 4; + } + } private DisruptorUtil() {} diff --git a/log4j-osgi-test/pom.xml b/log4j-osgi-test/pom.xml index b63666715e0..9517f3385c9 100644 --- a/log4j-osgi-test/pom.xml +++ b/log4j-osgi-test/pom.xml @@ -257,6 +257,7 @@ org.apache.logging.log4j.osgi.tests.DisruptorTest + org.apache.logging.log4j.osgi.tests.DisruptorTest$TestExceptionHandler org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector @@ -291,6 +292,7 @@ org.apache.logging.log4j.osgi.tests.DisruptorTest + org.apache.logging.log4j.osgi.tests.DisruptorTest$TestExceptionHandler org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector diff --git a/log4j-osgi-test/src/test/java/org/apache/logging/log4j/osgi/tests/DisruptorTest.java b/log4j-osgi-test/src/test/java/org/apache/logging/log4j/osgi/tests/DisruptorTest.java index 45ce5fef61c..813331210c5 100644 --- a/log4j-osgi-test/src/test/java/org/apache/logging/log4j/osgi/tests/DisruptorTest.java +++ b/log4j-osgi-test/src/test/java/org/apache/logging/log4j/osgi/tests/DisruptorTest.java @@ -17,19 +17,26 @@ package org.apache.logging.log4j.osgi.tests; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.ops4j.pax.exam.CoreOptions.junitBundles; import static org.ops4j.pax.exam.CoreOptions.linkBundle; import static org.ops4j.pax.exam.CoreOptions.options; +import com.lmax.disruptor.ExceptionHandler; +import java.io.IOException; +import java.net.URL; +import java.util.concurrent.atomic.AtomicReference; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.async.AsyncLoggerContext; +import org.apache.logging.log4j.core.async.RingBufferLogEvent; import org.junit.Test; import org.junit.runner.RunWith; +import org.junitpioneer.jupiter.Issue; import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.junit.PaxExam; import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy; @@ -39,6 +46,8 @@ @ExamReactorStrategy(PerClass.class) public class DisruptorTest { + private static final int MESSAGE_COUNT = 128; + @org.ops4j.pax.exam.Configuration public Option[] config() { return options( @@ -59,21 +68,58 @@ public Option[] config() { } @Test - public void testDisruptorLog() { - // Logger context - LoggerContext context = getLoggerContext(); - assertTrue("LoggerContext is an instance of AsyncLoggerContext", context instanceof AsyncLoggerContext); - final CustomConfiguration custom = (CustomConfiguration) context.getConfiguration(); - // Logging - final Logger logger = LogManager.getLogger(getClass()); - logger.info("Hello OSGI from Log4j2!"); - - context.stop(); - assertEquals(1, custom.getEvents().size()); - final LogEvent event = custom.getEvents().get(0); - assertEquals("Hello OSGI from Log4j2!", event.getMessage().getFormattedMessage()); - assertEquals(Level.INFO, event.getLevel()); - custom.clearEvents(); + @Issue("https://github.com/apache/logging-log4j2/issues/3706") + public void testDisruptorLog() throws IOException { + ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader classLoader = createClassLoader(); + try { + // Set the context classloader to an empty classloader, so attempts to use the TCCL will not find any + // classes. + Thread.currentThread().setContextClassLoader(classLoader); + // Logger context + LoggerContext context = getLoggerContext(); + assertTrue("LoggerContext is an instance of AsyncLoggerContext", context instanceof AsyncLoggerContext); + final CustomConfiguration custom = (CustomConfiguration) context.getConfiguration(); + // Logging + final Logger logger = LogManager.getLogger(getClass()); + for (int i = 0; i < MESSAGE_COUNT; i++) { + logger.info("Hello OSGI from Log4j2! {}", i); + } + + context.stop(); + assertEquals(MESSAGE_COUNT, custom.getEvents().size()); + for (int i = 0; i < MESSAGE_COUNT; i++) { + final LogEvent event = custom.getEvents().get(i); + assertEquals( + "Message nr " + i, + "Hello OSGI from Log4j2! " + i, + event.getMessage().getFormattedMessage()); + assertEquals(Level.INFO, event.getLevel()); + } + custom.clearEvents(); + assertNull("Asynchronous exception", TestExceptionHandler.exception.get()); + } finally { + Thread.currentThread().setContextClassLoader(threadContextClassLoader); + } + } + + private static ClassLoader createClassLoader() { + // We want a classloader capable only of loading TestExceptionHandler. + // This is needed to detect exceptions thrown by the asynchronous thread. + return new ClassLoader() { + @Override + public Class loadClass(String name) throws ClassNotFoundException { + if (name.equals(TestExceptionHandler.class.getName())) { + return TestExceptionHandler.class; + } + throw new ClassNotFoundException(name); + } + + @Override + public URL getResource(String name) { + return null; // No resources available. + } + }; } private static LoggerContext getLoggerContext() { @@ -81,4 +127,28 @@ private static LoggerContext getLoggerContext() { assertEquals("AsyncDefault", ctx.getName()); return ctx; } + + public static class TestExceptionHandler implements ExceptionHandler { + + private static final AtomicReference exception = new AtomicReference<>(); + + @Override + public void handleEventException(Throwable ex, long sequence, RingBufferLogEvent event) { + setException(ex); + } + + @Override + public void handleOnStartException(Throwable ex) { + setException(ex); + } + + @Override + public void handleOnShutdownException(Throwable ex) { + setException(ex); + } + + private static void setException(Throwable ex) { + exception.compareAndSet(null, ex); + } + } } diff --git a/src/changelog/.2.x.x/3706_disruptor-tccl.xml b/src/changelog/.2.x.x/3706_disruptor-tccl.xml new file mode 100644 index 00000000000..a6a446fb14f --- /dev/null +++ b/src/changelog/.2.x.x/3706_disruptor-tccl.xml @@ -0,0 +1,12 @@ + + + + + Fix detection of the Disruptor major version in some environments. + +