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>