This is an automated email from the ASF dual-hosted git repository.

pkarwasz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 3067dca1e34795964e72b76b40bad75954010ff4
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Sun Aug 28 23:05:28 2022 +0200

    [LOG4J2-3583] Implements stack-valued MDC
    
    This PR implements SLF4J-531 by storing the result of `pushByKey(key,
    value)` in the usual thread context map and restoring the previous
    value, when a `popByKey(key)` call occurs.
---
 log4j-slf4j20-impl/pom.xml                         |  8 +++
 .../org/apache/logging/slf4j/Log4jMDCAdapter.java  | 84 +++++++++++++++++++---
 .../apache/logging/slf4j/Log4jMDCAdapterTest.java  | 63 ++++++++++++++++
 3 files changed, 147 insertions(+), 8 deletions(-)

diff --git a/log4j-slf4j20-impl/pom.xml b/log4j-slf4j20-impl/pom.xml
index 5f72a38614..d52ee1b592 100644
--- a/log4j-slf4j20-impl/pom.xml
+++ b/log4j-slf4j20-impl/pom.xml
@@ -91,6 +91,14 @@
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter-engine</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-params</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+    </dependency>
   </dependencies>
   <build>
     <plugins>
diff --git 
a/log4j-slf4j20-impl/src/main/java/org/apache/logging/slf4j/Log4jMDCAdapter.java
 
b/log4j-slf4j20-impl/src/main/java/org/apache/logging/slf4j/Log4jMDCAdapter.java
index 11f52de5b0..71e5a0463a 100644
--- 
a/log4j-slf4j20-impl/src/main/java/org/apache/logging/slf4j/Log4jMDCAdapter.java
+++ 
b/log4j-slf4j20-impl/src/main/java/org/apache/logging/slf4j/Log4jMDCAdapter.java
@@ -16,10 +16,16 @@
  */
 package org.apache.logging.slf4j;
 
+import java.util.ArrayDeque;
 import java.util.Deque;
+import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
+import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.ThreadContext.ContextStack;
+import org.apache.logging.log4j.status.StatusLogger;
 import org.slf4j.spi.MDCAdapter;
 
 /**
@@ -27,6 +33,10 @@ import org.slf4j.spi.MDCAdapter;
  */
 public class Log4jMDCAdapter implements MDCAdapter {
 
+    private static Logger LOGGER = StatusLogger.getLogger();
+
+    private final ThreadLocalMapOfStacks mapOfStacks = new 
ThreadLocalMapOfStacks();
+
     @Override
     public void put(final String key, final String val) {
         ThreadContext.put(key, val);
@@ -53,31 +63,89 @@ public class Log4jMDCAdapter implements MDCAdapter {
     }
 
     @Override
-    @SuppressWarnings("unchecked") // nothing we can do about this, restricted 
by SLF4J API
-    public void setContextMap(@SuppressWarnings("rawtypes") final Map map) {
+    public void setContextMap(final Map<String, String> map) {
         ThreadContext.clearMap();
         ThreadContext.putAll(map);
     }
 
     @Override
     public void pushByKey(String key, String value) {
-        // not implemented yet
+        if (key == null) {
+            ThreadContext.push(value);
+        } else {
+            final String oldValue = mapOfStacks.peekByKey(key);
+            if (!Objects.equals(ThreadContext.get(key), oldValue)) {
+                LOGGER.warn("The key {} was used in both the string and 
stack-valued MDC.", key);
+            }
+            mapOfStacks.pushByKey(key, value);
+            ThreadContext.put(key, value);
+        }
     }
 
     @Override
     public String popByKey(String key) {
-        // not implemented yet
-        return null;
+        if (key == null) {
+            return ThreadContext.getDepth() > 0 ? ThreadContext.pop() : null;
+        }
+        final String value = mapOfStacks.popByKey(key);
+        if (!Objects.equals(ThreadContext.get(key), value)) {
+            LOGGER.warn("The key {} was used in both the string and 
stack-valued MDC.", key);
+        }
+        ThreadContext.put(key, mapOfStacks.peekByKey(key));
+        return value;
     }
 
     @Override
     public Deque<String> getCopyOfDequeByKey(String key) {
-        // not implemented yet
-        return null;
+        if (key == null) {
+            final ContextStack stack = ThreadContext.getImmutableStack();
+            final Deque<String> copy = new ArrayDeque<>(stack.size());
+            stack.forEach(copy::push);
+            return copy;
+        }
+        return mapOfStacks.getCopyOfDequeByKey(key);
     }
 
     @Override
     public void clearDequeByKey(String key) {
-        // not implemented yet
+        if (key == null) {
+            ThreadContext.clearStack();
+        } else {
+            mapOfStacks.clearByKey(key);
+            ThreadContext.put(key, null);
+        }
+    }
+
+    private static class ThreadLocalMapOfStacks {
+
+        private final ThreadLocal<Map<String, Deque<String>>> tlMapOfStacks = 
ThreadLocal.withInitial(HashMap::new);
+
+        public void pushByKey(String key, String value) {
+            tlMapOfStacks.get()
+                    .computeIfAbsent(key, ignored -> new ArrayDeque<>())
+                    .push(value);
+        }
+
+        public String popByKey(String key) {
+            final Deque<String> deque = tlMapOfStacks.get().get(key);
+            return deque != null ? deque.poll() : null;
+        }
+
+        public Deque<String> getCopyOfDequeByKey(String key) {
+            final Deque<String> deque = tlMapOfStacks.get().get(key);
+            return deque != null ? new ArrayDeque<>(deque) : null;
+        }
+
+        public void clearByKey(String key) {
+            final Deque<String> deque = tlMapOfStacks.get().get(key);
+            if (deque != null) {
+                deque.clear();
+            }
+        }
+
+        public String peekByKey(String key) {
+            final Deque<String> deque = tlMapOfStacks.get().get(key);
+            return deque != null ? deque.peek() : null;
+        }
     }
 }
diff --git 
a/log4j-slf4j20-impl/src/test/java/org/apache/logging/slf4j/Log4jMDCAdapterTest.java
 
b/log4j-slf4j20-impl/src/test/java/org/apache/logging/slf4j/Log4jMDCAdapterTest.java
new file mode 100644
index 0000000000..4fa9038be1
--- /dev/null
+++ 
b/log4j-slf4j20-impl/src/test/java/org/apache/logging/slf4j/Log4jMDCAdapterTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.slf4j;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class Log4jMDCAdapterTest {
+
+    private static final Log4jMDCAdapter MDC_ADAPTER = new Log4jMDCAdapter();
+    private static final String KEY = "Log4j2";
+
+    private static Deque<String> createDeque(int size) {
+        final Deque<String> result = new ArrayDeque<>(size);
+        IntStream.range(0, 
size).mapToObj(Integer::toString).forEach(result::addLast);
+        return result;
+    }
+
+    private static Deque<String> popDeque(String key) {
+        final Deque<String> result = new ArrayDeque<>();
+        String value;
+        while ((value = MDC_ADAPTER.popByKey(key)) != null) {
+            result.addLast(value);
+        }
+        return result;
+    }
+
+    static Stream<String> keys() {
+        return Stream.of(KEY, "", null);
+    }
+
+    @ParameterizedTest
+    @MethodSource("keys")
+    public void testPushPopByKey(final String key) {
+        MDC_ADAPTER.clearDequeByKey(key);
+        final Deque<String> expectedValues = createDeque(100);
+        expectedValues.descendingIterator().forEachRemaining(v -> 
MDC_ADAPTER.pushByKey(key, v));
+        
assertThat(MDC_ADAPTER.getCopyOfDequeByKey(key)).containsExactlyElementsOf(expectedValues);
+        assertThat(popDeque(key)).containsExactlyElementsOf(expectedValues);
+    }
+
+}

Reply via email to