You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by ck...@apache.org on 2018/12/30 18:43:17 UTC

[logging-log4j2] branch master updated: LOG4J2-2527: ListAppender getters return immutable snapshots

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 8cd0ef5  LOG4J2-2527: ListAppender getters return immutable snapshots
8cd0ef5 is described below

commit 8cd0ef5b294319c579ae1f692eff3c9535aaf564
Author: Carter Kozak <ck...@apache.org>
AuthorDate: Sun Dec 30 09:55:22 2018 -0500

    LOG4J2-2527: ListAppender getters return immutable snapshots
    
    Provides a bit more thread safety to ListAppender. The class
    is marked not-thread-safe, however this functionality is
    important for testing, particularly when asynchronous logging is
    configured.
    
    Previously we returned unmodifiable wrappers around the internal
    state, which can be dangerous to iterate over while events are
    actively logged.
    
    State is tracked in synchronized lists, which should be safer
    than synchronizing on individual methods.
---
 .../log4j/core/CustomLevelsOverrideTest.java       | 14 ++----
 .../logging/log4j/core/CustomLevelsTest.java       | 14 ++----
 .../org/apache/logging/log4j/core/LoggerTest.java  | 52 +++++++++-------------
 .../core/appender/ScriptAppenderSelectorTest.java  | 14 ++----
 .../routing/DefaultRouteScriptAppenderTest.java    | 21 ++++-----
 .../appender/routing/RoutesScriptAppenderTest.java | 27 +++++------
 .../layout/PatternLayoutMainMapLookupTest.java     | 12 ++---
 .../logging/log4j/test/appender/ListAppender.java  | 33 +++++++-------
 src/changes/changes.xml                            |  6 +++
 9 files changed, 81 insertions(+), 112 deletions(-)

diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsOverrideTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsOverrideTest.java
index 628d73f..2fed8af 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsOverrideTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsOverrideTest.java
@@ -22,8 +22,6 @@ import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 
-import java.util.List;
-
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.junit.LoggerContextRule;
 import org.apache.logging.log4j.test.appender.ListAppender;
@@ -31,9 +29,6 @@ import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Test;
 
-/**
- *
- */
 public class CustomLevelsOverrideTest {
 
     private static final String CONFIG = "log4j-customLevels.xml";
@@ -81,13 +76,12 @@ public class CustomLevelsOverrideTest {
     @Test
     public void testLog() {
         final Logger logger = context.getLogger();
-        final List<LogEvent> events = listAppender.getEvents();
-        assertThat(events, hasSize(0));
+        assertThat(listAppender.getEvents(), hasSize(0));
         logger.debug("Hello, {}", "World");
-        assertThat(events, hasSize(1));
+        assertThat(listAppender.getEvents(), hasSize(1));
         logger.log(warnLevel, "Hello DIAG");
-        assertThat(events, hasSize(2));
-        assertEquals(events.get(1).getLevel(), warnLevel);
+        assertThat(listAppender.getEvents(), hasSize(2));
+        assertEquals(listAppender.getEvents().get(1).getLevel(), warnLevel);
 
     }
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsTest.java
index 7bb7297..826b397 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsTest.java
@@ -21,8 +21,6 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 
-import java.util.List;
-
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.junit.LoggerContextRule;
 import org.apache.logging.log4j.test.appender.ListAppender;
@@ -30,9 +28,6 @@ import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Test;
 
-/**
- *
- */
 public class CustomLevelsTest {
 
     private static final String CONFIG = "log4j-customLevels.xml";
@@ -70,13 +65,12 @@ public class CustomLevelsTest {
     @Test
     public void testLog() {
         final Logger logger = context.getLogger();
-        final List<LogEvent> events = listAppender.getEvents();
-        assertThat(events, hasSize(0));
+        assertThat(listAppender.getEvents(), hasSize(0));
         logger.debug("Hello, {}", "World");
-        assertThat(events, hasSize(1));
+        assertThat(listAppender.getEvents(), hasSize(1));
         logger.log(diagLevel, "Hello DIAG");
-        assertThat(events, hasSize(2));
-        assertEquals(events.get(1).getLevel(), diagLevel);
+        assertThat(listAppender.getEvents(), hasSize(2));
+        assertEquals(listAppender.getEvents().get(1).getLevel(), diagLevel);
 
     }
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/LoggerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/LoggerTest.java
index d03e72a..81f2d9d 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/LoggerTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/LoggerTest.java
@@ -51,9 +51,6 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestName;
 
-/**
- *
- */
 public class LoggerTest {
 
     private static final String CONFIG = "log4j-test2.xml";
@@ -130,14 +127,13 @@ public class LoggerTest {
     @Test
     public void debugChangeLevel() {
         logger.debug("Debug message 1");
-        final List<LogEvent> events = app.getEvents();
-        assertEventCount(events, 1);
+        assertEventCount(app.getEvents(), 1);
         Configurator.setLevel(logger.getName(), Level.OFF);
         logger.debug("Debug message 2");
-        assertEventCount(events, 1);
+        assertEventCount(app.getEvents(), 1);
         Configurator.setLevel(logger.getName(), Level.DEBUG);
         logger.debug("Debug message 3");
-        assertEventCount(events, 2);
+        assertEventCount(app.getEvents(), 2);
     }
 
     @Test
@@ -146,18 +142,17 @@ public class LoggerTest {
         logger.debug("Debug message 1");
         loggerChild.debug("Debug message 1 child");
         loggerGrandchild.debug("Debug message 1 grandchild");
-        final List<LogEvent> events = app.getEvents();
-        assertEventCount(events, 3);
+        assertEventCount(app.getEvents(), 3);
         Configurator.setAllLevels(logger.getName(), Level.OFF);
         logger.debug("Debug message 2");
         loggerChild.warn("Warn message 2 child");
         loggerGrandchild.fatal("Fatal message 2 grandchild");
-        assertEventCount(events, 3);
+        assertEventCount(app.getEvents(), 3);
         Configurator.setAllLevels(logger.getName(), Level.DEBUG);
         logger.debug("Debug message 3");
         loggerChild.warn("Trace message 3 child");
         loggerGrandchild.trace("Fatal message 3 grandchild");
-        assertEventCount(events, 5);
+        assertEventCount(app.getEvents(), 5);
     }
 
     @Test
@@ -166,18 +161,17 @@ public class LoggerTest {
         logger.debug("Debug message 1");
         loggerChild.debug("Debug message 1 child");
         loggerGrandchild.debug("Debug message 1 grandchild");
-        final List<LogEvent> events = app.getEvents();
-        assertEventCount(events, 3);
+        assertEventCount(app.getEvents(), 3);
         Configurator.setLevel(logger.getName(), Level.OFF);
         logger.debug("Debug message 2");
         loggerChild.debug("Debug message 2 child");
         loggerGrandchild.debug("Debug message 2 grandchild");
-        assertEventCount(events, 3);
+        assertEventCount(app.getEvents(), 3);
         Configurator.setLevel(logger.getName(), Level.DEBUG);
         logger.debug("Debug message 3");
         loggerChild.debug("Debug message 3 child");
         loggerGrandchild.debug("Debug message 3 grandchild");
-        assertEventCount(events, 6);
+        assertEventCount(app.getEvents(), 6);
     }
 
     @Test
@@ -186,33 +180,31 @@ public class LoggerTest {
         // Use logger AND loggerChild
         logger.debug("Debug message 1");
         loggerChild.debug("Debug message 1 child");
-        final List<LogEvent> events = app.getEvents();
-        assertEventCount(events, 2);
+        assertEventCount(app.getEvents(), 2);
         Configurator.setLevel(logger.getName(), Level.ERROR);
         Configurator.setLevel(loggerChild.getName(), Level.DEBUG);
         logger.debug("Debug message 2");
         loggerChild.debug("Debug message 2 child");
-        assertEventCount(events, 3);
+        assertEventCount(app.getEvents(), 3);
         Configurator.setLevel(logger.getName(), Level.DEBUG);
         logger.debug("Debug message 3");
         loggerChild.debug("Debug message 3 child");
-        assertEventCount(events, 5);
+        assertEventCount(app.getEvents(), 5);
     }
 
     @Test
     public void debugChangeLevelsMap() {
         logger.debug("Debug message 1");
-        final List<LogEvent> events = app.getEvents();
-        assertEventCount(events, 1);
+        assertEventCount(app.getEvents(), 1);
         final Map<String, Level> map = new HashMap<>();
         map.put(logger.getName(), Level.OFF);
         Configurator.setLevel(map);
         logger.debug("Debug message 2");
-        assertEventCount(events, 1);
+        assertEventCount(app.getEvents(), 1);
         map.put(logger.getName(), Level.DEBUG);
         Configurator.setLevel(map);
         logger.debug("Debug message 3");
-        assertEventCount(events, 2);
+        assertEventCount(app.getEvents(), 2);
     }
 
     @Test
@@ -220,8 +212,7 @@ public class LoggerTest {
         logger.debug("Debug message 1");
         loggerChild.debug("Debug message 1 C");
         loggerGrandchild.debug("Debug message 1 GC");
-        final List<LogEvent> events = app.getEvents();
-        assertEventCount(events, 3);
+        assertEventCount(app.getEvents(), 3);
         final Map<String, Level> map = new HashMap<>();
         map.put(logger.getName(), Level.OFF);
         map.put(loggerChild.getName(), Level.DEBUG);
@@ -230,7 +221,7 @@ public class LoggerTest {
         logger.debug("Debug message 2");
         loggerChild.debug("Debug message 2 C");
         loggerGrandchild.debug("Debug message 2 GC");
-        assertEventCount(events, 4);
+        assertEventCount(app.getEvents(), 4);
         map.put(logger.getName(), Level.DEBUG);
         map.put(loggerChild.getName(), Level.OFF);
         map.put(loggerGrandchild.getName(), Level.DEBUG);
@@ -238,20 +229,19 @@ public class LoggerTest {
         logger.debug("Debug message 3");
         loggerChild.debug("Debug message 3 C");
         loggerGrandchild.debug("Debug message 3 GC");
-        assertEventCount(events, 6);
+        assertEventCount(app.getEvents(), 6);
     }
 
     @Test
     public void debugChangeRootLevel() {
         logger.debug("Debug message 1");
-        final List<LogEvent> events = app.getEvents();
-        assertEventCount(events, 1);
+        assertEventCount(app.getEvents(), 1);
         Configurator.setRootLevel(Level.OFF);
         logger.debug("Debug message 2");
-        assertEventCount(events, 1);
+        assertEventCount(app.getEvents(), 1);
         Configurator.setRootLevel(Level.DEBUG);
         logger.debug("Debug message 3");
-        assertEventCount(events, 2);
+        assertEventCount(app.getEvents(), 2);
     }
 
     @Test
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ScriptAppenderSelectorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ScriptAppenderSelectorTest.java
index a1fe45c..62a29b6 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ScriptAppenderSelectorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ScriptAppenderSelectorTest.java
@@ -16,12 +16,9 @@
  */
 package org.apache.logging.log4j.core.appender;
 
-import java.util.List;
-
 import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.MarkerManager;
 import org.apache.logging.log4j.categories.Scripts;
-import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.Logger;
 import org.apache.logging.log4j.junit.LoggerContextRule;
 import org.apache.logging.log4j.test.appender.ListAppender;
@@ -33,9 +30,6 @@ import org.junit.runners.Parameterized;
 
 import static org.junit.Assert.*;
 
-/**
- *
- */
 @RunWith(Parameterized.class)
 @Category(Scripts.Groovy.class)
 public class ScriptAppenderSelectorTest {
@@ -66,13 +60,11 @@ public class ScriptAppenderSelectorTest {
         final Logger logger = loggerContextRule.getLogger(ScriptAppenderSelectorTest.class);
         logger.error("Hello");
         final ListAppender listAppender = getListAppender();
-        final List<LogEvent> list = listAppender.getEvents();
-        assertNotNull("No events generated", list);
-        assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
+        assertEquals("Incorrect number of events", 1, listAppender.getEvents().size());
         logger.error("World");
-        assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
+        assertEquals("Incorrect number of events", 2, listAppender.getEvents().size());
         logger.error(marker, "DEADBEEF");
-        assertTrue("Incorrect number of events. Expected 3, got " + list.size(), list.size() == 3);
+        assertEquals("Incorrect number of events", 3, listAppender.getEvents().size());
     }
 
     @Test(expected = AssertionError.class)
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/DefaultRouteScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/DefaultRouteScriptAppenderTest.java
index b112fb7..4e14e8d 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/DefaultRouteScriptAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/DefaultRouteScriptAppenderTest.java
@@ -16,16 +16,11 @@
  */
 package org.apache.logging.log4j.core.appender.routing;
 
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentMap;
 
 import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.MarkerManager;
-import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.Logger;
 import org.apache.logging.log4j.core.config.AppenderControl;
 import org.apache.logging.log4j.junit.LoggerContextRule;
@@ -36,6 +31,8 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+import static org.junit.Assert.*;
+
 /**
  *
  */
@@ -68,8 +65,8 @@ public class DefaultRouteScriptAppenderTest {
         final RoutingAppender routingAppender = getRoutingAppender();
         final ConcurrentMap<Object, Object> map = routingAppender.getScriptStaticVariables();
         if (expectBindingEntries) {
-            Assert.assertEquals("TestValue2", map.get("TestKey"));
-            Assert.assertEquals("HEXDUMP", map.get("MarkerName"));
+            assertEquals("TestValue2", map.get("TestKey"));
+            assertEquals("HEXDUMP", map.get("MarkerName"));
         }
     }
 
@@ -93,13 +90,11 @@ public class DefaultRouteScriptAppenderTest {
         final Logger logger = loggerContextRule.getLogger(DefaultRouteScriptAppenderTest.class);
         logger.error("Hello");
         final ListAppender listAppender = getListAppender();
-        final List<LogEvent> list = listAppender.getEvents();
-        assertNotNull("No events generated", list);
-        assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
+        assertEquals("Incorrect number of events", 1, listAppender.getEvents().size());
         logger.error("World");
-        assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
+        assertEquals("Incorrect number of events", 2, listAppender.getEvents().size());
         logger.error(marker, "DEADBEEF");
-        assertTrue("Incorrect number of events. Expected 3, got " + list.size(), list.size() == 3);
+        assertEquals("Incorrect number of events", 3, listAppender.getEvents().size());
     }
 
     @Test(expected = AssertionError.class)
@@ -130,7 +125,7 @@ public class DefaultRouteScriptAppenderTest {
         final RoutingAppender routingAppender = getRoutingAppender();
         Assert.assertNotNull(routingAppender.getDefaultRouteScript());
         Assert.assertNotNull(routingAppender.getDefaultRoute());
-        Assert.assertEquals("Service2", routingAppender.getDefaultRoute().getKey());
+        assertEquals("Service2", routingAppender.getDefaultRoute().getKey());
     }
 
     @Test
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
index 12b571f..3b20f8f 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
@@ -17,9 +17,8 @@
 package org.apache.logging.log4j.core.appender.routing;
 
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -41,9 +40,6 @@ import org.junit.experimental.categories.Category;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
-/**
- *
- */
 @RunWith(Parameterized.class)
 @Category(Scripts.Groovy.class) // technically only half of these tests require groovy
 public class RoutesScriptAppenderTest {
@@ -74,8 +70,8 @@ public class RoutesScriptAppenderTest {
         final RoutingAppender routingAppender = getRoutingAppender();
         final ConcurrentMap<Object, Object> map = routingAppender.getScriptStaticVariables();
         if (expectBindingEntries) {
-            Assert.assertEquals("TestValue2", map.get("TestKey"));
-            Assert.assertEquals("HEXDUMP", map.get("MarkerName"));
+            assertEquals("TestValue2", map.get("TestKey"));
+            assertEquals("HEXDUMP", map.get("MarkerName"));
         }
     }
     private ListAppender getListAppender() {
@@ -85,8 +81,7 @@ public class RoutesScriptAppenderTest {
         final Map<String, AppenderControl> appenders = routingAppender.getAppenders();
         final AppenderControl appenderControl = appenders.get(key);
         assertNotNull("No appender control generated for '" + key + "'; appenders = " + appenders, appenderControl);
-        final ListAppender listAppender = (ListAppender) appenderControl.getAppender();
-        return listAppender;
+        return (ListAppender) appenderControl.getAppender();
     }
 
     private RoutingAppender getRoutingAppender() {
@@ -98,13 +93,11 @@ public class RoutesScriptAppenderTest {
         final Logger logger = loggerContextRule.getLogger(RoutesScriptAppenderTest.class);
         logger.error("Hello");
         final ListAppender listAppender = getListAppender();
-        final List<LogEvent> list = listAppender.getEvents();
-        assertNotNull("No events generated", list);
-        assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
+        assertEquals("Incorrect number of events", 1, listAppender.getEvents().size());
         logger.error("World");
-        assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
+        assertEquals("Incorrect number of events", 2, listAppender.getEvents().size());
         logger.error(marker, "DEADBEEF");
-        assertTrue("Incorrect number of events. Expected 3, got " + list.size(), list.size() == 3);
+        assertEquals("Incorrect number of events", 3, listAppender.getEvents().size());
     }
 
     @Test(expected = AssertionError.class)
@@ -133,14 +126,14 @@ public class RoutesScriptAppenderTest {
     @Test
     public void testRoutingAppenderRoutes() {
         final RoutingAppender routingAppender = getRoutingAppender();
-        Assert.assertEquals(expectBindingEntries, routingAppender.getDefaultRouteScript() != null);
-        Assert.assertEquals(expectBindingEntries, routingAppender.getDefaultRoute() != null);
+        assertEquals(expectBindingEntries, routingAppender.getDefaultRouteScript() != null);
+        assertEquals(expectBindingEntries, routingAppender.getDefaultRoute() != null);
         final Routes routes = routingAppender.getRoutes();
         Assert.assertNotNull(routes);
         Assert.assertNotNull(routes.getPatternScript());
         final LogEvent logEvent = DefaultLogEventFactory.getInstance().createEvent("", null, "", Level.ERROR, null,
                 null, null);
-        Assert.assertEquals("Service2", routes.getPattern(logEvent, new ConcurrentHashMap<>()));
+        assertEquals("Service2", routes.getPattern(logEvent, new ConcurrentHashMap<>()));
     }
 
     @Test
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java
index a2881df..120af89 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java
@@ -53,12 +53,14 @@ public class PatternLayoutMainMapLookupTest {
         final ListAppender listApp = context.getListAppender("List");
         final Logger logger = context.getLogger(this.getClass().getName());
         logger.info("Hello World");
-        final List<String> messages = listApp.getMessages();
-        Assert.assertFalse(messages.isEmpty());
-        final String messagesStr = messages.toString();
-        Assert.assertEquals(messagesStr, "Header: value0", messages.get(0));
+        final List<String> initialMessages = listApp.getMessages();
+        Assert.assertFalse(initialMessages.isEmpty());
+        final String messagesStr = initialMessages.toString();
+        Assert.assertEquals(messagesStr, "Header: value0", initialMessages.get(0));
         listApp.stop();
-        Assert.assertEquals(messagesStr, "Footer: value1", messages.get(2));
+        final List<String> finalMessages = listApp.getMessages();
+        Assert.assertEquals(3, finalMessages.size());
+        Assert.assertEquals("Footer: value1", finalMessages.get(2));
     }
 
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java
index 2d28926..ffe4226 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java
@@ -51,13 +51,13 @@ import org.apache.logging.log4j.core.impl.MutableLogEvent;
 @Plugin(name = "List", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
 public class ListAppender extends AbstractAppender {
 
-    // Use CopyOnWriteArrayList?
+    // Use Collections.synchronizedList rather than CopyOnWriteArrayList because we expect
+    // more frequent writes than reads.
+    final List<LogEvent> events = Collections.synchronizedList(new ArrayList<>());
 
-    final List<LogEvent> events = new ArrayList<>();
+    private final List<String> messages = Collections.synchronizedList(new ArrayList<>());
 
-    private final List<String> messages = new ArrayList<>();
-
-    final List<byte[]> data = new ArrayList<>();
+    final List<byte[]> data = Collections.synchronizedList(new ArrayList<>());
 
     private final boolean newLine;
 
@@ -92,7 +92,7 @@ public class ListAppender extends AbstractAppender {
      * }
      * </pre>
      */
-    public CountDownLatch countDownLatch = null;
+    public volatile CountDownLatch countDownLatch = null;
 
     public ListAppender(final String name) {
         super(name, null, null, true, Property.EMPTY_ARRAY);
@@ -114,7 +114,7 @@ public class ListAppender extends AbstractAppender {
     }
 
     @Override
-    public synchronized void append(final LogEvent event) {
+    public void append(final LogEvent event) {
         final Layout<? extends Serializable> layout = getLayout();
         if (layout == null) {
             if (event instanceof MutableLogEvent) {
@@ -183,19 +183,21 @@ public class ListAppender extends AbstractAppender {
         return true;
     }
 
-    public synchronized ListAppender clear() {
+    public ListAppender clear() {
         events.clear();
         messages.clear();
         data.clear();
         return this;
     }
 
-    public synchronized List<LogEvent> getEvents() {
-        return Collections.unmodifiableList(events);
+    /** Returns an immutable snapshot of captured log events */
+    public List<LogEvent> getEvents() {
+        return Collections.unmodifiableList(new ArrayList<>(events));
     }
 
-    public synchronized List<String> getMessages() {
-        return Collections.unmodifiableList(messages);
+    /** Returns an immutable snapshot of captured messages */
+    public List<String> getMessages() {
+        return Collections.unmodifiableList(new ArrayList<>(messages));
     }
 
     /**
@@ -208,11 +210,12 @@ public class ListAppender extends AbstractAppender {
         while (messages.size() < minSize && System.currentTimeMillis() < endMillis) {
             Thread.sleep(100);
         }
-        return Collections.unmodifiableList(messages);
+        return getMessages();
     }
 
-    public synchronized List<byte[]> getData() {
-        return Collections.unmodifiableList(data);
+    /** Returns an immutable snapshot of captured data */
+    public List<byte[]> getData() {
+        return Collections.unmodifiableList(new ArrayList<>(data));
     }
 
     public static ListAppender createAppender(final String name, final boolean newLine, final boolean raw,
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index ea844de..c851d8c 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -338,6 +338,9 @@
       <action issue="LOG4J2-1246" dev="ggregory" type="add">
         PatternLayout %date conversion pattern should render time zone designator for ISO-ISO8601.
       </action>
+      <action issue="LOG4J2-2527" dev="ckozak" type="fix">
+        Prevent ConcurrentModificationException while iterating over ListAppender events.
+      </action>
     </release>
     <release version="2.11.2" date="2018-MM-DD" description="GA Release 2.11.2">
       <action issue="LOG4J2-1576" dev="rgoers" type="update">
@@ -469,6 +472,9 @@
       <action issue="LOG4J2-1246" dev="ggregory" type="add">
         PatternLayout %date conversion pattern should render time zone designator for ISO-ISO8601.
       </action>
+      <action issue="LOG4J2-2527" dev="ckozak" type="fix">
+        Prevent ConcurrentModificationException while iterating over ListAppender events.
+      </action>
     </release>
     <release version="2.11.1" date="2018-07-22" description="GA Release 2.11.1">
       <action issue="LOG4J2-2389" dev="rgoers" type="fix" due-to="Liu Wen">