Skip to content

Commit 3577d1d

Browse files
committed
Centralize initialization in Provider class
We move the code responsible for the instantiation of `LoggerContextFactory` and `ThreadContextMap` from the static entry points to the logging system (`LogManager` and `ThreadContext`) to the `Provider` class. The `Provider` class is instantiated using `ServiceLoader`, so `log4j-core` 2.x and 3.x can reimplement the initialization process according to their own rules. E.g. `log4j-core` 3.x can use the DI to create an instance of `LoggerContextFactory` and `ThreadContextMap`. The following modification were performed: * a **new** system property `log4j.provider` was introduced, * the old `log4j2.loggerContextFactory` has been deprecated and revised: if set it selects the first provider that uses the given `LoggerContextFactory`. Therefore it selects now both the context factory and thread context map implementations, * private static configuration values were removed from `ThreadContextMap` implementations, helping test parallelisation, * a distinct `NoOpThreadContextStack` implementation has been introduced.
1 parent 6a2c179 commit 3577d1d

File tree

16 files changed

+676
-432
lines changed

16 files changed

+676
-432
lines changed

log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@
2424
*/
2525
public class TestProvider extends Provider {
2626
public TestProvider() {
27-
super(0, "2.6.0", TestLoggerContextFactory.class);
27+
super(10, "2.6.0", TestLoggerContextFactory.class);
2828
}
2929
}

log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,8 @@
2424

2525
import java.util.HashMap;
2626
import java.util.Map;
27-
import org.apache.logging.log4j.test.junit.InitializesThreadContext;
28-
import org.apache.logging.log4j.test.junit.SetTestProperty;
2927
import org.apache.logging.log4j.test.junit.UsingThreadContextMap;
3028
import org.junit.jupiter.api.Test;
31-
import org.junitpioneer.jupiter.ClearSystemProperty;
3229

3330
/**
3431
* Tests the {@code DefaultThreadContextMap} class.
@@ -217,26 +214,4 @@ public void testToStringShowsMapContext() {
217214
map.put("key2", "value2");
218215
assertEquals("{key2=value2}", map.toString());
219216
}
220-
221-
@Test
222-
@ClearSystemProperty(key = DefaultThreadContextMap.INHERITABLE_MAP)
223-
@InitializesThreadContext
224-
public void testThreadLocalNotInheritableByDefault() {
225-
ThreadContextMapFactory.init();
226-
final ThreadLocal<Map<String, String>> threadLocal = DefaultThreadContextMap.createThreadLocalMap(true);
227-
assertFalse(threadLocal instanceof InheritableThreadLocal<?>);
228-
}
229-
230-
@Test
231-
@SetTestProperty(key = DefaultThreadContextMap.INHERITABLE_MAP, value = "true")
232-
@InitializesThreadContext
233-
public void testThreadLocalInheritableIfConfigured() {
234-
ThreadContextMapFactory.init();
235-
try {
236-
final ThreadLocal<Map<String, String>> threadLocal = DefaultThreadContextMap.createThreadLocalMap(true);
237-
assertTrue(threadLocal instanceof InheritableThreadLocal<?>);
238-
} finally {
239-
System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP);
240-
}
241-
}
242217
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.logging.log4j.spi;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
import java.time.Duration;
22+
import java.util.Properties;
23+
import java.util.concurrent.ExecutorService;
24+
import java.util.concurrent.Executors;
25+
import java.util.stream.Stream;
26+
import org.apache.logging.log4j.util.PropertiesUtil;
27+
import org.junit.jupiter.params.ParameterizedTest;
28+
import org.junit.jupiter.params.provider.MethodSource;
29+
30+
class ThreadContextMapTest {
31+
32+
static Stream<ThreadContextMap> defaultMaps() {
33+
return Stream.of(
34+
new DefaultThreadContextMap(),
35+
new CopyOnWriteSortedArrayThreadContextMap(),
36+
new GarbageFreeSortedArrayThreadContextMap());
37+
}
38+
39+
static Stream<ThreadContextMap> inheritableMaps() {
40+
final Properties props = new Properties();
41+
props.setProperty("log4j2.isThreadContextMapInheritable", "true");
42+
final PropertiesUtil util = new PropertiesUtil(props);
43+
return Stream.of(
44+
new DefaultThreadContextMap(true, util),
45+
new CopyOnWriteSortedArrayThreadContextMap(util),
46+
new GarbageFreeSortedArrayThreadContextMap(util));
47+
}
48+
49+
@ParameterizedTest
50+
@MethodSource("defaultMaps")
51+
void threadLocalNotInheritableByDefault(final ThreadContextMap contextMap) {
52+
contextMap.put("key", "threadLocalNotInheritableByDefault");
53+
final ExecutorService executorService = Executors.newSingleThreadExecutor();
54+
try {
55+
assertThat(executorService.submit(() -> contextMap.get("key")))
56+
.succeedsWithin(Duration.ofSeconds(1))
57+
.isEqualTo(null);
58+
} finally {
59+
executorService.shutdown();
60+
}
61+
}
62+
63+
@ParameterizedTest
64+
@MethodSource("inheritableMaps")
65+
void threadLocalInheritableIfConfigured(final ThreadContextMap contextMap) {
66+
contextMap.put("key", "threadLocalInheritableIfConfigured");
67+
final ExecutorService executorService = Executors.newSingleThreadExecutor();
68+
try {
69+
assertThat(executorService.submit(() -> contextMap.get("key")))
70+
.succeedsWithin(Duration.ofSeconds(1))
71+
.isEqualTo("threadLocalInheritableIfConfigured");
72+
} finally {
73+
executorService.shutdown();
74+
}
75+
}
76+
}

log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,61 @@
1616
*/
1717
package org.apache.logging.log4j.util;
1818

19-
import static org.junit.jupiter.api.Assertions.assertTrue;
20-
21-
import java.io.File;
22-
import java.net.URL;
23-
import java.net.URLClassLoader;
24-
import org.apache.logging.log4j.LogManager;
25-
import org.apache.logging.log4j.spi.LoggerContext;
26-
import org.apache.logging.log4j.test.TestLoggerContext;
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
import java.util.Properties;
22+
import org.apache.logging.log4j.TestProvider;
23+
import org.apache.logging.log4j.test.TestLoggerContextFactory;
2724
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.api.parallel.Execution;
26+
import org.junit.jupiter.api.parallel.ExecutionMode;
27+
28+
@Execution(ExecutionMode.CONCURRENT)
29+
class ProviderUtilTest {
2830

29-
public class ProviderUtilTest {
31+
/*
32+
* Force initialization of ProviderUtil#PROVIDERS
33+
*/
34+
static {
35+
ProviderUtil.lazyInit();
36+
}
37+
38+
@Test
39+
void should_select_provider_with_highest_priority() {
40+
final PropertiesUtil properties = new PropertiesUtil(new Properties());
41+
assertThat(ProviderUtil.selectProvider(properties))
42+
.as("check selected provider")
43+
.isInstanceOf(TestProvider.class);
44+
}
45+
46+
@Test
47+
void should_recognize_log4j_provider_property() {
48+
final Properties map = new Properties();
49+
map.setProperty("log4j.provider", LocalProvider.class.getName());
50+
final PropertiesUtil properties = new PropertiesUtil(map);
51+
assertThat(ProviderUtil.selectProvider(properties))
52+
.as("check selected provider")
53+
.isInstanceOf(LocalProvider.class);
54+
}
3055

3156
@Test
32-
public void complexTest() throws Exception {
33-
final File file = new File("target/classes");
34-
final ClassLoader classLoader =
35-
new URLClassLoader(new URL[] {file.toURI().toURL()});
36-
final Worker worker = new Worker();
37-
worker.setContextClassLoader(classLoader);
38-
worker.start();
39-
worker.join();
40-
assertTrue(worker.context instanceof TestLoggerContext, "Incorrect LoggerContext");
57+
void should_recognize_log4j_factory_property() {
58+
final Properties map = new Properties();
59+
map.setProperty("log4j2.loggerContextFactory", LocalLoggerContextFactory.class.getName());
60+
final PropertiesUtil properties = new PropertiesUtil(map);
61+
assertThat(ProviderUtil.selectProvider(properties).getLoggerContextFactory())
62+
.as("check selected logger context factory")
63+
.isInstanceOf(LocalLoggerContextFactory.class);
4164
}
4265

43-
private static class Worker extends Thread {
44-
LoggerContext context = null;
66+
public static class LocalLoggerContextFactory extends TestLoggerContextFactory {}
4567

46-
@Override
47-
public void run() {
48-
context = LogManager.getContext(false);
68+
/**
69+
* A provider with a smaller priority than {@link org.apache.logging.log4j.TestProvider}.
70+
*/
71+
public static class LocalProvider extends org.apache.logging.log4j.spi.Provider {
72+
public LocalProvider() {
73+
super(0, CURRENT_VERSION, LocalLoggerContextFactory.class);
4974
}
5075
}
5176
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
org.apache.logging.log4j.TestProvider
1+
org.apache.logging.log4j.TestProvider
2+
org.apache.logging.log4j.util.ProviderUtilTest.LocalProvider

log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java

Lines changed: 5 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,14 @@
1717
package org.apache.logging.log4j;
1818

1919
import java.net.URI;
20-
import java.util.Map;
21-
import java.util.SortedMap;
22-
import java.util.TreeMap;
2320
import org.apache.logging.log4j.internal.LogManagerStatus;
2421
import org.apache.logging.log4j.message.MessageFactory;
2522
import org.apache.logging.log4j.message.StringFormatterMessageFactory;
2623
import org.apache.logging.log4j.simple.SimpleLoggerContextFactory;
2724
import org.apache.logging.log4j.spi.LoggerContext;
2825
import org.apache.logging.log4j.spi.LoggerContextFactory;
29-
import org.apache.logging.log4j.spi.Provider;
3026
import org.apache.logging.log4j.spi.Terminable;
3127
import org.apache.logging.log4j.status.StatusLogger;
32-
import org.apache.logging.log4j.util.LoaderUtil;
33-
import org.apache.logging.log4j.util.PropertiesUtil;
3428
import org.apache.logging.log4j.util.ProviderUtil;
3529
import org.apache.logging.log4j.util.StackLocatorUtil;
3630
import org.apache.logging.log4j.util.Strings;
@@ -48,8 +42,10 @@ public class LogManager {
4842

4943
/**
5044
* Log4j property to set to the fully qualified class name of a custom implementation of
51-
* {@link org.apache.logging.log4j.spi.LoggerContextFactory}.
45+
* {@link LoggerContextFactory}.
46+
* @deprecated Replaced since 2.24.0 with {@value org.apache.logging.log4j.spi.Provider#PROVIDER_PROPERTY_NAME}.
5247
*/
48+
@Deprecated
5349
public static final String FACTORY_PROPERTY_NAME = "log4j2.loggerContextFactory";
5450

5551
/**
@@ -62,69 +58,14 @@ public class LogManager {
6258
// for convenience
6359
private static final String FQCN = LogManager.class.getName();
6460

65-
private static volatile LoggerContextFactory factory;
61+
private static volatile LoggerContextFactory factory =
62+
ProviderUtil.getProvider().getLoggerContextFactory();
6663

6764
/*
6865
* Scans the classpath to find all logging implementation. Currently, only one will be used but this could be
6966
* extended to allow multiple implementations to be used.
7067
*/
7168
static {
72-
// Shortcut binding to force a specific logging implementation.
73-
final PropertiesUtil managerProps = PropertiesUtil.getProperties();
74-
final String factoryClassName = managerProps.getStringProperty(FACTORY_PROPERTY_NAME);
75-
if (factoryClassName != null) {
76-
try {
77-
factory = LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class);
78-
} catch (final ClassNotFoundException cnfe) {
79-
LOGGER.error("Unable to locate configured LoggerContextFactory {}", factoryClassName);
80-
} catch (final Exception ex) {
81-
LOGGER.error("Unable to create configured LoggerContextFactory {}", factoryClassName, ex);
82-
}
83-
}
84-
85-
if (factory == null) {
86-
final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>();
87-
// note that the following initial call to ProviderUtil may block until a Provider has been installed when
88-
// running in an OSGi environment
89-
if (ProviderUtil.hasProviders()) {
90-
for (final Provider provider : ProviderUtil.getProviders()) {
91-
final Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory();
92-
if (factoryClass != null) {
93-
try {
94-
factories.put(provider.getPriority(), LoaderUtil.newInstanceOf(factoryClass));
95-
} catch (final Exception e) {
96-
LOGGER.error(
97-
"Unable to create class {} specified in provider URL {}",
98-
factoryClass.getName(),
99-
provider.getUrl(),
100-
e);
101-
}
102-
}
103-
}
104-
105-
if (factories.isEmpty()) {
106-
LOGGER.error(
107-
"Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
108-
factory = SimpleLoggerContextFactory.INSTANCE;
109-
} else if (factories.size() == 1) {
110-
factory = factories.get(factories.lastKey());
111-
} else {
112-
final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n");
113-
for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) {
114-
sb.append("Factory: ")
115-
.append(entry.getValue().getClass().getName());
116-
sb.append(", Weighting: ").append(entry.getKey()).append('\n');
117-
}
118-
factory = factories.get(factories.lastKey());
119-
sb.append("Using factory: ").append(factory.getClass().getName());
120-
LOGGER.warn(sb.toString());
121-
}
122-
} else {
123-
LOGGER.error(
124-
"Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
125-
factory = SimpleLoggerContextFactory.INSTANCE;
126-
}
127-
}
12869
LogManagerStatus.setInitialized(true);
12970
}
13071

0 commit comments

Comments
 (0)