LOG4J2-2940: Implement BasicAsyncLoggerContextSelector

The BasicAsyncLoggerContextSelector is equivalent to the
AsyncLoggerContextSelector without ClassLoader introspection
and associated overhead.
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 0000000..90ce160
--- /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<LoggerContext> getLoggerContexts() {

+        return Collections.singletonList(CONTEXT);

+    }

+

+}

diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/CoreContextSelectors.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/CoreContextSelectors.java
index 1077e5a..2ee6b0d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/CoreContextSelectors.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/CoreContextSelectors.java
@@ -17,9 +17,15 @@
 package org.apache.logging.log4j.core.selector;

 

 import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;

+import org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector;

 

 public class CoreContextSelectors {

 

-    public static final Class<?>[] 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/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 0000000..823cc3f
--- /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<LoggerContext> 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/src/changes/changes.xml b/src/changes/changes.xml
index 3d8c3d8..3644802 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -186,6 +186,11 @@
         basic context selectors to avoid the unnecessary overhead of walking the stack to
         determine the caller's ClassLoader.
       </action>
+      <action issue="LOG4J2-2940" dev="ckozak" type="add">
+        Add BasicAsyncLoggerContextSelector equivalent to AsyncLoggerContextSelector for
+        applications with a single LoggerContext. This selector avoids classloader lookup
+        overhead incurred by the existing AsyncLoggerContextSelector.
+      </action>
       <action issue="LOG4J2-3041" dev="rgoers" type="update">
         Allow a PatternSelector to be specified on GelfLayout.
       </action>