You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rp...@apache.org on 2014/07/12 13:22:51 UTC

svn commit: r1609903 - in /logging/log4j/log4j2/trunk: log4j-api/src/main/java/org/apache/logging/log4j/ log4j-api/src/main/java/org/apache/logging/log4j/spi/ log4j-api/src/test/java/org/apache/logging/log4j/spi/ log4j-core/src/test/java/org/apache/log...

Author: rpopma
Date: Sat Jul 12 11:22:51 2014
New Revision: 1609903

URL: http://svn.apache.org/r1609903
Log:
LOG4J2-705 Fixed issue where Async Logger does not log thread context stack data. API change: added method getImmutableStackOrNull() to ThreadContext.ContextStack interface.

Added:
    logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadContextTest.java   (with props)
    logging/log4j/log4j2/trunk/log4j-core/src/test/resources/AsyncLoggerThreadContextTest.xml   (with props)
Modified:
    logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java
    logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java
    logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/spi/MutableThreadContextStack.java
    logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextStackTest.java
    logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/spi/MutableThreadContextStackTest.java
    logging/log4j/log4j2/trunk/src/changes/changes.xml

Modified: logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java?rev=1609903&r1=1609902&r2=1609903&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java (original)
+++ logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java Sat Jul 12 11:22:51 2014
@@ -142,6 +142,10 @@ public final class ThreadContext  {
             return 0;
         }
 
+        @Override
+        public ContextStack getImmutableStackOrNull() {
+            return this;
+        }
     }
 
     /**
@@ -169,12 +173,14 @@ public final class ThreadContext  {
     /**
      * Empty, immutable Map.
      */
-    @SuppressWarnings("PublicStaticCollectionField")
+    // ironically, this annotation gives an "unsupported @SuppressWarnings" warning in Eclipse
+    @SuppressWarnings("PublicStaticCollectionField") // I like irony, so I won't delete it...
     public static final Map<String, String> EMPTY_MAP = Collections.emptyMap();
 
     /**
      * Empty, immutable ContextStack.
      */
+    // ironically, this annotation gives an "unsupported @SuppressWarnings" warning in Eclipse
     @SuppressWarnings("PublicStaticCollectionField")
     public static final ThreadContextStack EMPTY_STACK = new EmptyThreadContextStack();
 
@@ -352,7 +358,8 @@ public final class ThreadContext  {
      * @return an immutable copy of the ThreadContext stack.
      */
     public static ContextStack getImmutableStack() {
-        return contextStack;
+        ContextStack result = contextStack.getImmutableStackOrNull();
+        return result == null ? EMPTY_STACK : result;
     }
 
     /**
@@ -527,8 +534,16 @@ public final class ThreadContext  {
 
         /**
          * Returns a copy of the ContextStack.
-         * @return a copy of the ContextStack.s
+         * @return a copy of the ContextStack.
          */
         ContextStack copy();
+
+        /**
+         * Returns a ContextStack with the same contents as this ContextStack or {@code null}.
+         * Attempts to modify the returned stack may or may not throw an exception, but will not affect the contents
+         * of this ContextStack.
+         * @return a ContextStack with the same contents as this ContextStack or {@code null}.
+         */
+        ContextStack getImmutableStackOrNull();
     }
 }

Modified: logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java?rev=1609903&r1=1609902&r2=1609903&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java (original)
+++ logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java Sat Jul 12 11:22:51 2014
@@ -16,13 +16,13 @@
  */
 package org.apache.logging.log4j.spi;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
 
+import org.apache.logging.log4j.ThreadContext.ContextStack;
 import org.apache.logging.log4j.util.Strings;
 
 /**
@@ -33,23 +33,28 @@ public class DefaultThreadContextStack i
 
     private static final long serialVersionUID = 5050501L;
 
-    private static final ThreadLocal<List<String>> stack = new ThreadLocal<List<String>>();
+    private static final ThreadLocal<MutableThreadContextStack> stack = new ThreadLocal<MutableThreadContextStack>();
 
     private final boolean useStack;
 
     public DefaultThreadContextStack(final boolean useStack) {
         this.useStack = useStack;
     }
+    
+    private MutableThreadContextStack getNonNullStackCopy() {
+        final MutableThreadContextStack values = stack.get();
+        return (MutableThreadContextStack) (values == null ? new MutableThreadContextStack() : values.copy());
+    }
 
     @Override
     public boolean add(final String s) {
         if (!useStack) {
             return false;
         }
-        final List<String> list = stack.get();
-        final List<String> copy = list == null ? new ArrayList<String>() : new ArrayList<String>(list);
+        final MutableThreadContextStack copy = getNonNullStackCopy();
         copy.add(s);
-        stack.set(Collections.unmodifiableList(copy));
+        copy.freeze();
+        stack.set(copy);
         return true;
     }
 
@@ -58,20 +63,20 @@ public class DefaultThreadContextStack i
         if (!useStack || strings.isEmpty()) {
             return false;
         }
-        final List<String> list = stack.get();
-        final List<String> copy = list == null ? new ArrayList<String>() : new ArrayList<String>(list);
+        final MutableThreadContextStack copy = getNonNullStackCopy();
         copy.addAll(strings);
-        stack.set(Collections.unmodifiableList(copy));
+        copy.freeze();
+        stack.set(copy);
         return true;
     }
 
     @Override
     public List<String> asList() {
-        final List<String> list = stack.get();
-        if (list == null) {
+        final MutableThreadContextStack values = stack.get();
+        if (values == null) {
             return Collections.emptyList();
         }
-        return list;
+        return values.asList();
     }
 
     @Override
@@ -81,8 +86,8 @@ public class DefaultThreadContextStack i
 
     @Override
     public boolean contains(final Object o) {
-        final List<String> result = stack.get();
-        return result != null && result.contains(o);
+        final MutableThreadContextStack values = stack.get();
+        return values != null && values.contains(o);
     }
 
     @Override
@@ -91,17 +96,17 @@ public class DefaultThreadContextStack i
             return true; // looks counter-intuitive, but see
                          // j.u.AbstractCollection
         }
-        final List<String> list = stack.get();
-        return list != null && list.containsAll(objects);
+        final MutableThreadContextStack values = stack.get();
+        return values != null && values.containsAll(objects);
     }
 
     @Override
     public ThreadContextStack copy() {
-        List<String> result = null;
-        if (!useStack || (result = stack.get()) == null) {
-            return new MutableThreadContextStack(new ArrayList<String>());
+        MutableThreadContextStack values = null;
+        if (!useStack || (values = stack.get()) == null) {
+            return new MutableThreadContextStack();
         }
-        return new MutableThreadContextStack(result);
+        return values.copy();
     }
 
     @Override
@@ -122,58 +127,52 @@ public class DefaultThreadContextStack i
             return false;
         }
         final ThreadContextStack other = (ThreadContextStack) obj;
-        final List<String> otherAsList = other.asList();
-        final List<String> list = stack.get();
-        if (list == null) {
-            if (otherAsList != null) {
-                return false;
-            }
-        } else if (!list.equals(otherAsList)) {
-            return false;
+        final MutableThreadContextStack values = stack.get();
+        if (values == null) {
+            return other == null;
         }
-        return true;
+        return values.equals(other);
     }
 
     @Override
     public int getDepth() {
-        final List<String> list = stack.get();
-        return list == null ? 0 : list.size();
+        final MutableThreadContextStack values = stack.get();
+        return values == null ? 0 : values.getDepth();
     }
 
     @Override
     public int hashCode() {
-        final List<String> list = stack.get();
+        final MutableThreadContextStack values = stack.get();
         final int prime = 31;
         int result = 1;
         // Factor in the stack itself to compare vs. other implementors.
-        result = prime * result + ((list == null) ? 0 : list.hashCode());
+        result = prime * result + ((values == null) ? 0 : values.hashCode());
         return result;
     }
 
     @Override
     public boolean isEmpty() {
-        final List<String> result = stack.get();
-        return result == null || result.isEmpty();
+        final MutableThreadContextStack values = stack.get();
+        return values == null || values.isEmpty();
     }
 
     @Override
     public Iterator<String> iterator() {
-        final List<String> immutable = stack.get();
-        if (immutable == null) {
+        final MutableThreadContextStack values = stack.get();
+        if (values == null) {
             final List<String> empty = Collections.emptyList();
             return empty.iterator();
         }
-        return immutable.iterator();
+        return values.iterator();
     }
 
     @Override
     public String peek() {
-        final List<String> list = stack.get();
-        if (list == null || list.size() == 0) {
+        final MutableThreadContextStack values = stack.get();
+        if (values == null || values.size() == 0) {
             return null;
         }
-        final int last = list.size() - 1;
-        return list.get(last);
+        return values.peek();
     }
 
     @Override
@@ -181,14 +180,14 @@ public class DefaultThreadContextStack i
         if (!useStack) {
             return Strings.EMPTY;
         }
-        final List<String> list = stack.get();
-        if (list == null || list.size() == 0) {
+        final MutableThreadContextStack values = stack.get();
+        if (values == null || values.size() == 0) {
             throw new NoSuchElementException("The ThreadContext stack is empty");
         }
-        final List<String> copy = new ArrayList<String>(list);
-        final int last = copy.size() - 1;
-        final String result = copy.remove(last);
-        stack.set(Collections.unmodifiableList(copy));
+        final MutableThreadContextStack copy = (MutableThreadContextStack) values.copy();
+        final String result = copy.pop();
+        copy.freeze();
+        stack.set(copy);
         return result;
     }
 
@@ -205,13 +204,14 @@ public class DefaultThreadContextStack i
         if (!useStack) {
             return false;
         }
-        final List<String> list = stack.get();
-        if (list == null || list.size() == 0) {
+        final MutableThreadContextStack values = stack.get();
+        if (values == null || values.size() == 0) {
             return false;
         }
-        final List<String> copy = new ArrayList<String>(list);
+        final MutableThreadContextStack copy = (MutableThreadContextStack) values.copy();
         final boolean result = copy.remove(o);
-        stack.set(Collections.unmodifiableList(copy));
+        copy.freeze();
+        stack.set(copy);
         return result;
     }
 
@@ -220,13 +220,14 @@ public class DefaultThreadContextStack i
         if (!useStack || objects.isEmpty()) {
             return false;
         }
-        final List<String> list = stack.get();
-        if (list == null || list.isEmpty()) {
+        final MutableThreadContextStack values = stack.get();
+        if (values == null || values.isEmpty()) {
             return false;
         }
-        final List<String> copy = new ArrayList<String>(list);
+        final MutableThreadContextStack copy = (MutableThreadContextStack) values.copy();
         final boolean result = copy.removeAll(objects);
-        stack.set(Collections.unmodifiableList(copy));
+        copy.freeze();
+        stack.set(copy);
         return result;
     }
 
@@ -235,25 +236,26 @@ public class DefaultThreadContextStack i
         if (!useStack || objects.isEmpty()) {
             return false;
         }
-        final List<String> list = stack.get();
-        if (list == null || list.isEmpty()) {
+        final MutableThreadContextStack values = stack.get();
+        if (values == null || values.isEmpty()) {
             return false;
         }
-        final List<String> copy = new ArrayList<String>(list);
+        final MutableThreadContextStack copy = (MutableThreadContextStack) values.copy();
         final boolean result = copy.retainAll(objects);
-        stack.set(Collections.unmodifiableList(copy));
+        copy.freeze();
+        stack.set(copy);
         return result;
     }
 
     @Override
     public int size() {
-        final List<String> result = stack.get();
-        return result == null ? 0 : result.size();
+        final MutableThreadContextStack values = stack.get();
+        return values == null ? 0 : values.size();
     }
 
     @Override
     public Object[] toArray() {
-        final List<String> result = stack.get();
+        final MutableThreadContextStack result = stack.get();
         if (result == null) {
             return new String[0];
         }
@@ -262,7 +264,7 @@ public class DefaultThreadContextStack i
 
     @Override
     public <T> T[] toArray(final T[] ts) {
-        final List<String> result = stack.get();
+        final MutableThreadContextStack result = stack.get();
         if (result == null) {
             if (ts.length > 0) { // as per the contract of j.u.List#toArray(T[])
                 ts[0] = null;
@@ -274,8 +276,8 @@ public class DefaultThreadContextStack i
 
     @Override
     public String toString() {
-        final List<String> list = stack.get();
-        return list == null ? "[]" : list.toString();
+        final MutableThreadContextStack values = stack.get();
+        return values == null ? "[]" : values.toString();
     }
 
     @Override
@@ -283,15 +285,21 @@ public class DefaultThreadContextStack i
         if (depth < 0) {
             throw new IllegalArgumentException("Maximum stack depth cannot be negative");
         }
-        final List<String> list = stack.get();
-        if (list == null) {
+        final MutableThreadContextStack values = stack.get();
+        if (values == null) {
             return;
         }
-        final List<String> copy = new ArrayList<String>();
-        final int count = Math.min(depth, list.size());
-        for (int i = 0; i < count; i++) {
-            copy.add(list.get(i));
-        }
+        final MutableThreadContextStack copy = (MutableThreadContextStack) values.copy();
+        copy.trim(depth);
+        copy.freeze();
         stack.set(copy);
     }
+
+    /* (non-Javadoc)
+     * @see org.apache.logging.log4j.ThreadContext.ContextStack#getImmutableStackOrNull()
+     */
+    @Override
+    public ContextStack getImmutableStackOrNull() {
+        return stack.get();
+    }
 }

Modified: logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/spi/MutableThreadContextStack.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/spi/MutableThreadContextStack.java?rev=1609903&r1=1609902&r2=1609903&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/spi/MutableThreadContextStack.java (original)
+++ logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/spi/MutableThreadContextStack.java Sat Jul 12 11:22:51 2014
@@ -21,6 +21,8 @@ import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 
+import org.apache.logging.log4j.ThreadContext.ContextStack;
+
 /**
  *
  */
@@ -32,6 +34,14 @@ public class MutableThreadContextStack i
      * The underlying list (never null).
      */
     private final List<String> list;
+    private boolean frozen;
+
+    /**
+     * Constructs an empty MutableThreadContextStack.
+     */
+    public MutableThreadContextStack() {
+        this(new ArrayList<String>());
+    }
 
     public MutableThreadContextStack(final List<String> list) {
         this.list = new ArrayList<String>(list);
@@ -41,8 +51,15 @@ public class MutableThreadContextStack i
         this.list = new ArrayList<String>(stack.list);
     }
 
+    private void checkInvariants() {
+        if (frozen) {
+            throw new UnsupportedOperationException("context stack has been frozen");
+        }
+    }
+
     @Override
     public String pop() {
+        checkInvariants();
         if (list.isEmpty()) {
             return null;
         }
@@ -62,6 +79,7 @@ public class MutableThreadContextStack i
 
     @Override
     public void push(final String message) {
+        checkInvariants();
         list.add(message);
     }
 
@@ -77,6 +95,7 @@ public class MutableThreadContextStack i
 
     @Override
     public void trim(final int depth) {
+        checkInvariants();
         if (depth < 0) {
             throw new IllegalArgumentException("Maximum stack depth cannot be negative");
         }
@@ -99,6 +118,7 @@ public class MutableThreadContextStack i
 
     @Override
     public void clear() {
+        checkInvariants();
         list.clear();
     }
 
@@ -134,11 +154,13 @@ public class MutableThreadContextStack i
 
     @Override
     public boolean add(final String s) {
+        checkInvariants();
         return list.add(s);
     }
 
     @Override
     public boolean remove(final Object o) {
+        checkInvariants();
         return list.remove(o);
     }
 
@@ -149,16 +171,19 @@ public class MutableThreadContextStack i
 
     @Override
     public boolean addAll(final Collection<? extends String> strings) {
+        checkInvariants();
         return list.addAll(strings);
     }
 
     @Override
     public boolean removeAll(final Collection<?> objects) {
+        checkInvariants();
         return list.removeAll(objects);
     }
 
     @Override
     public boolean retainAll(final Collection<?> objects) {
+        checkInvariants();
         return list.retainAll(objects);
     }
 
@@ -197,4 +222,20 @@ public class MutableThreadContextStack i
         }
         return true;
     }
+
+    @Override
+    public ContextStack getImmutableStackOrNull() {
+        return copy();
+    }
+
+    /**
+     * "Freezes" this context stack so it becomes immutable: all mutator methods will throw an exception from now on.
+     */
+    public void freeze() {
+        frozen = true;
+    }
+    
+    public boolean isFrozen() {
+        return frozen;
+    }
 }

Modified: logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextStackTest.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextStackTest.java?rev=1609903&r1=1609902&r2=1609903&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextStackTest.java (original)
+++ logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextStackTest.java Sat Jul 12 11:22:51 2014
@@ -19,12 +19,20 @@ package org.apache.logging.log4j.spi;
 import java.util.Arrays;
 import java.util.Iterator;
 
+import org.apache.logging.log4j.ThreadContext.ContextStack;
+import org.junit.Before;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
 
 public class DefaultThreadContextStackTest {
 
+    @Before
+    public void before() {
+        // clear the thread-local map
+        new DefaultThreadContextMap(true).clear();
+    }
+
     @Test
     public void testEqualsVsSameKind() {
         final DefaultThreadContextStack stack1 = createStack();
@@ -53,10 +61,38 @@ public class DefaultThreadContextStackTe
     }
 
     @Test
-    public void testHashCodeVsMutable() {
-        final DefaultThreadContextStack stack1 = createStack();
-        final MutableThreadContextStack stack2 = MutableThreadContextStackTest.createStack();
-        assertEquals(stack1.hashCode(), stack2.hashCode());
+    public void testImmutableOrNullReturnsNullIfUseStackIsFalse() {
+        final DefaultThreadContextStack stack = new DefaultThreadContextStack(false);
+        stack.clear();
+        assertEquals(null, stack.getImmutableStackOrNull());
+    }
+
+    @Test
+    public void testImmutableOrNullReturnsNullIfStackIsEmpty() {
+        final DefaultThreadContextStack stack = new DefaultThreadContextStack(true);
+        stack.clear();
+        assertTrue(stack.isEmpty());
+        assertEquals(null, stack.getImmutableStackOrNull());
+    }
+
+    @Test
+    public void testImmutableOrNullReturnsCopyOfContents() {
+        final DefaultThreadContextStack stack = createStack();
+        assertTrue(!stack.isEmpty());
+        final ContextStack actual = stack.getImmutableStackOrNull();
+        assertNotNull(actual);
+        assertEquals(stack, actual);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testModifyingImmutableOrNullThrowsException() {
+        final DefaultThreadContextStack stack = createStack();
+        final int originalSize = stack.size();
+        assertTrue(originalSize > 0);
+        final ContextStack actual = stack.getImmutableStackOrNull();
+        assertEquals(originalSize, actual.size());
+
+        actual.pop();
     }
 
     @Test
@@ -215,7 +251,7 @@ public class DefaultThreadContextStackTe
     public void testToArray() {
         final DefaultThreadContextStack stack = createStack();
 
-        final String[] expecteds = {"msg1", "msg2", "msg3"};
+        final String[] expecteds = { "msg1", "msg2", "msg3" };
         assertArrayEquals(expecteds, stack.toArray());
     }
 
@@ -223,8 +259,8 @@ public class DefaultThreadContextStackTe
     public void testToArrayTArray() {
         final DefaultThreadContextStack stack = createStack();
 
-        final String[] expecteds = {"msg1", "msg2", "msg3"};
-        final String[] result = new String[3] ;
+        final String[] expecteds = { "msg1", "msg2", "msg3" };
+        final String[] result = new String[3];
         assertArrayEquals(expecteds, stack.toArray(result));
         assertSame(result, stack.toArray(result));
     }

Modified: logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/spi/MutableThreadContextStackTest.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/spi/MutableThreadContextStackTest.java?rev=1609903&r1=1609902&r2=1609903&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/spi/MutableThreadContextStackTest.java (original)
+++ logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/spi/MutableThreadContextStackTest.java Sat Jul 12 11:22:51 2014
@@ -202,7 +202,7 @@ public class MutableThreadContextStackTe
     public void testToArray() {
         final MutableThreadContextStack stack = createStack();
 
-        final String[] expecteds = {"msg1", "msg2", "msg3"};
+        final String[] expecteds = { "msg1", "msg2", "msg3" };
         assertArrayEquals(expecteds, stack.toArray());
     }
 
@@ -210,8 +210,8 @@ public class MutableThreadContextStackTe
     public void testToArrayTArray() {
         final MutableThreadContextStack stack = createStack();
 
-        final String[] expecteds = {"msg1", "msg2", "msg3"};
-        final String[] result = new String[3] ;
+        final String[] expecteds = { "msg1", "msg2", "msg3" };
+        final String[] result = new String[3];
         assertArrayEquals(expecteds, stack.toArray(result));
         assertSame(result, stack.toArray(result));
     }
@@ -287,4 +287,81 @@ public class MutableThreadContextStackTe
         stack.retainAll(Arrays.asList("msg1", "msg3"));
         assertEquals("[msg1, msg3]", stack.toString());
     }
+
+    @Test
+    public void testIsFrozenIsFalseByDefault() {
+        assertFalse(new MutableThreadContextStack().isFrozen());
+        assertFalse(createStack().isFrozen());
+    }
+
+    @Test
+    public void testIsFrozenIsTrueAfterCallToFreeze() {
+        final MutableThreadContextStack stack = new MutableThreadContextStack();
+        assertFalse(stack.isFrozen());
+        stack.freeze();
+        assertTrue(stack.isFrozen());
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAddAllOnFrozenStackThrowsException() {
+        final MutableThreadContextStack stack = new MutableThreadContextStack();
+        stack.freeze();
+        stack.addAll(Arrays.asList("a", "b", "c"));
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAddOnFrozenStackThrowsException() {
+        final MutableThreadContextStack stack = new MutableThreadContextStack();
+        stack.freeze();
+        stack.add("a");
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testClearOnFrozenStackThrowsException() {
+        final MutableThreadContextStack stack = new MutableThreadContextStack();
+        stack.freeze();
+        stack.clear();
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testPopOnFrozenStackThrowsException() {
+        final MutableThreadContextStack stack = new MutableThreadContextStack();
+        stack.freeze();
+        stack.pop();
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testPushOnFrozenStackThrowsException() {
+        final MutableThreadContextStack stack = new MutableThreadContextStack();
+        stack.freeze();
+        stack.push("a");
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRemoveOnFrozenStackThrowsException() {
+        final MutableThreadContextStack stack = new MutableThreadContextStack();
+        stack.freeze();
+        stack.remove("a");
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRemoveAllOnFrozenStackThrowsException() {
+        final MutableThreadContextStack stack = new MutableThreadContextStack();
+        stack.freeze();
+        stack.removeAll(Arrays.asList("a", "b"));
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRetainAllOnFrozenStackThrowsException() {
+        final MutableThreadContextStack stack = new MutableThreadContextStack();
+        stack.freeze();
+        stack.retainAll(Arrays.asList("a", "b"));
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testTrimOnFrozenStackThrowsException() {
+        final MutableThreadContextStack stack = new MutableThreadContextStack();
+        stack.freeze();
+        stack.trim(3);
+    }
 }

Added: logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadContextTest.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadContextTest.java?rev=1609903&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadContextTest.java (added)
+++ logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadContextTest.java Sat Jul 12 11:22:51 2014
@@ -0,0 +1,76 @@
+/*
+ * 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.log4j.core.async;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.util.Strings;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class AsyncLoggerThreadContextTest {
+
+    @BeforeClass
+    public static void beforeClass() {
+        System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR,
+                AsyncLoggerContextSelector.class.getName());
+        System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY,
+                "AsyncLoggerThreadContextTest.xml");
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY);
+    }
+
+    @Test
+    public void testAsyncLogWritesToLog() throws Exception {
+        final File f = new File("target", "AsyncLoggerTest.log");
+        // System.out.println(f.getAbsolutePath());
+        f.delete();
+        
+        ThreadContext.push("stackvalue");
+        ThreadContext.put("KEY", "mapvalue");
+        
+        final Logger log = LogManager.getLogger("com.foo.Bar");
+        final String msg = "Async logger msg";
+        log.info(msg, new InternalError("this is not a real error"));
+        ((LifeCycle) LogManager.getContext()).stop(); // stop async thread
+
+        final BufferedReader reader = new BufferedReader(new FileReader(f));
+        final String line1 = reader.readLine();
+        reader.close();
+        f.delete();
+        assertNotNull("line1", line1);
+        assertTrue("line1 correct", line1.contains(msg));
+
+        assertTrue("ThreadContext.map", line1.contains("mapvalue"));
+        assertTrue("ThreadContext.stack", line1.contains("stackvalue"));
+    }
+
+}

Propchange: logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadContextTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: logging/log4j/log4j2/trunk/log4j-core/src/test/resources/AsyncLoggerThreadContextTest.xml
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/test/resources/AsyncLoggerThreadContextTest.xml?rev=1609903&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/log4j-core/src/test/resources/AsyncLoggerThreadContextTest.xml (added)
+++ logging/log4j/log4j2/trunk/log4j-core/src/test/resources/AsyncLoggerThreadContextTest.xml Sat Jul 12 11:22:51 2014
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="ERROR">
+  <Appenders>
+    <RandomAccessFile name="RandomAccessFile" fileName="target/AsyncLoggerTest.log" 
+	    		immediateFlush="false" append="false">
+      <PatternLayout>
+        <Pattern>%d %p %c{1.} [%t] %X{KEY} %x %location %m %ex%n</Pattern>
+      </PatternLayout>
+    </RandomAccessFile>
+  </Appenders>
+  
+  <Loggers>
+    <Root level="info" includeLocation="false">
+      <AppenderRef ref="RandomAccessFile"/>
+    </Root>
+  </Loggers>
+</Configuration>
\ No newline at end of file

Propchange: logging/log4j/log4j2/trunk/log4j-core/src/test/resources/AsyncLoggerThreadContextTest.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: logging/log4j/log4j2/trunk/src/changes/changes.xml
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/src/changes/changes.xml?rev=1609903&r1=1609902&r2=1609903&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/src/changes/changes.xml (original)
+++ logging/log4j/log4j2/trunk/src/changes/changes.xml Sat Jul 12 11:22:51 2014
@@ -22,6 +22,10 @@
   </properties>
   <body>
     <release version="2.0-???" date="2014-0M-DD" description="Bug fixes and enhancements">
+      <action issue="LOG4J2-705" dev="rpopma" type="fix">
+        Fixed issue where Async Logger does not log thread context stack data.
+        API change: added method getImmutableStackOrNull() to ThreadContext.ContextStack interface.
+      </action>
       <action issue="LOG4J2-631" dev="rpopma" type="fix">
         Update docs to clarify how to use formatter logger and standard logger together.
       </action>