diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/StackLocator.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/StackLocator.java index c19670c4483..2740fcfa4de 100644 --- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/StackLocator.java +++ b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/StackLocator.java @@ -45,9 +45,18 @@ public Class getCallerClass(final String fqcn) { } public Class getCallerClass(final String fqcn, final String pkg) { - return walker.walk(s -> s.dropWhile(f -> !f.getClassName().equals(fqcn)). - dropWhile(f -> f.getClassName().equals(fqcn)).dropWhile(f -> !f.getClassName().startsWith(pkg)). - findFirst()).map(StackWalker.StackFrame::getDeclaringClass).orElse(null); + return getCallerClass(fqcn, pkg, 0); + } + + public Class getCallerClass(final String fqcn, final String pkg, final int skipDepth) { + return walker.walk(s -> s + .dropWhile(f -> !f.getClassName().equals(fqcn)) + .dropWhile(f -> f.getClassName().equals(fqcn)) + .dropWhile(f -> !f.getClassName().startsWith(pkg)) + .skip(skipDepth) + .findFirst()) + .map(StackWalker.StackFrame::getDeclaringClass) + .orElse(null); } public Class getCallerClass(final Class anchor) { diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContextFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContextFactory.java index 8ea57a8a5df..b78a3dec80c 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContextFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContextFactory.java @@ -44,4 +44,9 @@ public LoggerContext getContext(final String fqcn, final ClassLoader loader, fin public void removeContext(final LoggerContext removeContext) { // do nothing } + + @Override + public boolean isClassLoaderDependent() { + return false; + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java index 7855098791e..4c1caf3b76b 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java @@ -87,4 +87,17 @@ LoggerContext getContext(String fqcn, ClassLoader loader, Object externalContext * @param context The context to remove. */ void removeContext(LoggerContext context); + + /** + * Determines whether or not this factory and perhaps the underlying + * ContextSelector behavior depend on the callers classloader. + * + * This method should be overridden by implementations, however a default method is provided which always + * returns {@code true} to preserve the old behavior. + * + * @since 2.15.0 + */ + default boolean isClassLoaderDependent() { + return true; + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocator.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocator.java index e83dc12844a..cbe70a92394 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocator.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocator.java @@ -119,6 +119,14 @@ public Class getCallerClass(final int depth) { // migrated from Log4jLoggerFactory @PerformanceSensitive public Class getCallerClass(final String fqcn, final String pkg) { + return getCallerClass(fqcn, pkg, 0); + } + + @PerformanceSensitive + public Class getCallerClass(final String fqcn, final String pkg, final int skipDepth) { + if (skipDepth < 0) { + throw new IllegalArgumentException("skipDepth cannot be negative"); + } boolean next = false; Class clazz; for (int i = 2; null != (clazz = getCallerClass(i)); i++) { @@ -127,7 +135,9 @@ public Class getCallerClass(final String fqcn, final String pkg) { continue; } if (next && clazz.getName().startsWith(pkg)) { - return clazz; + return skipDepth == 0 + ? clazz + : getCallerClass(i + skipDepth); } } // TODO: return Object.class diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocatorUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocatorUtil.java index 35032292e4d..7f1dc7e69c4 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocatorUtil.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocatorUtil.java @@ -48,18 +48,36 @@ public static StackTraceElement getStackTraceElement(final int depth) { return stackLocator.getStackTraceElement(depth + 1); } + /** + * Equivalent to {@link #getCallerClass(String, String)} with an empty {@code pkg}. + */ // migrated from ClassLoaderContextSelector @PerformanceSensitive public static Class getCallerClass(final String fqcn) { return getCallerClass(fqcn, Strings.EMPTY); } - // migrated from Log4jLoggerFactory + /** + * Equivalent to {@link #getCallerClass(String, String, int)} with {@code skipDepth = 0}. + */ @PerformanceSensitive public static Class getCallerClass(final String fqcn, final String pkg) { return stackLocator.getCallerClass(fqcn, pkg); } + /** + * Search for a calling class. + * + * @param fqcn Root class name whose caller to search for. + * @param pkg Package name prefix that must be matched after the {@code fqcn} has been found. + * @param skipDepth Number of stack frames to skip after the {@code fqcn} and {@code pkg} have been matched. + * @return The caller class that was matched, or null if one could not be located. + */ + @PerformanceSensitive + public static Class getCallerClass(final String fqcn, final String pkg, final int skipDepth) { + return stackLocator.getCallerClass(fqcn, pkg, skipDepth); + } + // added for use in LoggerAdapter implementations mainly @PerformanceSensitive public static Class getCallerClass(final Class anchor) { diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/TestLoggerContextFactory.java b/log4j-api/src/test/java/org/apache/logging/log4j/TestLoggerContextFactory.java index 847fedd81c5..04ad4a39e98 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/TestLoggerContextFactory.java +++ b/log4j-api/src/test/java/org/apache/logging/log4j/TestLoggerContextFactory.java @@ -43,4 +43,9 @@ public LoggerContext getContext(final String fqcn, final ClassLoader loader, fin @Override public void removeContext(final LoggerContext context) { } + + @Override + public boolean isClassLoaderDependent() { + return false; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelector.java new file mode 100644 index 00000000000..90ce160365a --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelector.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.impl.ContextAnchor; +import org.apache.logging.log4j.core.selector.ContextSelector; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Returns either this Thread's context or the default {@link AsyncLoggerContext}. + * Single-application instances should prefer this implementation over the {@link AsyncLoggerContextSelector} + * due the the reduced overhead avoiding classloader lookups. + */ +public class BasicAsyncLoggerContextSelector implements ContextSelector { + + private static final AsyncLoggerContext CONTEXT = new AsyncLoggerContext("AsyncDefault"); + + @Override + public void shutdown(String fqcn, ClassLoader loader, boolean currentContext, boolean allContexts) { + LoggerContext ctx = getContext(fqcn, loader, currentContext); + if (ctx != null && ctx.isStarted()) { + ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS); + } + } + + @Override + public boolean hasContext(String fqcn, ClassLoader loader, boolean currentContext) { + LoggerContext ctx = getContext(fqcn, loader, currentContext); + return ctx != null && ctx.isStarted(); + } + + @Override + public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { + final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get(); + return ctx != null ? ctx : CONTEXT; + } + + + @Override + public LoggerContext getContext( + final String fqcn, + final ClassLoader loader, + final boolean currentContext, + final URI configLocation) { + final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get(); + return ctx != null ? ctx : CONTEXT; + } + + @Override + public void removeContext(final LoggerContext context) { + // does not remove anything + } + + @Override + public boolean isClassLoaderDependent() { + return false; + } + + @Override + public List getLoggerContexts() { + return Collections.singletonList(CONTEXT); + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java index 3bee68b5e66..8ac35b476b9 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java @@ -373,6 +373,11 @@ public void removeContext(final org.apache.logging.log4j.spi.LoggerContext conte } } + @Override + public boolean isClassLoaderDependent() { + return selector.isClassLoaderDependent(); + } + @Override public Cancellable addShutdownCallback(final Runnable callback) { return isShutdownHookEnabled() ? shutdownCallbackRegistry.addShutdownCallback(callback) : null; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java index 6aa4dcdfa4c..1c19e361ccb 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java @@ -70,6 +70,11 @@ public void removeContext(final LoggerContext context) { // does not remove anything } + @Override + public boolean isClassLoaderDependent() { + return false; + } + @Override public List getLoggerContexts() { final List list = new ArrayList<>(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java index f7e934fc207..2b5b0fff24d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java @@ -157,6 +157,12 @@ public void removeContext(final LoggerContext context) { } } + @Override + public boolean isClassLoaderDependent() { + // By definition the ClassLoaderContextSelector depends on the callers class loader. + return true; + } + @Override public List getLoggerContexts() { final List list = new ArrayList<>(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java index 727ea3e8e5d..31d9dce989e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java @@ -126,5 +126,15 @@ default LoggerContext getContext(String fqcn, ClassLoader loader, Map.Entry[] CLASSES = new Class[] { ClassLoaderContextSelector.class, BasicContextSelector.class, AsyncLoggerContextSelector.class }; + public static final Class[] CLASSES = new Class[] { + ClassLoaderContextSelector.class, + BasicContextSelector.class, + AsyncLoggerContextSelector.class, + BasicAsyncLoggerContextSelector.class + }; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java index 09bd0a63fe7..181285f021a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java @@ -179,6 +179,11 @@ public void removeContext(final LoggerContext context) { } } + @Override + public boolean isClassLoaderDependent() { + return false; + } + @Override public LoggerContext removeContext(final String name) { return CONTEXT_MAP.remove(name); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorTest.java index 29f21a5f071..19683deb3c8 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorTest.java @@ -65,4 +65,9 @@ public void testContextNameIsAsyncLoggerContextWithClassHashCode() { assertEquals(expectedContextName, context.getName()); } + @Test + public void testDependentOnClassLoader() { + final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(); + assertTrue(selector.isClassLoaderDependent()); + } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerCustomSelectorLocationTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerCustomSelectorLocationTest.java index e5984aac2a4..5fa8da8a3c5 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerCustomSelectorLocationTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerCustomSelectorLocationTest.java @@ -106,5 +106,10 @@ public List getLoggerContexts() { public void removeContext(final LoggerContext context) { // does not remove anything } + + @Override + public boolean isClassLoaderDependent() { + return false; + } } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTest.java index 27c667852f6..fd05419863a 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTest.java @@ -72,6 +72,8 @@ public void testAsyncLogWritesToLog() throws Exception { final String location = "testAsyncLogWritesToLog"; assertTrue("no location", !line1.contains(location)); + + assertTrue(LogManager.getFactory().isClassLoaderDependent()); } // NOTE: only define one @Test method per test class with Async Loggers to prevent spurious failures diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelectorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelectorTest.java new file mode 100644 index 00000000000..823cc3fa5dd --- /dev/null +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelectorTest.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.categories.AsyncLoggers; +import org.apache.logging.log4j.core.LifeCycle; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.util.Constants; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@Category(AsyncLoggers.class) +public class BasicAsyncLoggerContextSelectorTest { + + private static final String FQCN = BasicAsyncLoggerContextSelectorTest.class.getName(); + + @BeforeClass + public static void beforeClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, + BasicAsyncLoggerContextSelector.class.getName()); + } + + @AfterClass + public static void afterClass() { + System.clearProperty(Constants.LOG4J_CONTEXT_SELECTOR); + } + + @Test + public void testContextReturnsAsyncLoggerContext() { + final BasicAsyncLoggerContextSelector selector = new BasicAsyncLoggerContextSelector(); + final LoggerContext context = selector.getContext(FQCN, null, false); + + assertTrue(context instanceof AsyncLoggerContext); + } + + @Test + public void testContext2ReturnsAsyncLoggerContext() { + final BasicAsyncLoggerContextSelector selector = new BasicAsyncLoggerContextSelector(); + final LoggerContext context = selector.getContext(FQCN, null, false, null); + + assertTrue(context instanceof AsyncLoggerContext); + } + + @Test + public void testLoggerContextsReturnsAsyncLoggerContext() { + final BasicAsyncLoggerContextSelector selector = new BasicAsyncLoggerContextSelector(); + + List list = selector.getLoggerContexts(); + assertEquals(1, list.size()); + assertTrue(list.get(0) instanceof AsyncLoggerContext); + + selector.getContext(FQCN, null, false); + + list = selector.getLoggerContexts(); + assertEquals(1, list.size()); + assertTrue(list.get(0) instanceof AsyncLoggerContext); + } + + @Test + public void testContextNameIsAsyncDefault() { + final BasicAsyncLoggerContextSelector selector = new BasicAsyncLoggerContextSelector(); + final LoggerContext context = selector.getContext(FQCN, null, false); + assertEquals("AsyncDefault" , context.getName()); + } + + @Test + public void testDependentOnClassLoader() { + final BasicAsyncLoggerContextSelector selector = new BasicAsyncLoggerContextSelector(); + assertFalse(selector.isClassLoaderDependent()); + } + + @Test + public void testFactoryIsNotDependentOnClassLoader() { + assertFalse(LogManager.getFactory().isClassLoaderDependent()); + } + + @Test + public void testLogManagerShutdown() { + LoggerContext context = (LoggerContext) LogManager.getContext(); + assertEquals(LifeCycle.State.STARTED, context.getState()); + LogManager.shutdown(); + assertEquals(LifeCycle.State.STOPPED, context.getState()); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/selector/BasicContextSelectorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/selector/BasicContextSelectorTest.java index 6e35b722c90..ece4082831d 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/selector/BasicContextSelectorTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/selector/BasicContextSelectorTest.java @@ -25,10 +25,10 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; public final class BasicContextSelectorTest { - @BeforeClass public static void beforeClass() { System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, @@ -47,4 +47,9 @@ public void testLogManagerShutdown() { LogManager.shutdown(); assertEquals(LifeCycle.State.STOPPED, context.getState()); } -} \ No newline at end of file + + @Test + public void testNotDependentOnClassLoader() { + assertFalse(LogManager.getFactory().isClassLoaderDependent()); + } +} diff --git a/log4j-jcl/src/main/java/org/apache/logging/log4j/jcl/LogAdapter.java b/log4j-jcl/src/main/java/org/apache/logging/log4j/jcl/LogAdapter.java index 1b402e2fd8e..94e80f1a742 100644 --- a/log4j-jcl/src/main/java/org/apache/logging/log4j/jcl/LogAdapter.java +++ b/log4j-jcl/src/main/java/org/apache/logging/log4j/jcl/LogAdapter.java @@ -18,6 +18,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.spi.AbstractLoggerAdapter; import org.apache.logging.log4j.spi.LoggerContext; import org.apache.logging.log4j.util.StackLocatorUtil; @@ -36,7 +37,9 @@ protected Log newLogger(final String name, final LoggerContext context) { @Override protected LoggerContext getContext() { - return getContext(StackLocatorUtil.getCallerClass(LogFactory.class)); + return getContext(LogManager.getFactory().isClassLoaderDependent() + ? StackLocatorUtil.getCallerClass(LogFactory.class) + : null); } } diff --git a/log4j-jpl/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerAdapter.java b/log4j-jpl/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerAdapter.java index 3db9b5257b7..d6df19e5244 100644 --- a/log4j-jpl/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerAdapter.java +++ b/log4j-jpl/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerAdapter.java @@ -20,6 +20,7 @@ import java.lang.System.Logger; import java.lang.System.LoggerFinder; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.spi.AbstractLoggerAdapter; import org.apache.logging.log4j.spi.LoggerContext; import org.apache.logging.log4j.util.StackLocatorUtil; @@ -38,6 +39,8 @@ protected Logger newLogger(String name, LoggerContext context) { @Override protected LoggerContext getContext() { - return getContext(StackLocatorUtil.getCallerClass(LoggerFinder.class)); + return getContext(LogManager.getFactory().isClassLoaderDependent() + ? StackLocatorUtil.getCallerClass(LoggerFinder.class) + : null); } } diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/AbstractLoggerAdapter.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/AbstractLoggerAdapter.java index ef533044f5f..9337642f736 100644 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/AbstractLoggerAdapter.java +++ b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/AbstractLoggerAdapter.java @@ -18,6 +18,7 @@ import java.util.logging.Logger; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.spi.LoggerContext; import org.apache.logging.log4j.util.StackLocatorUtil; @@ -31,7 +32,9 @@ public abstract class AbstractLoggerAdapter extends org.apache.logging.log4j.spi @Override protected LoggerContext getContext() { - return getContext(StackLocatorUtil.getCallerClass(java.util.logging.LogManager.class)); + return getContext(LogManager.getFactory().isClassLoaderDependent() + ? StackLocatorUtil.getCallerClass(java.util.logging.LogManager.class) + : null); } } diff --git a/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java b/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java index b54ed9ed09d..f513f0dd267 100644 --- a/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java +++ b/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java @@ -41,8 +41,12 @@ protected Logger newLogger(final String name, final LoggerContext context) { @Override protected LoggerContext getContext() { - final Class anchor = StackLocatorUtil.getCallerClass(FQCN, PACKAGE); - return anchor == null ? LogManager.getContext() : getContext(StackLocatorUtil.getCallerClass(anchor)); + final Class anchor = LogManager.getFactory().isClassLoaderDependent() + ? StackLocatorUtil.getCallerClass(FQCN, PACKAGE, 1) + : null; + return anchor == null + ? LogManager.getContext() + : getContext(anchor); } private LoggerContext validateContext(final LoggerContext context) { if (TO_SLF4J_CONTEXT.equals(context.getClass().getName())) { diff --git a/log4j-slf4j18-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java b/log4j-slf4j18-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java index d0d5ae615d3..3404e0b315c 100644 --- a/log4j-slf4j18-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java +++ b/log4j-slf4j18-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java @@ -47,8 +47,12 @@ protected Logger newLogger(final String name, final LoggerContext context) { @Override protected LoggerContext getContext() { - final Class anchor = StackLocatorUtil.getCallerClass(FQCN, PACKAGE); - return anchor == null ? LogManager.getContext() : getContext(StackLocatorUtil.getCallerClass(anchor)); + final Class anchor = LogManager.getFactory().isClassLoaderDependent() + ? StackLocatorUtil.getCallerClass(FQCN, PACKAGE, 1) + : null; + return anchor == null + ? LogManager.getContext() + : getContext(anchor); } Log4jMarkerFactory getMarkerFactory() { diff --git a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContextFactory.java b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContextFactory.java index 2f206855fdc..7de053a7bdc 100644 --- a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContextFactory.java +++ b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContextFactory.java @@ -28,7 +28,7 @@ */ public class SLF4JLoggerContextFactory implements LoggerContextFactory { private static final StatusLogger LOGGER = StatusLogger.getLogger(); - private static LoggerContext context = new SLF4JLoggerContext(); + private static final LoggerContext context = new SLF4JLoggerContext(); public SLF4JLoggerContextFactory() { // LOG4J2-230, LOG4J2-204 (improve error reporting when misconfigured) @@ -60,4 +60,10 @@ public LoggerContext getContext(final String fqcn, final ClassLoader loader, fin @Override public void removeContext(final LoggerContext ignored) { } + + @Override + public boolean isClassLoaderDependent() { + // context is always used + return false; + } } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 27d87a8702a..825222fcaba 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -42,12 +42,25 @@ Add RepeatPatternConverter. + + Context selectors are aware of their dependence upon the callers ClassLoader, allowing + basic context selectors to avoid the unnecessary overhead of walking the stack to + determine the caller's ClassLoader. + + + Add BasicAsyncLoggerContextSelector equivalent to AsyncLoggerContextSelector for + applications with a single LoggerContext. This selector avoids classloader lookup + overhead incurred by the existing AsyncLoggerContextSelector. + Allow a PatternSelector to be specified on GelfLayout. BasicContextSelector hasContext and shutdown take the default context into account + + Slf4j implementations walk the stack at most once rather than twice to determine the caller's class loader. + diff --git a/src/site/xdoc/manual/async.xml b/src/site/xdoc/manual/async.xml index 699ec97f5b1..e704f78c622 100644 --- a/src/site/xdoc/manual/async.xml +++ b/src/site/xdoc/manual/async.xml @@ -133,7 +133,8 @@

This is simplest to configure and gives the best performance. To make all loggers asynchronous, add the disruptor jar to the classpath and set the system property log4j2.contextSelector - to org.apache.logging.log4j.core.async.AsyncLoggerContextSelector. + to org.apache.logging.log4j.core.async.AsyncLoggerContextSelector or + org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector.

By default, location is not passed to the I/O thread by @@ -147,6 +148,8 @@ @@ -165,9 +168,10 @@ ]]>

- When AsyncLoggerContextSelector is used to make all loggers asynchronous, make sure to use normal + When AsyncLoggerContextSelector or BasicAsyncLoggerContextSelector is used to make all + loggers asynchronous, make sure to use normal <root> and <logger> elements in the configuration. The - AsyncLoggerContextSelector will ensure that all loggers are asynchronous, using a mechanism + context selector will ensure that all loggers are asynchronous, using a mechanism that is different from what happens when you configure <asyncRoot> or <asyncLogger>. The latter elements are intended for mixing async with sync loggers. If you use both mechanisms diff --git a/src/site/xdoc/manual/configuration.xml.vm b/src/site/xdoc/manual/configuration.xml.vm index 72272c4f9d7..dbcae155860 100644 --- a/src/site/xdoc/manual/configuration.xml.vm +++ b/src/site/xdoc/manual/configuration.xml.vm @@ -1740,6 +1740,7 @@ public class AwesomeTest { Available context selector implementation classes:
org.apache.logging.log4j.core.async .AsyncLoggerContextSelector - makes all loggers asynchronous.
+ org.apache.logging.log4j.core.async .BasicAsyncLoggerContextSelector - makes all loggers asynchronous using a single shared AsyncLoggerContext.
org.apache.logging.log4j.core.selector .BasicContextSelector - creates a single shared LoggerContext.
org.apache.logging.log4j.core.selector .ClassLoaderContextSelector - separate LoggerContexts for each web application.
org.apache.logging.log4j.core.selector .JndiContextSelector - use JNDI to locate each web application's LoggerContext.