Skip to content

Commit f43b42d

Browse files
committed
Move ThreadContextMap implementations to log4j-core
We move `ThreadContextMap` alternative implementations to `log4j-core`. These thread context maps are not useful to other `log4j-api` implementations: * `log4j-to-slf4j` delegates everything to SLF4J's `MDCAdapter`, * `log4j-to-jul` uses the no-op context map.
1 parent 8c01341 commit f43b42d

30 files changed

+665
-919
lines changed

log4j-api-test/src/main/java/org/apache/logging/log4j/test/ThreadContextUtilityClass.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,4 @@ public static void testPut() {
112112
ThreadContext.put("testKey", "testValue");
113113
assertEquals("testValue", ThreadContext.get("testKey"));
114114
}
115-
116-
public static void reset() {
117-
ThreadContext.init();
118-
}
119115
}

log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/InitializesThreadContext.java

Lines changed: 0 additions & 39 deletions
This file was deleted.

log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextInitializer.java

Lines changed: 0 additions & 58 deletions
This file was deleted.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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.test.spi;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
import java.time.Duration;
22+
import java.util.concurrent.ExecutorService;
23+
import java.util.concurrent.Executors;
24+
import org.apache.logging.log4j.spi.ThreadContextMap;
25+
26+
/**
27+
* Provides a set of utility methods to test implementations of {@link ThreadContextMap}.
28+
*/
29+
public abstract class AbstractThreadContextMapTest {
30+
31+
private static final String KEY = "key";
32+
33+
/**
34+
* Implementations SHOULD not propagate the context to newly created threads by default.
35+
*
36+
* @param contextMap A {@link ThreadContextMap implementation}.
37+
*/
38+
protected static void assertThreadLocalNotInheritable(final ThreadContextMap contextMap) {
39+
contextMap.put(KEY, "threadLocalNotInheritableByDefault");
40+
verifyThreadContextValueFromANewThread(contextMap, null);
41+
}
42+
43+
/**
44+
* Implementations MAY offer a configuration that propagates the context to newly created threads.
45+
*
46+
* @param contextMap A {@link ThreadContextMap implementation}.
47+
*/
48+
protected static void assertThreadLocalInheritable(final ThreadContextMap contextMap) {
49+
contextMap.put(KEY, "threadLocalInheritableIfConfigured");
50+
verifyThreadContextValueFromANewThread(contextMap, "threadLocalInheritableIfConfigured");
51+
}
52+
53+
private static void verifyThreadContextValueFromANewThread(
54+
final ThreadContextMap contextMap, final String expected) {
55+
final ExecutorService executorService = Executors.newSingleThreadExecutor();
56+
try {
57+
assertThat(executorService.submit(() -> contextMap.get(KEY)))
58+
.succeedsWithin(Duration.ofSeconds(1))
59+
.isEqualTo(expected);
60+
} finally {
61+
executorService.shutdown();
62+
}
63+
}
64+
}

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

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,18 @@
2323

2424
import org.apache.logging.log4j.spi.DefaultThreadContextMap;
2525
import org.apache.logging.log4j.test.ThreadContextUtilityClass;
26-
import org.apache.logging.log4j.test.junit.InitializesThreadContext;
2726
import org.apache.logging.log4j.test.junit.SetTestProperty;
2827
import org.apache.logging.log4j.test.junit.UsingThreadContextMap;
2928
import org.apache.logging.log4j.test.junit.UsingThreadContextStack;
3029
import org.junit.jupiter.api.AfterAll;
3130
import org.junit.jupiter.api.BeforeAll;
3231
import org.junit.jupiter.api.Tag;
3332
import org.junit.jupiter.api.Test;
34-
import org.junitpioneer.jupiter.SetSystemProperty;
3533

3634
/**
3735
* Tests {@link ThreadContext}.
3836
*/
3937
@SetTestProperty(key = DefaultThreadContextMap.INHERITABLE_MAP, value = "true")
40-
@InitializesThreadContext
4138
@UsingThreadContextMap
4239
@UsingThreadContextStack
4340
public class ThreadContextInheritanceTest {
@@ -63,31 +60,6 @@ public void testPush() {
6360
assertEquals(ThreadContext.pop(), "Hello", "Incorrect simple stack value");
6461
}
6562

66-
@Test
67-
@SetSystemProperty(key = DefaultThreadContextMap.INHERITABLE_MAP, value = "true")
68-
@InitializesThreadContext
69-
public void testInheritanceSwitchedOn() throws Exception {
70-
System.setProperty(DefaultThreadContextMap.INHERITABLE_MAP, "true");
71-
try {
72-
ThreadContext.clearMap();
73-
ThreadContext.put("Greeting", "Hello");
74-
StringBuilder sb = new StringBuilder();
75-
TestThread thread = new TestThread(sb);
76-
thread.start();
77-
thread.join();
78-
String str = sb.toString();
79-
assertEquals("Hello", str, "Unexpected ThreadContext value. Expected Hello. Actual " + str);
80-
sb = new StringBuilder();
81-
thread = new TestThread(sb);
82-
thread.start();
83-
thread.join();
84-
str = sb.toString();
85-
assertEquals("Hello", str, "Unexpected ThreadContext value. Expected Hello. Actual " + str);
86-
} finally {
87-
System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP);
88-
}
89-
}
90-
9163
@Test
9264
@Tag("performance")
9365
public void perfTest() {

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

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,17 @@
2424

2525
import java.util.HashMap;
2626
import java.util.Map;
27+
import java.util.Properties;
2728
import org.apache.logging.log4j.test.junit.UsingThreadContextMap;
29+
import org.apache.logging.log4j.test.spi.AbstractThreadContextMapTest;
30+
import org.apache.logging.log4j.util.PropertiesUtil;
2831
import org.junit.jupiter.api.Test;
2932

3033
/**
3134
* Tests the {@code DefaultThreadContextMap} class.
3235
*/
3336
@UsingThreadContextMap
34-
public class DefaultThreadContextMapTest {
35-
36-
@Test
37-
public void testEqualsVsSameKind() {
38-
final DefaultThreadContextMap map1 = createMap();
39-
final DefaultThreadContextMap map2 = createMap();
40-
assertEquals(map1, map1);
41-
assertEquals(map2, map2);
42-
assertEquals(map1, map2);
43-
assertEquals(map2, map1);
44-
}
45-
46-
@Test
47-
public void testHashCodeVsSameKind() {
48-
final DefaultThreadContextMap map1 = createMap();
49-
final DefaultThreadContextMap map2 = createMap();
50-
assertEquals(map1.hashCode(), map2.hashCode());
51-
}
37+
class DefaultThreadContextMapTest extends AbstractThreadContextMapTest {
5238

5339
@Test
5440
public void testDoesNothingIfConstructedWithUseMapIsFalse() {
@@ -214,4 +200,17 @@ public void testToStringShowsMapContext() {
214200
map.put("key2", "value2");
215201
assertEquals("{key2=value2}", map.toString());
216202
}
203+
204+
@Test
205+
void threadLocalNotInheritableByDefault() {
206+
assertThreadLocalNotInheritable(new DefaultThreadContextMap());
207+
}
208+
209+
@Test
210+
void threadLocalInheritableIfConfigured() {
211+
final Properties props = new Properties();
212+
props.setProperty("log4j2.isThreadContextMapInheritable", "true");
213+
final PropertiesUtil util = new PropertiesUtil(props);
214+
assertThreadLocalInheritable(new DefaultThreadContextMap(true, util));
215+
}
217216
}

log4j-api/pom.xml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@
4646
<bnd-module-name>org.apache.logging.log4j</bnd-module-name>
4747
<bnd-extra-package-options>
4848
<!-- Not exported by most OSGi system bundles, hence we use the system classloader to load `sun.reflect.Reflection` -->
49-
!sun.reflect
49+
!sun.reflect,
50+
<!-- Annotations only -->
51+
org.jspecify.*;resolution:=optional
5052
</bnd-extra-package-options>
5153
<bnd-extra-module-options>
5254
<!-- Used in StringBuilders through reflection -->
@@ -57,6 +59,13 @@
5759

5860
</properties>
5961
<dependencies>
62+
63+
<dependency>
64+
<groupId>org.jspecify</groupId>
65+
<artifactId>jspecify</artifactId>
66+
<scope>provided</scope>
67+
</dependency>
68+
6069
<dependency>
6170
<groupId>org.osgi</groupId>
6271
<artifactId>org.osgi.core</artifactId>

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

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,12 @@
2323
import java.util.Iterator;
2424
import java.util.List;
2525
import java.util.Map;
26-
import org.apache.logging.log4j.internal.map.StringArrayThreadContextMap;
2726
import org.apache.logging.log4j.message.ParameterizedMessage;
28-
import org.apache.logging.log4j.spi.CleanableThreadContextMap;
2927
import org.apache.logging.log4j.spi.DefaultThreadContextMap;
3028
import org.apache.logging.log4j.spi.DefaultThreadContextStack;
3129
import org.apache.logging.log4j.spi.MutableThreadContextStack;
3230
import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
3331
import org.apache.logging.log4j.spi.ThreadContextMap;
34-
import org.apache.logging.log4j.spi.ThreadContextMap2;
3532
import org.apache.logging.log4j.spi.ThreadContextMapFactory;
3633
import org.apache.logging.log4j.spi.ThreadContextStack;
3734
import org.apache.logging.log4j.util.PropertiesUtil;
@@ -268,21 +265,11 @@ public static void putIfNull(final String key, final String value) {
268265
*
269266
* <p>If the current thread does not have a context map it is
270267
* created as a side effect.</p>
271-
* @param m The map.
268+
* @param map The map.
272269
* @since 2.7
273270
*/
274-
public static void putAll(final Map<String, String> m) {
275-
if (contextMap instanceof ThreadContextMap2) {
276-
((ThreadContextMap2) contextMap).putAll(m);
277-
} else if (contextMap instanceof DefaultThreadContextMap) {
278-
((DefaultThreadContextMap) contextMap).putAll(m);
279-
} else if (contextMap instanceof StringArrayThreadContextMap) {
280-
((StringArrayThreadContextMap) contextMap).putAll(m);
281-
} else {
282-
for (final Map.Entry<String, String> entry : m.entrySet()) {
283-
contextMap.put(entry.getKey(), entry.getValue());
284-
}
285-
}
271+
public static void putAll(final Map<String, String> map) {
272+
contextMap.putAll(map);
286273
}
287274

288275
/**
@@ -316,17 +303,7 @@ public static void remove(final String key) {
316303
* @since 2.8
317304
*/
318305
public static void removeAll(final Iterable<String> keys) {
319-
if (contextMap instanceof CleanableThreadContextMap) {
320-
((CleanableThreadContextMap) contextMap).removeAll(keys);
321-
} else if (contextMap instanceof DefaultThreadContextMap) {
322-
((DefaultThreadContextMap) contextMap).removeAll(keys);
323-
} else if (contextMap instanceof StringArrayThreadContextMap) {
324-
((StringArrayThreadContextMap) contextMap).removeAll(keys);
325-
} else {
326-
for (final String key : keys) {
327-
contextMap.remove(key);
328-
}
329-
}
306+
contextMap.removeAll(keys);
330307
}
331308

332309
/**

0 commit comments

Comments
 (0)