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 2016/09/11 17:05:17 UTC

[4/7] logging-log4j2 git commit: LOG4J2-1575 (GC) Store Configuration Properties in List instead of Map to prevent creating temporary iterator objects

LOG4J2-1575 (GC) Store Configuration Properties in List instead of Map to prevent creating temporary iterator objects


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

Branch: refs/heads/master
Commit: 9cfdd64210aeb0352fdb35975c881df8e00594b8
Parents: 418955b
Author: rpopma <rp...@apache.org>
Authored: Mon Sep 12 01:38:05 2016 +0900
Committer: rpopma <rp...@apache.org>
Committed: Mon Sep 12 01:38:05 2016 +0900

----------------------------------------------------------------------
 .../logging/log4j/core/config/LoggerConfig.java |  98 +++++++++++++-----
 .../logging/log4j/core/config/Property.java     |  12 ++-
 .../log4j/core/GcFreeLoggingTestUtil.java       |   2 +-
 ...ggerAllThreadContextImplementationsTest.java |   4 +-
 ...nfigAllThreadContextImplementationsTest.java |   4 +-
 .../log4j/core/config/LoggerConfigTest.java     | 102 +++++++++++++++++++
 .../logging/log4j/core/config/PropertyTest.java |  12 ++-
 .../AsyncLoggerConfigThreadContextTest.xml      |   7 +-
 .../resources/AsyncLoggerThreadContextTest.xml  |   5 +-
 log4j-core/src/test/resources/gcFreeLogging.xml |   2 +
 .../resources/gcFreeMixedSyncAsyncLogging.xml   |   4 +
 src/changes/changes.xml                         |   3 +
 12 files changed, 218 insertions(+), 37 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/9cfdd642/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java
index c7170c7..0fc7b3f 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java
@@ -67,7 +67,9 @@ public class LoggerConfig extends AbstractFilterable {
     private boolean additive = true;
     private boolean includeLocation = true;
     private LoggerConfig parent;
-    private final Map<Property, Boolean> properties;
+    private Map<Property, Boolean> propertiesMap;
+    private final List<Property> properties;
+    private final boolean propertiesRequireLookup;
     private final Configuration config;
     private final ReliabilityStrategy reliabilityStrategy;
 
@@ -98,6 +100,7 @@ public class LoggerConfig extends AbstractFilterable {
         this.level = Level.ERROR;
         this.name = Strings.EMPTY;
         this.properties = null;
+        this.propertiesRequireLookup = false;
         this.config = null;
         this.reliabilityStrategy = new DefaultReliabilityStrategy(this);
     }
@@ -115,6 +118,7 @@ public class LoggerConfig extends AbstractFilterable {
         this.level = level;
         this.additive = additive;
         this.properties = null;
+        this.propertiesRequireLookup = false;
         this.config = null;
         this.reliabilityStrategy = new DefaultReliabilityStrategy(this);
     }
@@ -131,18 +135,27 @@ public class LoggerConfig extends AbstractFilterable {
         this.includeLocation = includeLocation;
         this.config = config;
         if (properties != null && properties.length > 0) {
-            final Map<Property, Boolean> map = new HashMap<>(properties.length);
-            for (final Property prop : properties) {
-                final boolean interpolate = prop.getValue().contains("${");
-                map.put(prop, interpolate);
-            }
-            this.properties = Collections.unmodifiableMap(map);
+            this.properties = Collections.unmodifiableList(Arrays.asList(Arrays.copyOf(
+                    properties, properties.length)));
         } else {
             this.properties = null;
         }
+        this.propertiesRequireLookup = containsPropertyRequiringLookup(properties);
         this.reliabilityStrategy = config.getReliabilityStrategy(this);
     }
 
+    private static boolean containsPropertyRequiringLookup(final Property[] properties) {
+        if (properties == null) {
+            return false;
+        }
+        for (int i = 0; i < properties.length; i++) {
+            if (properties[i].isValueNeedsLookup()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public Filter getFilter() {
         return super.getFilter();
@@ -310,12 +323,43 @@ public class LoggerConfig extends AbstractFilterable {
      * @return an unmodifiable map with the configuration properties, or {@code null}
      * @see Configuration#getStrSubstitutor()
      * @see StrSubstitutor
+     * @deprecated use {@link #getPropertyList()} instead
      */
     // LOG4J2-157
     public Map<Property, Boolean> getProperties() {
+        if (properties == null) {
+            return null;
+        }
+        if (propertiesMap == null) { // lazily initialize: only used by user custom code, not by Log4j any more
+            final Map<Property, Boolean> result = new HashMap<>(properties.size() * 2);
+            for (int i = 0; i < properties.size(); i++) {
+                result.put(properties.get(i), Boolean.valueOf(properties.get(i).isValueNeedsLookup()));
+            }
+            propertiesMap = Collections.unmodifiableMap(result);
+        }
+        return propertiesMap;
+    }
+
+    /**
+     * Returns an unmodifiable list with the configuration properties, or {@code null} if this {@code LoggerConfig} does
+     * not have any configuration properties.
+     * <p>
+     * Each {@code Property} in the list has an attribute {@link Property#isValueNeedsLookup() valueNeedsLookup} that
+     * is {@code true} if the property value has a variable that needs to be substituted.
+     *
+     * @return an unmodifiable list with the configuration properties, or {@code null}
+     * @see Configuration#getStrSubstitutor()
+     * @see StrSubstitutor
+     * @since 2.7
+     */
+    public List<Property> getPropertyList() {
         return properties;
     }
 
+    public boolean isPropertiesRequireLookup() {
+        return propertiesRequireLookup;
+    }
+
     /**
      * Logs an event.
      *
@@ -326,24 +370,30 @@ public class LoggerConfig extends AbstractFilterable {
      * @param data The Message.
      * @param t A Throwable or null.
      */
+    @PerformanceSensitive("allocation")
     public void log(final String loggerName, final String fqcn, final Marker marker, final Level level,
             final Message data, final Throwable t) {
         List<Property> props = null;
-        if (properties != null) {
-            props = new ArrayList<>(properties.size());
-            final LogEvent event = Log4jLogEvent.newBuilder()
-                .setMessage(data)
-                .setMarker(marker)
-                .setLevel(level)
-                .setLoggerName(loggerName)
-                .setLoggerFqcn(fqcn)
-                .setThrown(t)
-                .build();
-            for (final Map.Entry<Property, Boolean> entry : properties.entrySet()) {
-                final Property prop = entry.getKey();
-                final String value = entry.getValue() ? config.getStrSubstitutor().replace(event, prop.getValue())
-                        : prop.getValue();
-                props.add(Property.createProperty(prop.getName(), value));
+        if (!propertiesRequireLookup) {
+            props = properties;
+        } else {
+            if (properties != null) {
+                props = new ArrayList<>(properties.size());
+                final LogEvent event = Log4jLogEvent.newBuilder()
+                        .setMessage(data)
+                        .setMarker(marker)
+                        .setLevel(level)
+                        .setLoggerName(loggerName)
+                        .setLoggerFqcn(fqcn)
+                        .setThrown(t)
+                        .build();
+                for (int i = 0; i < properties.size(); i++) {
+                    final Property prop = properties.get(i);
+                    final String value = prop.isValueNeedsLookup() // since LOG4J2-1575
+                            ? config.getStrSubstitutor().replace(event, prop.getValue()) //
+                            : prop.getValue();
+                    props.add(Property.createProperty(prop.getName(), value));
+                }
             }
         }
         log(logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t));
@@ -413,11 +463,11 @@ public class LoggerConfig extends AbstractFilterable {
     @Deprecated
     public static LoggerConfig createLogger(final String additivity,
             // @formatter:off
-            final Level level, 
+            final Level level,
             @PluginAttribute("name") final String loggerName,
             final String includeLocation,
             final AppenderRef[] refs,
-            final Property[] properties, 
+            final Property[] properties,
             @PluginConfiguration final Configuration config,
             final Filter filter) {
             // @formatter:on

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/9cfdd642/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Property.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Property.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Property.java
index c33efb4..4dca05a 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Property.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Property.java
@@ -33,10 +33,12 @@ public final class Property {
 
     private final String name;
     private final String value;
+    private final boolean valueNeedsLookup;
 
     private Property(final String name, final String value) {
         this.name = name;
         this.value = value;
+        this.valueNeedsLookup = value != null && value.contains("${");
     }
 
     /**
@@ -56,8 +58,16 @@ public final class Property {
     }
 
     /**
+     * Returns {@code true} if the value contains a substitutable property that requires a lookup to be resolved.
+     * @return {@code true} if the value contains {@code "${"}, {@code false} otherwise
+     */
+    public boolean isValueNeedsLookup() {
+        return valueNeedsLookup;
+    }
+
+    /**
      * Creates a Property.
-     * 
+     *
      * @param name The key.
      * @param value The value.
      * @return A Property.

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/9cfdd642/log4j-core/src/test/java/org/apache/logging/log4j/core/GcFreeLoggingTestUtil.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/GcFreeLoggingTestUtil.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/GcFreeLoggingTestUtil.java
index 9cf4164..1d57c2d 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/GcFreeLoggingTestUtil.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/GcFreeLoggingTestUtil.java
@@ -132,7 +132,7 @@ public class GcFreeLoggingTestUtil {
         final String text = new String(Files.readAllBytes(tempFile.toPath()));
         final List<String> lines = Files.readAllLines(tempFile.toPath(), Charset.defaultCharset());
         final String className = cls.getSimpleName();
-        assertEquals(text, "FATAL o.a.l.l.c." + className + " [main] value1 {aKey=value1, key2=value2} This message is logged to the console",
+        assertEquals(text, "FATAL o.a.l.l.c." + className + " [main] value1 {aKey=value1, key2=value2, prop1=value1, prop2=value2} This message is logged to the console",
                 lines.get(0));
 
         final String LINESEP = System.getProperty("line.separator");

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/9cfdd642/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerAllThreadContextImplementationsTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerAllThreadContextImplementationsTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerAllThreadContextImplementationsTest.java
index 4283cbb..38d959f 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerAllThreadContextImplementationsTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerAllThreadContextImplementationsTest.java
@@ -180,10 +180,10 @@ public class AsyncLoggerAllThreadContextImplementationsTest {
             for (int i = 0; i < LINE_COUNT; i++) {
                 String line = reader.readLine();
                 if ((i & 1) == 1) {
-                    expect = "INFO c.f.Bar mapvalue [stackvalue] {KEY=mapvalue, count=" + i + "} "
+                    expect = "INFO c.f.Bar mapvalue [stackvalue] {KEY=mapvalue, configProp=configValue, configProp2=configValue2, count=" + i + "} "
                             + contextDesc + " i=" + i;
                 } else {
-                    expect = "INFO c.f.Bar mapvalue [stackvalue] {KEY=mapvalue} " + contextDesc + " i=" + i;
+                    expect = "INFO c.f.Bar mapvalue [stackvalue] {KEY=mapvalue, configProp=configValue, configProp2=configValue2} " + contextDesc + " i=" + i;
                 }
                 assertEquals(file.getName() + ": line " + i, expect, line);
             }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/9cfdd642/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigAllThreadContextImplementationsTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigAllThreadContextImplementationsTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigAllThreadContextImplementationsTest.java
index 3c3d69c..4d09ffb 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigAllThreadContextImplementationsTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigAllThreadContextImplementationsTest.java
@@ -151,10 +151,10 @@ public class AsyncLoggerConfigAllThreadContextImplementationsTest {
             for (int i = 0; i < LINE_COUNT; i++) {
                 String line = reader.readLine();
                 if ((i & 1) == 1) {
-                    expect = "INFO c.f.Bar mapvalue [stackvalue] {KEY=mapvalue, count=" + i + "} "
+                    expect = "INFO c.f.Bar mapvalue [stackvalue] {KEY=mapvalue, configProp=configValue, configProp2=configValue2, count=" + i + "} "
                             + contextDesc + " i=" + i;
                 } else {
-                    expect = "INFO c.f.Bar mapvalue [stackvalue] {KEY=mapvalue} " + contextDesc + " i=" + i;
+                    expect = "INFO c.f.Bar mapvalue [stackvalue] {KEY=mapvalue, configProp=configValue, configProp2=configValue2} " + contextDesc + " i=" + i;
                 }
                 assertEquals(file.getName() + ": line " + i, expect, line);
             }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/9cfdd642/log4j-core/src/test/java/org/apache/logging/log4j/core/config/LoggerConfigTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/LoggerConfigTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/LoggerConfigTest.java
new file mode 100644
index 0000000..6eb04a3
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/LoggerConfigTest.java
@@ -0,0 +1,102 @@
+package org.apache.logging.log4j.core.config;/*
+ * 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.
+ */
+
+import java.util.HashSet;
+import java.util.List;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.core.impl.LogEventFactory;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for LoggerConfig.
+ */
+public class LoggerConfigTest {
+
+    private static LoggerConfig createForProperties(final Property[] properties) {
+        return LoggerConfig.createLogger(true, Level.INFO, "name", "false", new AppenderRef[0], properties,
+                new NullConfiguration(), null);
+    }
+
+    @SuppressWarnings({"deprecation", "unchecked"})
+    @Test
+    public void testPropertiesWithoutSubstitution() {
+        assertNull("null propertiesList", createForProperties(null).getPropertyList());
+        assertNull("null property Map", createForProperties(null).getProperties());
+
+        final Property[] all = new Property[] {
+                Property.createProperty("key1", "value1"),
+                Property.createProperty("key2", "value2"),
+        };
+        final LoggerConfig loggerConfig = createForProperties(all);
+        final List<Property> list = loggerConfig.getPropertyList();
+        assertEquals("map and list contents equal", new HashSet(list), loggerConfig.getProperties().keySet());
+
+        final Object[] actualList = new Object[1];
+        loggerConfig.setLogEventFactory(new LogEventFactory() {
+            @Override
+            public LogEvent createEvent(final String loggerName, final Marker marker, final String fqcn,
+                    final Level level, final Message data,
+                    final List<Property> properties, final Throwable t) {
+                actualList[0] = properties;
+                return new Log4jLogEvent(System.currentTimeMillis());
+            }
+        });
+        loggerConfig.log("name", "fqcn", null, Level.INFO, new SimpleMessage("msg"), null);
+        assertSame("propertiesList passed in as is if no substitutions required", list, actualList[0]);
+    }
+
+    @SuppressWarnings({"deprecation", "unchecked"})
+    @Test
+    public void testPropertiesWithSubstitution() {
+        final Property[] all = new Property[] {
+                Property.createProperty("key1", "value1-${sys:user.name}"),
+                Property.createProperty("key2", "value2-${sys:user.name}"),
+        };
+        final LoggerConfig loggerConfig = createForProperties(all);
+        final List<Property> list = loggerConfig.getPropertyList();
+        assertEquals("map and list contents equal", new HashSet(list), loggerConfig.getProperties().keySet());
+
+        final Object[] actualListHolder = new Object[1];
+        loggerConfig.setLogEventFactory(new LogEventFactory() {
+            @Override
+            public LogEvent createEvent(final String loggerName, final Marker marker, final String fqcn,
+                    final Level level, final Message data,
+                    final List<Property> properties, final Throwable t) {
+                actualListHolder[0] = properties;
+                return new Log4jLogEvent(System.currentTimeMillis());
+            }
+        });
+        loggerConfig.log("name", "fqcn", null, Level.INFO, new SimpleMessage("msg"), null);
+        assertNotSame("propertiesList with substitutions", list, actualListHolder[0]);
+
+        final List<Property> actualList = (List<Property>) actualListHolder[0];
+
+        for (int i = 0; i < list.size(); i++) {
+            assertEquals("name[" + i + "]", list.get(i).getName(), actualList.get(i).getName());
+            final String value = list.get(i).getValue().replace("${sys:user.name}", System.getProperty("user.name"));
+            assertEquals("value[" + i + "]", value, actualList.get(i).getValue());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/9cfdd642/log4j-core/src/test/java/org/apache/logging/log4j/core/config/PropertyTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/PropertyTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/PropertyTest.java
index a38382d..8e4a081 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/PropertyTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/PropertyTest.java
@@ -60,4 +60,14 @@ public class PropertyTest {
         assertEquals(expect, messages.get(0));
         app.clear();
     }
-}
\ No newline at end of file
+
+    @Test
+    public void testIsValueNeedsLookup() {
+        assertTrue("with ${ as value", Property.createProperty("", "${").isValueNeedsLookup());
+        assertTrue("with ${ in value", Property.createProperty("", "blah${blah").isValueNeedsLookup());
+        assertFalse("empty value", Property.createProperty("", "").isValueNeedsLookup());
+        assertFalse("without ${ in value", Property.createProperty("", "blahblah").isValueNeedsLookup());
+        assertFalse("without $ in value", Property.createProperty("", "blahb{sys:lah").isValueNeedsLookup());
+        assertFalse("without { in value", Property.createProperty("", "blahb$sys:lah").isValueNeedsLookup());
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/9cfdd642/log4j-core/src/test/resources/AsyncLoggerConfigThreadContextTest.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/AsyncLoggerConfigThreadContextTest.xml b/log4j-core/src/test/resources/AsyncLoggerConfigThreadContextTest.xml
index 1ce6faa..5d40e1a 100644
--- a/log4j-core/src/test/resources/AsyncLoggerConfigThreadContextTest.xml
+++ b/log4j-core/src/test/resources/AsyncLoggerConfigThreadContextTest.xml
@@ -1,8 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Configuration status="ERROR">
-  <Properties>
-    <Property name="configProp">configValue</Property>
-  </Properties>
   <Appenders>
     <RandomAccessFile name="AsyncLoggerAndAsyncAppenderFile" fileName="target/AsyncLoggerAndAsyncAppenderTest.log"
         immediateFlush="false" append="false">
@@ -38,10 +35,14 @@
 
   <Loggers>
     <AsyncLogger name="com.foo" level="info" includeLocation="false" additivity="true">
+      <Property name="configProp">configValue</Property>
+      <Property name="configProp2">configValue2</Property>
       <AppenderRef ref="RandomAccessFile"/>
       <AppenderRef ref="AsyncLoggerAndAsyncAppender"/>
     </AsyncLogger>
     <Root level="info" includeLocation="false">
+      <Property name="configProp">configValue</Property>
+      <Property name="configProp2">configValue2</Property>
       <AppenderRef ref="SynchronousRandomAccessFile"/>
       <AppenderRef ref="AsyncAppender"/>
     </Root>

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/9cfdd642/log4j-core/src/test/resources/AsyncLoggerThreadContextTest.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/AsyncLoggerThreadContextTest.xml b/log4j-core/src/test/resources/AsyncLoggerThreadContextTest.xml
index 6e507d2..683f41c 100644
--- a/log4j-core/src/test/resources/AsyncLoggerThreadContextTest.xml
+++ b/log4j-core/src/test/resources/AsyncLoggerThreadContextTest.xml
@@ -1,8 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Configuration status="ERROR">
-  <Properties>
-    <Property name="configProp">configValue</Property>
-  </Properties>
   <Appenders>
     <RandomAccessFile name="RandomAccessFile" fileName="target/AsyncLoggerTest.log"
 	    		immediateFlush="false" append="false">
@@ -14,6 +11,8 @@
 
   <Loggers>
     <Root level="info" includeLocation="false">
+      <Property name="configProp">configValue</Property>
+      <Property name="configProp2">configValue2</Property>
       <AppenderRef ref="RandomAccessFile"/>
     </Root>
   </Loggers>

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/9cfdd642/log4j-core/src/test/resources/gcFreeLogging.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/gcFreeLogging.xml b/log4j-core/src/test/resources/gcFreeLogging.xml
index 3a3c07e..fd3d1a2 100644
--- a/log4j-core/src/test/resources/gcFreeLogging.xml
+++ b/log4j-core/src/test/resources/gcFreeLogging.xml
@@ -48,6 +48,8 @@
   </Appenders>
   <Loggers>
     <Root level="trace" includeLocation="false">
+      <Property name="prop1">value1</Property>
+      <Property name="prop2">value2</Property>
       <appender-ref ref="Console" level="FATAL" />
       <appender-ref ref="File"/>
       <appender-ref ref="RandomAccessFile"/>

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/9cfdd642/log4j-core/src/test/resources/gcFreeMixedSyncAsyncLogging.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/gcFreeMixedSyncAsyncLogging.xml b/log4j-core/src/test/resources/gcFreeMixedSyncAsyncLogging.xml
index 03d55b5..7c29931 100644
--- a/log4j-core/src/test/resources/gcFreeMixedSyncAsyncLogging.xml
+++ b/log4j-core/src/test/resources/gcFreeMixedSyncAsyncLogging.xml
@@ -48,6 +48,8 @@
   <Loggers>
     <AsyncLogger name="org.apache.logging.log4j.core.GcFreeMixedSyncAyncLoggingTest"
         level="trace" includeLocation="false">
+      <Property name="prop1">value1</Property>
+      <Property name="prop2">value2</Property>
       <appender-ref ref="Console" level="FATAL" />
       <appender-ref ref="File"/>
       <appender-ref ref="RandomAccessFile"/>
@@ -58,6 +60,8 @@
       <appender-ref ref="RandomAccessFileGelf"/>
     </AsyncLogger>
     <Root level="trace" includeLocation="false">
+      <Property name="prop1">value1</Property>
+      <Property name="prop2">value2</Property>
       <appender-ref ref="Console" level="FATAL" />
       <appender-ref ref="File"/>
       <appender-ref ref="RandomAccessFile"/>

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/9cfdd642/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 2b34b8f..e0fa64d 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -24,6 +24,9 @@
   </properties>
   <body>
     <release version="2.7" date="2016-MM-DD" description="GA Release 2.7">
+      <action issue="LOG4J2-1575" dev="rpopma" type="fix">
+        (GC) LoggerConfig now stores configuration properties in a List, not a Map to prevent creating temporary Iterator objects. Added method LoggerConfig#getPropertyList(), deprecated method #getProperties().
+      </action>
       <action issue="LOG4J2-1457" dev="mattsicker" type="fix" due-to="Leon Finker">
         Fixed class loader deadlock when using async logging and extended stack trace pattern.
       </action>