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/06/14 13:34:30 UTC

svn commit: r1602578 - in /logging/log4j/log4j2/trunk: log4j-core/src/main/java/org/apache/logging/log4j/core/async/ log4j-core/src/test/java/org/apache/logging/log4j/core/async/ log4j-core/src/test/resources/ src/changes/

Author: rpopma
Date: Sat Jun 14 11:34:30 2014
New Revision: 1602578

URL: http://svn.apache.org/r1602578
Log:
LOG4J2-669: Prevent NPE when combining AsyncLoggers with AsyncLoggerConfigs

Added:
    logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncLoggerConfigTest.java   (with props)
    logging/log4j/log4j2/trunk/log4j-core/src/test/resources/AsyncLoggersWithAsyncLoggerConfigTest.xml   (with props)
Modified:
    logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigHelper.java
    logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
    logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java
    logging/log4j/log4j2/trunk/src/changes/changes.xml

Modified: logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigHelper.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigHelper.java?rev=1602578&r1=1602577&r2=1602578&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigHelper.java (original)
+++ logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigHelper.java Sat Jun 14 11:34:30 2014
@@ -332,10 +332,14 @@ class AsyncLoggerConfigHelper {
         }
         // LOG4J2-639: catch NPE if disruptor field was set to null after our check above
         try {
+            LogEvent logEvent = event;
+            if (event instanceof RingBufferLogEvent) {
+                logEvent = ((RingBufferLogEvent) event).createMemento();
+            }
             // Note: do NOT use the temp variable above!
             // That could result in adding a log event to the disruptor after it was shut down,
             // which could cause the publishEvent method to hang and never return.
-            disruptor.getRingBuffer().publishEvent(translator, event, asyncLoggerConfig);
+            disruptor.getRingBuffer().publishEvent(translator, logEvent, asyncLoggerConfig);
         } catch (NullPointerException npe) {
             LOGGER.fatal("Ignoring log event after log4j was shut down.");
         }

Modified: logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java?rev=1602578&r1=1602577&r2=1602578&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java (original)
+++ logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java Sat Jun 14 11:34:30 2014
@@ -25,6 +25,7 @@ import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.ThreadContext.ContextStack;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 import org.apache.logging.log4j.core.impl.ThrowableProxy;
 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
 import org.apache.logging.log4j.message.Message;
@@ -35,135 +36,128 @@ import org.apache.logging.log4j.util.Str
 import com.lmax.disruptor.EventFactory;
 
 /**
- * When the Disruptor is started, the RingBuffer is populated with event
- * objects. These objects are then re-used during the life of the RingBuffer.
+ * When the Disruptor is started, the RingBuffer is populated with event objects. These objects are then re-used during
+ * the life of the RingBuffer.
  */
 public class RingBufferLogEvent implements LogEvent {
-	private static final long serialVersionUID = 8462119088943934758L;
+    private static final long serialVersionUID = 8462119088943934758L;
 
-	/**
-	 * Creates the events that will be put in the RingBuffer.
-	 */
-	private static class Factory implements EventFactory<RingBufferLogEvent> {
-
-		@Override
-		public RingBufferLogEvent newInstance() {
-			return new RingBufferLogEvent();
-		}
-	}
-
-	/** The {@code EventFactory} for {@code RingBufferLogEvent}s. */
-	public static final Factory FACTORY = new Factory();
-
-	private AsyncLogger asyncLogger;
-	private String loggerName;
-	private Marker marker;
-	private String fqcn;
-	private Level level;
-	private Message message;
-	private transient Throwable thrown;
+    /**
+     * Creates the events that will be put in the RingBuffer.
+     */
+    private static class Factory implements EventFactory<RingBufferLogEvent> {
+
+        @Override
+        public RingBufferLogEvent newInstance() {
+            return new RingBufferLogEvent();
+        }
+    }
+
+    /** The {@code EventFactory} for {@code RingBufferLogEvent}s. */
+    public static final Factory FACTORY = new Factory();
+
+    private AsyncLogger asyncLogger;
+    private String loggerName;
+    private Marker marker;
+    private String fqcn;
+    private Level level;
+    private Message message;
+    private transient Throwable thrown;
     private ThrowableProxy thrownProxy;
-	private Map<String, String> contextMap;
-	private ContextStack contextStack;
-	private String threadName;
-	private StackTraceElement location;
-	private long currentTimeMillis;
-	private boolean endOfBatch;
-	private boolean includeLocation;
-
-	public void setValues(final AsyncLogger asyncLogger,
-			final String loggerName, final Marker marker, final String fqcn,
-			final Level level, final Message data, final Throwable throwable,
-			final Map<String, String> map, final ContextStack contextStack,
-			final String threadName, final StackTraceElement location,
-			final long currentTimeMillis) {
-		this.asyncLogger = asyncLogger;
-		this.loggerName = loggerName;
-		this.marker = marker;
-		this.fqcn = fqcn;
-		this.level = level;
-		this.message = data;
-		this.thrown = throwable;
-		this.thrownProxy = null;
-		this.contextMap = map;
-		this.contextStack = contextStack;
-		this.threadName = threadName;
-		this.location = location;
-		this.currentTimeMillis = currentTimeMillis;
-	}
-
-	/**
-	 * Event processor that reads the event from the ringbuffer can call this
-	 * method.
-	 * 
-	 * @param endOfBatch
-	 *            flag to indicate if this is the last event in a batch from the
-	 *            RingBuffer
-	 */
-	public void execute(final boolean endOfBatch) {
-		this.endOfBatch = endOfBatch;
-		asyncLogger.actualAsyncLog(this);
-	}
-
-	/**
-	 * Returns {@code true} if this event is the end of a batch, {@code false}
-	 * otherwise.
-	 * 
-	 * @return {@code true} if this event is the end of a batch, {@code false}
-	 *         otherwise
-	 */
-	@Override
-	public boolean isEndOfBatch() {
-		return endOfBatch;
-	}
-
-	@Override
-	public void setEndOfBatch(final boolean endOfBatch) {
-		this.endOfBatch = endOfBatch;
-	}
-
-	@Override
-	public boolean isIncludeLocation() {
-		return includeLocation;
-	}
-
-	@Override
-	public void setIncludeLocation(final boolean includeLocation) {
-		this.includeLocation = includeLocation;
-	}
-
-	@Override
-	public String getLoggerName() {
-		return loggerName;
-	}
-
-	@Override
-	public Marker getMarker() {
-		return marker;
-	}
-
-	@Override
-	public String getLoggerFqcn() {
-		return fqcn;
-	}
-
-	@Override
-	public Level getLevel() {
-		if (level == null) {
-			level = Level.OFF; // LOG4J2-462, LOG4J2-465
-		}
-		return level;
-	}
-
-	@Override
-	public Message getMessage() {
-		if (message == null) {
-			message = new SimpleMessage(Strings.EMPTY);
-		}
-		return message;
-	}
+    private Map<String, String> contextMap;
+    private ContextStack contextStack;
+    private String threadName;
+    private StackTraceElement location;
+    private long currentTimeMillis;
+    private boolean endOfBatch;
+    private boolean includeLocation;
+
+    public void setValues(final AsyncLogger asyncLogger, final String loggerName, final Marker marker,
+            final String fqcn, final Level level, final Message data, final Throwable throwable,
+            final Map<String, String> map, final ContextStack contextStack, final String threadName,
+            final StackTraceElement location, final long currentTimeMillis) {
+        this.asyncLogger = asyncLogger;
+        this.loggerName = loggerName;
+        this.marker = marker;
+        this.fqcn = fqcn;
+        this.level = level;
+        this.message = data;
+        this.thrown = throwable;
+        this.thrownProxy = null;
+        this.contextMap = map;
+        this.contextStack = contextStack;
+        this.threadName = threadName;
+        this.location = location;
+        this.currentTimeMillis = currentTimeMillis;
+    }
+
+    /**
+     * Event processor that reads the event from the ringbuffer can call this method.
+     * 
+     * @param endOfBatch flag to indicate if this is the last event in a batch from the RingBuffer
+     */
+    public void execute(final boolean endOfBatch) {
+        this.endOfBatch = endOfBatch;
+        asyncLogger.actualAsyncLog(this);
+    }
+
+    /**
+     * Returns {@code true} if this event is the end of a batch, {@code false} otherwise.
+     * 
+     * @return {@code true} if this event is the end of a batch, {@code false} otherwise
+     */
+    @Override
+    public boolean isEndOfBatch() {
+        return endOfBatch;
+    }
+
+    @Override
+    public void setEndOfBatch(final boolean endOfBatch) {
+        this.endOfBatch = endOfBatch;
+    }
+
+    @Override
+    public boolean isIncludeLocation() {
+        return includeLocation;
+    }
+
+    @Override
+    public void setIncludeLocation(final boolean includeLocation) {
+        this.includeLocation = includeLocation;
+    }
+
+    @Override
+    public String getLoggerName() {
+        return loggerName;
+    }
+
+    @Override
+    public Marker getMarker() {
+        return marker;
+    }
+
+    @Override
+    public String getLoggerFqcn() {
+        return fqcn;
+    }
+
+    @Override
+    public Level getLevel() {
+        if (level == null) {
+            level = Level.OFF; // LOG4J2-462, LOG4J2-465
+        }
+        return level;
+    }
+
+    @Override
+    public Message getMessage() {
+        if (message == null) {
+            message = new SimpleMessage(Strings.EMPTY);
+        }
+        return message;
+    }
 
-	@Override
+    @Override
     public Throwable getThrown() {
         // after deserialization, thrown is null but thrownProxy may be non-null
         if (thrown == null) {
@@ -174,100 +168,107 @@ public class RingBufferLogEvent implemen
         return thrown;
     }
 
-	@Override
-	public ThrowableProxy getThrownProxy() {
-	    // lazily instantiate the (expensive) ThrowableProxy
-	    if (thrownProxy == null) {
-	        if (thrown != null) {
-	            thrownProxy = new ThrowableProxy(thrown);
-	        }
-	    }
-		return this.thrownProxy;
-	}
-
-	@Override
-	public Map<String, String> getContextMap() {
-		return contextMap;
-	}
-
-	@Override
-	public ContextStack getContextStack() {
-		return contextStack;
-	}
-
-	@Override
-	public String getThreadName() {
-		return threadName;
-	}
-
-	@Override
-	public StackTraceElement getSource() {
-		return location;
-	}
-
-	@Override
-	public long getTimeMillis() {
-		Message msg = getMessage();
-		if (msg instanceof TimestampMessage) { // LOG4J2-455
-			return ((TimestampMessage) msg).getTimestamp();
-		}
-		return currentTimeMillis;
-	}
-
-	/**
-	 * Merges the contents of the specified map into the contextMap, after
-	 * replacing any variables in the property values with the
-	 * StrSubstitutor-supplied actual values.
-	 * 
-	 * @param properties
-	 *            configured properties
-	 * @param strSubstitutor
-	 *            used to lookup values of variables in properties
-	 */
-	public void mergePropertiesIntoContextMap(
-			final Map<Property, Boolean> properties,
-			final StrSubstitutor strSubstitutor) {
-		if (properties == null) {
-			return; // nothing to do
-		}
-
-		final Map<String, String> map = contextMap == null ? new HashMap<String, String>()
-				: new HashMap<String, String>(contextMap);
-
-		for (final Map.Entry<Property, Boolean> entry : properties.entrySet()) {
-			final Property prop = entry.getKey();
-			if (map.containsKey(prop.getName())) {
-				continue; // contextMap overrides config properties
-			}
-			final String value = entry.getValue().booleanValue() ? strSubstitutor.replace(prop
-					.getValue()) : prop.getValue();
-			map.put(prop.getName(), value);
-		}
-		contextMap = map;
-	}
-
-	/**
-	 * Release references held by ring buffer to allow objects to be
-	 * garbage-collected.
-	 */
-	public void clear() {
-		setValues(null, // asyncLogger
-				null, // loggerName
-				null, // marker
-				null, // fqcn
-				null, // level
-				null, // data
-				null, // t
-				null, // map
-				null, // contextStack
-				null, // threadName
-				null, // location
-				0 // currentTimeMillis
-		);
-	}
-	
-	private void writeObject(java.io.ObjectOutputStream out) throws IOException {
-	    getThrownProxy(); // initialize the ThrowableProxy before serializing
-	    out.defaultWriteObject();
-	}
+    @Override
+    public ThrowableProxy getThrownProxy() {
+        // lazily instantiate the (expensive) ThrowableProxy
+        if (thrownProxy == null) {
+            if (thrown != null) {
+                thrownProxy = new ThrowableProxy(thrown);
+            }
+        }
+        return this.thrownProxy;
+    }
+
+    @Override
+    public Map<String, String> getContextMap() {
+        return contextMap;
+    }
+
+    @Override
+    public ContextStack getContextStack() {
+        return contextStack;
+    }
+
+    @Override
+    public String getThreadName() {
+        return threadName;
+    }
+
+    @Override
+    public StackTraceElement getSource() {
+        return location;
+    }
+
+    @Override
+    public long getTimeMillis() {
+        Message msg = getMessage();
+        if (msg instanceof TimestampMessage) { // LOG4J2-455
+            return ((TimestampMessage) msg).getTimestamp();
+        }
+        return currentTimeMillis;
+    }
+
+    /**
+     * Merges the contents of the specified map into the contextMap, after replacing any variables in the property
+     * values with the StrSubstitutor-supplied actual values.
+     * 
+     * @param properties configured properties
+     * @param strSubstitutor used to lookup values of variables in properties
+     */
+    public void mergePropertiesIntoContextMap(final Map<Property, Boolean> properties,
+            final StrSubstitutor strSubstitutor) {
+        if (properties == null) {
+            return; // nothing to do
+        }
+
+        final Map<String, String> map = contextMap == null ? new HashMap<String, String>()
+                : new HashMap<String, String>(contextMap);
+
+        for (final Map.Entry<Property, Boolean> entry : properties.entrySet()) {
+            final Property prop = entry.getKey();
+            if (map.containsKey(prop.getName())) {
+                continue; // contextMap overrides config properties
+            }
+            final String value = entry.getValue().booleanValue() ? strSubstitutor.replace(prop.getValue()) : prop
+                    .getValue();
+            map.put(prop.getName(), value);
+        }
+        contextMap = map;
+    }
+
+    /**
+     * Release references held by ring buffer to allow objects to be garbage-collected.
+     */
+    public void clear() {
+        setValues(null, // asyncLogger
+                null, // loggerName
+                null, // marker
+                null, // fqcn
+                null, // level
+                null, // data
+                null, // t
+                null, // map
+                null, // contextStack
+                null, // threadName
+                null, // location
+                0 // currentTimeMillis
+        );
+    }
+
+    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
+        getThrownProxy(); // initialize the ThrowableProxy before serializing
+        out.defaultWriteObject();
+    }
+
+    /**
+     * Creates and returns a new immutable copy of this {@code RingBufferLogEvent}.
+     * 
+     * @return a new immutable copy of the data in this {@code RingBufferLogEvent}
+     */
+    public LogEvent createMemento() {
+        // Ideally, would like to use the LogEventFactory here but signature does not match:
+        // results in factory re-creating the timestamp, context map and context stack, which we don't want.
+        return new Log4jLogEvent(loggerName, marker, fqcn, level, message, thrown, contextMap, contextStack,
+                threadName, location, currentTimeMillis);
+    }
 }

Added: logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncLoggerConfigTest.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncLoggerConfigTest.java?rev=1602578&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncLoggerConfigTest.java (added)
+++ logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncLoggerConfigTest.java Sat Jun 14 11:34:30 2014
@@ -0,0 +1,74 @@
+/*
+ * 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.util.List;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class AsyncLoggersWithAsyncLoggerConfigTest {
+    private static Configuration config;
+    private static ListAppender listAppender;
+    private static LoggerContext ctx;
+
+    @BeforeClass
+    public static void beforeClass() {
+        System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY,
+                "AsyncLoggersWithAsyncLoggerConfigTest.xml");
+        System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, AsyncLoggerContextSelector.class.getName());
+        ctx = (LoggerContext) LogManager.getContext(false);
+        config = ctx.getConfiguration();
+        listAppender = (ListAppender) config.getAppender("List");
+    }
+
+    @AfterClass
+    public static void cleanupClass() {
+        System.clearProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY);
+        System.clearProperty(Constants.LOG4J_CONTEXT_SELECTOR);
+        ctx.reconfigure();
+        StatusLogger.getLogger().reset();
+    }
+
+    @Test
+    public void testLoggingWorks() throws Exception {        
+        final Logger logger = LogManager.getLogger();
+        logger.error("This is a test");
+        logger.warn("Hello world!");
+        Thread.sleep(100);
+        final List<String> list = listAppender.getMessages();
+        assertNotNull("No events generated", list);
+        assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
+        String msg = list.get(0);
+        String expected = getClass().getName() + " This is a test";
+        assertTrue("Expected " + expected + ", Actual " + msg, expected.equals(msg));
+        msg = list.get(1);
+        expected = getClass().getName() + " Hello world!";
+        assertTrue("Expected " + expected + ", Actual " + msg, expected.equals(msg));
+    }
+}

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

Modified: logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java?rev=1602578&r1=1602577&r2=1602578&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java (original)
+++ logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java Sat Jun 14 11:34:30 2014
@@ -22,15 +22,20 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.MarkerManager;
 import org.apache.logging.log4j.ThreadContext.ContextStack;
+import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.impl.ThrowableProxy;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.SimpleMessage;
 import org.apache.logging.log4j.message.TimestampMessage;
+import org.apache.logging.log4j.spi.MutableThreadContextStack;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
@@ -188,4 +193,37 @@ public class RingBufferLogEventTest {
         assertEquals(location, other.getSource());
         assertEquals(currentTimeMillis, other.getTimeMillis());
     }
+    
+    @Test
+    public void testCreateMementoReturnsCopy() {
+        RingBufferLogEvent evt = new RingBufferLogEvent();
+        String loggerName = "logger.name";
+        Marker marker = MarkerManager.getMarker("marked man");
+        String fqcn = "f.q.c.n";
+        Level level = Level.TRACE;
+        Message data = new SimpleMessage("message");
+        Throwable t = new InternalError("not a real error");
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("key", "value");
+        ContextStack contextStack = new MutableThreadContextStack(Arrays.asList("a", "b"));
+        String threadName = "main";
+        StackTraceElement location = null;
+        long currentTimeMillis = 12345;
+        evt.setValues(null, loggerName, marker, fqcn, level, data, t, map,
+                contextStack, threadName, location, currentTimeMillis);
+        
+        LogEvent actual = evt.createMemento();
+        assertEquals(evt.getLoggerName(), actual.getLoggerName());
+        assertEquals(evt.getMarker(), actual.getMarker());
+        assertEquals(evt.getLoggerFqcn(), actual.getLoggerFqcn());
+        assertEquals(evt.getLevel(), actual.getLevel());
+        assertEquals(evt.getMessage(), actual.getMessage());
+        assertEquals(evt.getThrown(), actual.getThrown());
+        assertEquals(evt.getContextMap(), actual.getContextMap());
+        assertEquals(evt.getContextStack(), actual.getContextStack());
+        assertEquals(evt.getThreadName(), actual.getThreadName());
+        assertEquals(evt.getTimeMillis(), actual.getTimeMillis());
+        assertEquals(evt.getSource(), actual.getSource());
+        assertEquals(evt.getThrownProxy(), actual.getThrownProxy());
+    }
 }

Added: logging/log4j/log4j2/trunk/log4j-core/src/test/resources/AsyncLoggersWithAsyncLoggerConfigTest.xml
URL: http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/test/resources/AsyncLoggersWithAsyncLoggerConfigTest.xml?rev=1602578&view=auto
==============================================================================
--- logging/log4j/log4j2/trunk/log4j-core/src/test/resources/AsyncLoggersWithAsyncLoggerConfigTest.xml (added)
+++ logging/log4j/log4j2/trunk/log4j-core/src/test/resources/AsyncLoggersWithAsyncLoggerConfigTest.xml Sat Jun 14 11:34:30 2014
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="ERROR">
+  <Appenders>
+    <List name="List">
+      <PatternLayout pattern="%c %m"/>
+    </List>
+  </Appenders>
+  
+  <Loggers>
+    <AsyncRoot level="trace">
+      <AppenderRef ref="List"/>
+    </AsyncRoot>
+  </Loggers>
+</Configuration>
\ No newline at end of file

Propchange: logging/log4j/log4j2/trunk/log4j-core/src/test/resources/AsyncLoggersWithAsyncLoggerConfigTest.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=1602578&r1=1602577&r2=1602578&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/src/changes/changes.xml (original)
+++ logging/log4j/log4j2/trunk/src/changes/changes.xml Sat Jun 14 11:34:30 2014
@@ -22,6 +22,9 @@
   </properties>
   <body>
     <release version="2.0-rc2" date="2014-MM-DD" description="Bug fixes and enhancements">
+      <action issue="LOG4J2-669" dev="rpopma" type="fix">
+        Prevent NPE when combining AsyncLoggers with AsyncLoggerConfigs.
+      </action>
       <action issue="LOG4J2-42" dev="rgoers" type="add">
         Create an appender to route log events to the ServletContext log.
       </action>