You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by gg...@apache.org on 2016/04/04 19:06:50 UTC

logging-log4j2 git commit: [LOG4J2-1348] Add an AutoCloseable ThreadContext class: CloseableThreadContext.

Repository: logging-log4j2
Updated Branches:
  refs/heads/master af2798724 -> cc86e96a0


[LOG4J2-1348] Add an AutoCloseable ThreadContext class:
CloseableThreadContext.

Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/cc86e96a
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/cc86e96a
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/cc86e96a

Branch: refs/heads/master
Commit: cc86e96a0438485a29f9d6e7d0ba0bd2832f5973
Parents: af27987
Author: ggregory <gg...@apache.org>
Authored: Mon Apr 4 10:06:47 2016 -0700
Committer: ggregory <gg...@apache.org>
Committed: Mon Apr 4 10:06:47 2016 -0700

----------------------------------------------------------------------
 .../logging/log4j/CloseableThreadContext.java   | 154 +++++++++++++++++++
 .../log4j/CloseableThreadContextTest.java       | 104 +++++++++++++
 src/changes/changes.xml                         |   3 +
 3 files changed, 261 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/cc86e96a/log4j-api/src/main/java/org/apache/logging/log4j/CloseableThreadContext.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/CloseableThreadContext.java b/log4j-api/src/main/java/org/apache/logging/log4j/CloseableThreadContext.java
new file mode 100644
index 0000000..53fb0e0
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/CloseableThreadContext.java
@@ -0,0 +1,154 @@
+package org.apache.logging.log4j;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Adds entries to the {@link ThreadContext stack or map} and them removes them when the object is closed, e.g. as part
+ * of a try-with-resources.
+ * 
+ * @since 2.6
+ */
+public class CloseableThreadContext implements AutoCloseable {
+
+    /**
+     * Pushes new diagnostic context information on to the Thread Context Stack. The information will be popped off when
+     * the instance is closed.
+     * 
+     * @param message
+     *            The new diagnostic context information.
+     * @return a new instance that will back out the changes when closed.
+     */
+    public static CloseableThreadContext push(final String message) {
+        return new CloseableThreadContext(message);
+    }
+
+    /**
+     * Pushes new diagnostic context information on to the Thread Context Stack. The information will be popped off when
+     * the instance is closed.
+     * 
+     * @param message
+     *            The new diagnostic context information.
+     * @param args
+     *            Parameters for the message.
+     * @return a new instance that will back out the changes when closed.
+     */
+    public static CloseableThreadContext push(final String message, final Object... args) {
+        return new CloseableThreadContext(message, args);
+    }
+
+    /**
+     * Populates the Thread Context Map with the supplied key/value pairs. Any existing keys in the
+     * {@link ThreadContext} will be replaced with the supplied values, and restored back to their original values when
+     * the instance is closed.
+     *
+     * @param firstKey
+     *            The first key to be added
+     * @param firstValue
+     *            The first value to be added
+     * @param subsequentKeyValuePairs
+     *            Any subsequent key/value pairs to be added. Note: If the last key does not have a corresponding value
+     *            then an empty String will be used as a value.
+     * @return a new instance that will back out the changes when closed.
+     */
+    public static CloseableThreadContext put(final String firstKey, final String firstValue,
+            final String... subsequentKeyValuePairs) {
+        return new CloseableThreadContext(firstKey, firstValue, subsequentKeyValuePairs);
+    }
+
+    private final boolean isStack;
+
+    private final Map<String, String> oldValues = new HashMap<>();
+
+    /**
+     * Creates an instance of a {@code CloseableThreadContext} that pushes new diagnostic context information on to the
+     * Thread Context Stack. The information will be popped off when the instance is closed.
+     * 
+     * @param message
+     *            The new diagnostic context information.
+     */
+    public CloseableThreadContext(final String message) {
+        this.isStack = true;
+        ThreadContext.push(message);
+    }
+
+    /**
+     * Creates an instance of a {@code CloseableThreadContext} that pushes new diagnostic context information on to the
+     * Thread Context Stack. The information will be popped off when the instance is closed.
+     * 
+     * @param message
+     *            The new diagnostic context information.
+     * @param args
+     *            Parameters for the message.
+     */
+    public CloseableThreadContext(final String message, final Object... args) {
+        this.isStack = true;
+        ThreadContext.push(message, args);
+    }
+
+    /**
+     * Creates an instance of a {@code CloseableThreadContext} that populates the Thread Context Map with the supplied
+     * key/value pairs. Any existing keys in the ThreadContext will be replaced with the supplied values, and restored
+     * back to their original values when the instance is closed.
+     *
+     * @param firstKey
+     *            The first key to be added
+     * @param firstValue
+     *            The first value to be added
+     * @param subsequentKeyValuePairs
+     *            Any subsequent key/value pairs to be added. Note: If the last key does not have a corresponding value
+     *            then an empty String will be used as a value.
+     */
+    public CloseableThreadContext(final String firstKey, final String firstValue,
+            final String... subsequentKeyValuePairs) {
+        this.isStack = false;
+        storeAndSet(firstKey, firstValue);
+        for (int i = 0; i < subsequentKeyValuePairs.length; i += 2) {
+            final String key = subsequentKeyValuePairs[i];
+            final String value = (i + 1) < subsequentKeyValuePairs.length ? subsequentKeyValuePairs[i + 1] : "";
+            storeAndSet(key, value);
+        }
+    }
+
+    /**
+     * Removes the values from the {@link ThreadContext}.
+     * <p>
+     * If this {@code CloseableThreadContext} was added to the {@link ThreadContext} <em>stack</em>, then this will pop
+     * the diagnostic information off the stack.
+     * </p>
+     * <p>
+     * If the {@code CloseableThreadContext} was added to the {@link ThreadContext} <em>map</em>, then this will either
+     * remove the values that were added, or restore them to their original values it they already existed.
+     * </p>
+     */
+    @Override
+    public void close() {
+        if (this.isStack) {
+            closeStack();
+        } else {
+            closeMap();
+        }
+    }
+
+    private void closeMap() {
+        for (final Map.Entry<String, String> entry : oldValues.entrySet()) {
+            // If the old value was null, remove it from the ThreadContext
+            if (null == entry.getValue()) {
+                ThreadContext.remove(entry.getKey());
+            } else {
+                ThreadContext.put(entry.getKey(), entry.getValue());
+            }
+        }
+    }
+
+    private String closeStack() {
+        return ThreadContext.pop();
+    }
+
+    private void storeAndSet(final String key, final String value) {
+        // If there are no existing values, a null will be stored as an old value
+        oldValues.put(key, ThreadContext.get(key));
+        ThreadContext.put(key, value);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/cc86e96a/log4j-api/src/test/java/org/apache/logging/log4j/CloseableThreadContextTest.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/CloseableThreadContextTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/CloseableThreadContextTest.java
new file mode 100644
index 0000000..42ed624
--- /dev/null
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/CloseableThreadContextTest.java
@@ -0,0 +1,104 @@
+package org.apache.logging.log4j;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests {@link CloseableThreadContext}.
+ * 
+ * @since 2.6
+ */
+public class CloseableThreadContextTest {
+
+    private final String key = "key";
+    private final String value = "value";
+
+    @Before
+    public void setUp() throws Exception {
+        ThreadContext.clearAll();
+    }
+
+    @Test
+    public void shouldAddAnEntryToTheMap() throws Exception {
+        try (final CloseableThreadContext ignored = CloseableThreadContext.put(key, value)) {
+            assertThat(ThreadContext.get(key), is(value));
+        }
+    }
+
+    @Test
+    public void shouldAddTwoEntriesToTheMap() throws Exception {
+        final String key2 = "key2";
+        final String value2 = "value2";
+        try (final CloseableThreadContext ignored = CloseableThreadContext.put(key, value, key2, value2)) {
+            assertThat(ThreadContext.get(key), is(value));
+            assertThat(ThreadContext.get(key2), is(value2));
+        }
+    }
+
+    @Test
+    public void shouldNestEntries() throws Exception {
+        final String oldValue = "oldValue";
+        final String innerValue = "innerValue";
+        ThreadContext.put(key, oldValue);
+        try (final CloseableThreadContext ignored = CloseableThreadContext.put(key, value)) {
+            assertThat(ThreadContext.get(key), is(value));
+            try (final CloseableThreadContext ignored2 = CloseableThreadContext.put(key, innerValue)) {
+                assertThat(ThreadContext.get(key), is(innerValue));
+            }
+            assertThat(ThreadContext.get(key), is(value));
+        }
+        assertThat(ThreadContext.get(key), is(oldValue));
+    }
+
+    @Test
+    public void shouldPreserveOldEntriesFromTheMapWhenAutoClosed() throws Exception {
+        final String oldValue = "oldValue";
+        ThreadContext.put(key, oldValue);
+        try (final CloseableThreadContext ignored = CloseableThreadContext.put(key, value)) {
+            assertThat(ThreadContext.get(key), is(value));
+        }
+        assertThat(ThreadContext.get(key), is(oldValue));
+    }
+
+    @Test
+    public void shouldPushAndPopAnEntryToTheStack() throws Exception {
+        final String message = "message";
+        try (final CloseableThreadContext ignored = CloseableThreadContext.push(message)) {
+            assertThat(ThreadContext.peek(), is(message));
+        }
+        assertThat(ThreadContext.peek(), is(""));
+    }
+
+    @Test
+    public void shouldPushAndPopAParameterizedEntryToTheStack() throws Exception {
+        final String parameterizedMessage = "message {}";
+        final String parameterizedMessageParameter = "param";
+        final String formattedMessage = parameterizedMessage.replace("{}", parameterizedMessageParameter);
+        try (final CloseableThreadContext ignored = CloseableThreadContext.push(parameterizedMessage,
+                parameterizedMessageParameter)) {
+            assertThat(ThreadContext.peek(), is(formattedMessage));
+        }
+        assertThat(ThreadContext.peek(), is(""));
+    }
+
+    @Test
+    public void shouldRemoveAnEntryFromTheMapWhenAutoClosed() throws Exception {
+        try (final CloseableThreadContext ignored = CloseableThreadContext.put(key, value)) {
+            assertThat(ThreadContext.get(key), is(value));
+        }
+        assertThat(ThreadContext.containsKey(key), is(false));
+    }
+
+    @Test
+    public void shouldUseAnEmptyStringIfNoValueIsSupplied() throws Exception {
+        final String key2 = "key2";
+        try (final CloseableThreadContext ignored = CloseableThreadContext.put(key, value, key2)) {
+            assertThat(ThreadContext.get(key), is(value));
+            assertThat(ThreadContext.get(key2), is(""));
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/cc86e96a/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index c6b0745..9e9da48 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -24,6 +24,9 @@
   </properties>
   <body>
     <release version="2.6" date="2016-MM-DD" description="GA Release 2.6">
+      <action issue="LOG4J2-1348" dev="ggregory" type="add" due-to="Greg Thomas, Gary Gregory">
+        Add an AutoCloseable ThreadContext class: CloseableThreadContext.
+      </action>
       <action issue="LOG4J2-1345" dev="rpopma" type="update">
         (Doc) Clarify documentation for properties that control Log4j behaviour.
       </action>