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/17 13:04:50 UTC

logging-log4j2 git commit: LOG4J2-1583 Fixed scrambled log messages triggered by nested logging from toString() method of a logging parameter object.

Repository: logging-log4j2
Updated Branches:
  refs/heads/master 7007b861c -> ae87a679f


LOG4J2-1583 Fixed scrambled log messages triggered by nested logging from toString() method of a logging parameter object.


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

Branch: refs/heads/master
Commit: ae87a679f4a04a2b1f3458fe62e469ae806a7eb9
Parents: 7007b86
Author: rpopma <rp...@apache.org>
Authored: Sat Sep 17 22:04:51 2016 +0900
Committer: rpopma <rp...@apache.org>
Committed: Sat Sep 17 22:04:51 2016 +0900

----------------------------------------------------------------------
 .../log4j/message/ReusableMessageFactory.java   |  15 ++-
 .../message/ReusableParameterizedMessage.java   |  12 +-
 .../logging/log4j/spi/AbstractLogger.java       |  78 ++++++-----
 .../message/ReusableMessageFactoryTest.java     | 130 ++++++++++++++++++
 .../logging/log4j/core/config/LoggerConfig.java |   8 +-
 .../log4j/core/impl/MutableLogEvent.java        |   1 +
 .../core/impl/ReusableLogEventFactory.java      |  20 ++-
 .../impl/NestedLoggingFromToStringTest.java     | 132 +++++++++++++++++++
 .../core/impl/ReusableLogEventFactoryTest.java  |  20 +++
 .../src/test/resources/log4j-sync-to-list.xml   |  35 +++++
 src/changes/changes.xml                         |   5 +-
 11 files changed, 416 insertions(+), 40 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ae87a679/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
index 9a6329b..d75ca01 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
@@ -55,7 +55,7 @@ public final class ReusableMessageFactory implements MessageFactory2, Serializab
             result = new ReusableParameterizedMessage();
             threadLocalParameterized.set(result);
         }
-        return result;
+        return result.reserved ? new ReusableParameterizedMessage().reserve() : result.reserve();
     }
 
     private static ReusableSimpleMessage getSimple() {
@@ -76,6 +76,19 @@ public final class ReusableMessageFactory implements MessageFactory2, Serializab
         return result;
     }
 
+    /**
+     * Switches the {@code reserved} flag off if the specified message is a ReusableParameterizedMessage,
+     * otherwise does nothing. This flag is used internally to verify that a reusable message is no longer in use and
+     * can be reused.
+     * @param message the message to make available again
+     * @since 2.7
+     */
+    public static void release(final Message message) { // LOG4J2-1583
+        if (message instanceof ReusableParameterizedMessage) {
+            ((ReusableParameterizedMessage) message).reserved = false;
+        }
+    }
+
     @Override
     public Message newMessage(final CharSequence charSequence) {
         final ReusableSimpleMessage result = getSimple();

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ae87a679/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
index 96f5394..85573b1 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
@@ -33,7 +33,7 @@ public class ReusableParameterizedMessage implements ReusableMessage {
     private static final int MIN_BUILDER_SIZE = 512;
     private static final int MAX_PARMS = 10;
     private static final long serialVersionUID = 7800075879295123856L;
-    private static ThreadLocal<StringBuilder> buffer = new ThreadLocal<>();
+    private ThreadLocal<StringBuilder> buffer = new ThreadLocal<>(); // non-static: LOG4J2-1583
 
     private String messagePattern;
     private int argCount;
@@ -42,6 +42,7 @@ public class ReusableParameterizedMessage implements ReusableMessage {
     private transient Object[] varargs;
     private transient Object[] params = new Object[MAX_PARMS];
     private transient Throwable throwable;
+    transient boolean reserved = false; // LOG4J2-1583 prevent scrambled logs with nested logging calls
 
     /**
      * Creates a reusable message.
@@ -308,6 +309,15 @@ public class ReusableParameterizedMessage implements ReusableMessage {
         }
     }
 
+    /**
+     * Sets the reserved flag to true and returns this object.
+     * @return this object
+     * @since 2.7
+     */
+    ReusableParameterizedMessage reserve() {
+        reserved = true;
+        return this;
+    }
 
     @Override
     public String toString() {

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ae87a679/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
index 475e93f..84ce567 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
@@ -169,14 +169,14 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable {
      */
     protected void catching(final String fqcn, final Level level, final Throwable t) {
         if (isEnabled(level, CATCHING_MARKER, (Object) null, null)) {
-            logMessage(fqcn, level, CATCHING_MARKER, catchingMsg(t), t);
+            logMessageSafely(fqcn, level, CATCHING_MARKER, catchingMsg(t), t);
         }
     }
 
     @Override
     public void catching(final Throwable t) {
         if (isEnabled(Level.ERROR, CATCHING_MARKER, (Object) null, null)) {
-            logMessage(FQCN, Level.ERROR, CATCHING_MARKER, catchingMsg(t), t);
+            logMessageSafely(FQCN, Level.ERROR, CATCHING_MARKER, catchingMsg(t), t);
         }
     }
 
@@ -501,7 +501,7 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable {
     protected EntryMessage enter(final String fqcn, final String format, final Supplier<?>... paramSuppliers) {
         EntryMessage entryMsg = null;
         if (isEnabled(Level.TRACE, ENTRY_MARKER, (Object) null, null)) {
-            logMessage(fqcn, Level.TRACE, ENTRY_MARKER, entryMsg = entryMsg(format, paramSuppliers), null);
+            logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, entryMsg = entryMsg(format, paramSuppliers), null);
         }
         return entryMsg;
     }
@@ -517,7 +517,7 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable {
     protected EntryMessage enter(final String fqcn, final String format, final MessageSupplier... paramSuppliers) {
         EntryMessage entryMsg = null;
         if (isEnabled(Level.TRACE, ENTRY_MARKER, (Object) null, null)) {
-            logMessage(fqcn, Level.TRACE, ENTRY_MARKER, entryMsg = entryMsg(format, paramSuppliers), null);
+            logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, entryMsg = entryMsg(format, paramSuppliers), null);
         }
         return entryMsg;
     }
@@ -532,7 +532,7 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable {
     protected EntryMessage enter(final String fqcn, final String format, final Object... params) {
         EntryMessage entryMsg = null;
         if (isEnabled(Level.TRACE, ENTRY_MARKER, (Object) null, null)) {
-            logMessage(fqcn, Level.TRACE, ENTRY_MARKER, entryMsg = entryMsg(format, params), null);
+            logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, entryMsg = entryMsg(format, params), null);
         }
         return entryMsg;
     }
@@ -547,7 +547,7 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable {
     protected EntryMessage enter(final String fqcn, final MessageSupplier msgSupplier) {
         EntryMessage message = null;
         if (isEnabled(Level.TRACE, ENTRY_MARKER, (Object) null, null)) {
-            logMessage(fqcn, Level.TRACE, ENTRY_MARKER, message = flowMessageFactory.newEntryMessage(
+            logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, message = flowMessageFactory.newEntryMessage(
                     msgSupplier.get()), null);
         }
         return message;
@@ -565,7 +565,7 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable {
     protected EntryMessage enter(final String fqcn, final Message message) {
         EntryMessage flowMessage = null;
         if (isEnabled(Level.TRACE, ENTRY_MARKER, (Object) null, null)) {
-            logMessage(fqcn, Level.TRACE, ENTRY_MARKER, flowMessage = flowMessageFactory.newEntryMessage(message),
+            logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, flowMessage = flowMessageFactory.newEntryMessage(message),
                     null);
         }
         return flowMessage;
@@ -590,9 +590,9 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable {
     protected void entry(final String fqcn, final Object... params) {
         if (isEnabled(Level.TRACE, ENTRY_MARKER, (Object) null, null)) {
             if (params == null) {
-                logMessage(fqcn, Level.TRACE, ENTRY_MARKER, entryMsg(null, (Supplier<?>[]) null), null);
+                logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, entryMsg(null, (Supplier<?>[]) null), null);
             } else {
-                logMessage(fqcn, Level.TRACE, ENTRY_MARKER, entryMsg(null, params), null);
+                logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, entryMsg(null, params), null);
             }
         }
     }
@@ -1810,7 +1810,7 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable {
     public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final Message msg,
             final Throwable t) {
         if (isEnabled(level, marker, msg, t)) {
-            logMessage(fqcn, level, marker, msg, t);
+            logMessageSafely(fqcn, level, marker, msg, t);
         }
     }
 
@@ -1963,117 +1963,117 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable {
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final CharSequence message,
             final Throwable t) {
-        logMessage(fqcn, level, marker, messageFactory.newMessage(message), t);
+        logMessageSafely(fqcn, level, marker, messageFactory.newMessage(message), t);
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final Object message,
             final Throwable t) {
-        logMessage(fqcn, level, marker, messageFactory.newMessage(message), t);
+        logMessageSafely(fqcn, level, marker, messageFactory.newMessage(message), t);
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker,
             final MessageSupplier msgSupplier, final Throwable t) {
         final Message message = LambdaUtil.get(msgSupplier);
-        logMessage(fqcn, level, marker, message, t);
+        logMessageSafely(fqcn, level, marker, message, t);
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final Supplier<?> msgSupplier,
             final Throwable t) {
         final Message message = LambdaUtil.getMessage(msgSupplier, messageFactory);
-        logMessage(fqcn, level, marker, message, t);
+        logMessageSafely(fqcn, level, marker, message, t);
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message,
             final Throwable t) {
-        logMessage(fqcn, level, marker, messageFactory.newMessage(message), t);
+        logMessageSafely(fqcn, level, marker, messageFactory.newMessage(message), t);
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message) {
         final Message msg = messageFactory.newMessage(message);
-        logMessage(fqcn, level, marker, msg, msg.getThrowable());
+        logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message,
             final Object... params) {
         final Message msg = messageFactory.newMessage(message, params);
-        logMessage(fqcn, level, marker, msg, msg.getThrowable());
+        logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message,
             final Object p0) {
         final Message msg = messageFactory.newMessage(message, p0);
-        logMessage(fqcn, level, marker, msg, msg.getThrowable());
+        logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message,
             final Object p0, final Object p1) {
         final Message msg = messageFactory.newMessage(message, p0, p1);
-        logMessage(fqcn, level, marker, msg, msg.getThrowable());
+        logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message,
             final Object p0, final Object p1, final Object p2) {
         final Message msg = messageFactory.newMessage(message, p0, p1, p2);
-        logMessage(fqcn, level, marker, msg, msg.getThrowable());
+        logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message,
             final Object p0, final Object p1, final Object p2, final Object p3) {
         final Message msg = messageFactory.newMessage(message, p0, p1, p2, p3);
-        logMessage(fqcn, level, marker, msg, msg.getThrowable());
+        logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message,
             final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) {
         final Message msg = messageFactory.newMessage(message, p0, p1, p2, p3, p4);
-        logMessage(fqcn, level, marker, msg, msg.getThrowable());
+        logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message,
             final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5) {
         final Message msg = messageFactory.newMessage(message, p0, p1, p2, p3, p4, p5);
-        logMessage(fqcn, level, marker, msg, msg.getThrowable());
+        logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message,
             final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
             final Object p6) {
         final Message msg = messageFactory.newMessage(message, p0, p1, p2, p3, p4, p5, p6);
-        logMessage(fqcn, level, marker, msg, msg.getThrowable());
+        logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message,
             final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
             final Object p6, final Object p7) {
         final Message msg = messageFactory.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7);
-        logMessage(fqcn, level, marker, msg, msg.getThrowable());
+        logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message,
             final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
             final Object p6, final Object p7, final Object p8) {
         final Message msg = messageFactory.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8);
-        logMessage(fqcn, level, marker, msg, msg.getThrowable());
+        logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message,
             final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
             final Object p6, final Object p7, final Object p8, final Object p9) {
         final Message msg = messageFactory.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
-        logMessage(fqcn, level, marker, msg, msg.getThrowable());
+        logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());
     }
 
     protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message,
             final Supplier<?>... paramSuppliers) {
         final Message msg = messageFactory.newMessage(message, LambdaUtil.getAll(paramSuppliers));
-        logMessage(fqcn, level, marker, msg, msg.getThrowable());
+        logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());
     }
 
     @Override
     public void printf(final Level level, final Marker marker, final String format, final Object... params) {
         if (isEnabled(level, marker, format, params)) {
             final Message msg = new StringFormattedMessage(format, params);
-            logMessage(FQCN, level, marker, msg, msg.getThrowable());
+            logMessageSafely(FQCN, level, marker, msg, msg.getThrowable());
         }
     }
 
@@ -2081,7 +2081,17 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable {
     public void printf(final Level level, final String format, final Object... params) {
         if (isEnabled(level, null, format, params)) {
             final Message msg = new StringFormattedMessage(format, params);
-            logMessage(FQCN, level, null, msg, msg.getThrowable());
+            logMessageSafely(FQCN, level, null, msg, msg.getThrowable());
+        }
+    }
+
+    private void logMessageSafely(final String fqcn, final Level level, final Marker marker, final Message msg,
+            final Throwable throwable) {
+        try {
+            logMessage(fqcn, level, marker, msg, throwable);
+        } finally {
+            // LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
+            ReusableMessageFactory.release(msg);
         }
     }
 
@@ -2106,7 +2116,7 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable {
      */
     protected <T extends Throwable> T throwing(final String fqcn, final Level level, final T t) {
         if (isEnabled(level, THROWING_MARKER, (Object) null, null)) {
-            logMessage(fqcn, level, THROWING_MARKER, throwingMsg(t), t);
+            logMessageSafely(fqcn, level, THROWING_MARKER, throwingMsg(t), t);
         }
         return t;
     }
@@ -2414,7 +2424,7 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable {
     public void traceExit(final EntryMessage message) {
         // If the message is null, traceEnter returned null because flow logging was disabled, we can optimize out calling isEnabled().
         if (message != null && isEnabled(Level.TRACE, EXIT_MARKER, message, null)) {
-            logMessage(FQCN, Level.TRACE, EXIT_MARKER, flowMessageFactory.newExitMessage(message), null);
+            logMessageSafely(FQCN, Level.TRACE, EXIT_MARKER, flowMessageFactory.newExitMessage(message), null);
         }
     }
 
@@ -2422,7 +2432,7 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable {
     public <R> R traceExit(final EntryMessage message, final R result) {
         // If the message is null, traceEnter returned null because flow logging was disabled, we can optimize out calling isEnabled().
         if (message != null && isEnabled(Level.TRACE, EXIT_MARKER, message, null)) {
-            logMessage(FQCN, Level.TRACE, EXIT_MARKER, flowMessageFactory.newExitMessage(result, message), null);
+            logMessageSafely(FQCN, Level.TRACE, EXIT_MARKER, flowMessageFactory.newExitMessage(result, message), null);
         }
         return result;
     }
@@ -2431,7 +2441,7 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable {
     public <R> R traceExit(final Message message, final R result) {
         // If the message is null, traceEnter returned null because flow logging was disabled, we can optimize out calling isEnabled().
         if (message != null && isEnabled(Level.TRACE, EXIT_MARKER, message, null)) {
-            logMessage(FQCN, Level.TRACE, EXIT_MARKER, flowMessageFactory.newExitMessage(result, message), null);
+            logMessageSafely(FQCN, Level.TRACE, EXIT_MARKER, flowMessageFactory.newExitMessage(result, message), null);
         }
         return result;
     }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ae87a679/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java
new file mode 100644
index 0000000..6c62503
--- /dev/null
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.message;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests the ReusableMessageFactory class.
+ */
+public class ReusableMessageFactoryTest {
+
+    @Test
+    public void testCreateEventReturnsDifferentInstanceIfNotReleased() throws Exception {
+        final ReusableMessageFactory factory = new ReusableMessageFactory();
+        final Message message1 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4);
+        final Message message2 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 9, 8, 7, 6);
+        assertNotSame(message1, message2);
+        ReusableMessageFactory.release(message1);
+        ReusableMessageFactory.release(message2);
+    }
+
+    @Test
+    public void testCreateEventReturnsSameInstance() throws Exception {
+        final ReusableMessageFactory factory = new ReusableMessageFactory();
+        final Message message1 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4);
+
+        ReusableMessageFactory.release(message1);
+        final Message message2 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 9, 8, 7, 6);
+        assertSame(message1, message2);
+
+        ReusableMessageFactory.release(message2);
+        final Message message3 = factory.newMessage("text, AAA={} BBB={} p2={} p3={}", 9, 8, 7, 6);
+        assertSame(message2, message3);
+        ReusableMessageFactory.release(message3);
+    }
+
+    private void assertReusableParameterizeMessage(final Message message, final String txt, final Object[] params) {
+        assertTrue(message instanceof ReusableParameterizedMessage);
+        ReusableParameterizedMessage msg = (ReusableParameterizedMessage) message;
+        assertTrue("reserved", msg.reserved);
+
+        assertEquals(txt, msg.getFormat());
+        assertEquals("count", msg.getParameterCount(), params.length);
+        Object[] messageParams = msg.getParameters();
+        for (int i = 0; i < params.length; i++) {
+            assertEquals(messageParams[i], params[i]);
+        }
+    }
+
+    @Test
+    public void testCreateEventOverwritesFields() throws Exception {
+        final ReusableMessageFactory factory = new ReusableMessageFactory();
+        final Message message1 = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4);
+        assertReusableParameterizeMessage(message1, "text, p0={} p1={} p2={} p3={}", new Object[]{
+                new Integer(1), //
+                new Integer(2), //
+                new Integer(3), //
+                new Integer(4), //
+        });
+
+        ReusableMessageFactory.release(message1);
+        final Message message2 = factory.newMessage("other, A={} B={} C={} D={}", 1, 2, 3, 4);
+        assertReusableParameterizeMessage(message1, "other, A={} B={} C={} D={}", new Object[]{
+                new Integer(1), //
+                new Integer(2), //
+                new Integer(3), //
+                new Integer(4), //
+        });
+        assertSame(message1, message2);
+        ReusableMessageFactory.release(message2);
+    }
+
+    @Test
+    public void testCreateEventReturnsThreadLocalInstance() throws Exception {
+        final ReusableMessageFactory factory = new ReusableMessageFactory();
+        final Message[] message1 = new Message[1];
+        final Message[] message2 = new Message[1];
+        final Thread t1 = new Thread("THREAD 1") {
+            @Override
+            public void run() {
+                message1[0] = factory.newMessage("text, p0={} p1={} p2={} p3={}", 1, 2, 3, 4);
+            }
+        };
+        final Thread t2 = new Thread("Thread 2") {
+            @Override
+            public void run() {
+                message2[0] = factory.newMessage("other, A={} B={} C={} D={}", 1, 2, 3, 4);
+            }
+        };
+        t1.start();
+        t2.start();
+        t1.join();
+        t2.join();
+        assertNotNull(message1[0]);
+        assertNotNull(message2[0]);
+        assertNotSame(message1[0], message2[0]);
+        assertReusableParameterizeMessage(message1[0], "text, p0={} p1={} p2={} p3={}", new Object[]{
+                new Integer(1), //
+                new Integer(2), //
+                new Integer(3), //
+                new Integer(4), //
+        });
+
+        assertReusableParameterizeMessage(message2[0], "other, A={} B={} C={} D={}", new Object[]{
+                new Integer(1), //
+                new Integer(2), //
+                new Integer(3), //
+                new Integer(4), //
+        });
+        ReusableMessageFactory.release(message1[0]);
+        ReusableMessageFactory.release(message2[0]);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ae87a679/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 cc125b5..98b586f 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
@@ -397,7 +397,13 @@ public class LoggerConfig extends AbstractFilterable {
                 }
             }
         }
-        log(logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t));
+        final LogEvent logEvent = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t);
+        try {
+            log(logEvent);
+        } finally {
+            // LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
+            ReusableLogEventFactory.release(logEvent);
+        }
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ae87a679/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
index f0a20c9..68f7b78 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
@@ -61,6 +61,7 @@ public class MutableLogEvent implements LogEvent, ReusableMessage {
     private String loggerFqcn;
     private StackTraceElement source;
     private ThreadContext.ContextStack contextStack;
+    transient boolean reserved = false;
 
     public MutableLogEvent() {
         this(new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE), new Object[10]);

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ae87a679/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
index 7b27386..825f1f7 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
@@ -59,15 +59,19 @@ public class ReusableLogEventFactory implements LogEventFactory {
                                 final String fqcn, final Level level, final Message message,
                                 final List<Property> properties, final Throwable t) {
         MutableLogEvent result = mutableLogEventThreadLocal.get();
-        if (result == null) {
+        if (result == null || result.reserved) {
+            final boolean initThreadLocal = result == null;
             result = new MutableLogEvent();
 
             // usually no need to re-initialize thread-specific fields since the event is stored in a ThreadLocal
             result.setThreadId(Thread.currentThread().getId());
             result.setThreadName(Thread.currentThread().getName()); // Thread.getName() allocates Objects on each call
             result.setThreadPriority(Thread.currentThread().getPriority());
-            mutableLogEventThreadLocal.set(result);
+            if (initThreadLocal) {
+                mutableLogEventThreadLocal.set(result);
+            }
         }
+        result.reserved = true;
         result.clear(); // ensure any previously cached values (thrownProxy, source, etc.) are cleared
 
         result.setLoggerName(loggerName);
@@ -89,4 +93,16 @@ public class ReusableLogEventFactory implements LogEventFactory {
         }
         return result;
     }
+
+    /**
+     * Switches the {@code reserved} flag off if the specified event is a MutableLogEvent, otherwise does nothing.
+     * This flag is used internally to verify that a reusable log event is no longer in use and can be reused.
+     * @param logEvent the log event to make available again
+     * @since 2.7
+     */
+    public static void release(final LogEvent logEvent) { // LOG4J2-1583
+        if (logEvent instanceof MutableLogEvent) {
+            ((MutableLogEvent) logEvent).reserved = false;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ae87a679/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java
new file mode 100644
index 0000000..995232a
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.impl;
+
+import java.util.List;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * There are two logger.info() calls here.
+ *
+ * The outer one (in main()) indirectly invokes the inner one in the Thing.toString() method.
+ *
+ * The inner one comes out cleanly, but leaves ReusableParameterizedMessage.indices altered and this messes up
+ * the output of the outer call (which is still on the stack).
+ *
+ * <pre>
+ *     16:35:34.781 INFO  [main] problem.demo.apache.log4j2.Log4j2ProblemDemo - getX: values x=3 y=4 z=5
+ *     16:35:34.782 INFO  [main] problem.demo.apache.log4j2.Log4j2ProblemDemo - getX: values x=3 y=4 z=5[Thing x=3 y=4 z=5]
+ * </pre>
+ * @author lwest
+ * @since 2016-09-14 in recursion
+ */
+public class NestedLoggingFromToStringTest {
+
+    @Rule
+    public LoggerContextRule context = new LoggerContextRule("log4j-sync-to-list.xml");
+    private ListAppender listAppender;
+    private Logger logger;
+
+    @Before
+    public void before() {
+        listAppender = context.getListAppender("List");
+        logger = LogManager.getLogger(NestedLoggingFromToStringTest.class);
+    }
+
+    static class ParameterizedLoggingThing {
+        final Logger innerLogger = LogManager.getLogger(ParameterizedLoggingThing.class);
+        private int x = 3, y = 4, z = 5;
+        public int getX() {
+            innerLogger.debug("getX: values x={} y={} z={}", x, y, z);
+            return x;
+        }
+        @Override public String toString() {
+            return "[" + this.getClass().getSimpleName() + " x=" + getX() + " y=" + y  + " z=" + z + "]";
+        }
+    }
+
+    static class ObjectLoggingThing1 {
+        final Logger innerLogger = LogManager.getLogger(ObjectLoggingThing1.class);
+        public int getX() {
+            innerLogger.trace(new ObjectLoggingThing2());
+            return 999;
+        }
+        @Override public String toString() {
+            return "[" + this.getClass().getSimpleName() + " y=" + getX() + "]";
+        }
+    }
+
+    static class ObjectLoggingThing2 {
+        final Logger innerLogger = LogManager.getLogger(ObjectLoggingThing2.class);
+        public int getX() {
+            innerLogger.trace(new ParameterizedLoggingThing());
+            return 123;
+        }
+        @Override public String toString() {
+            return "[" + this.getClass().getSimpleName() + " x=" + getX() + "]";
+        }
+    }
+
+    @Test
+    public void testNestedLoggingInLastArgument() {
+        ParameterizedLoggingThing it = new ParameterizedLoggingThing();
+        logger.info("main: argCount={} it={}", "2", it);
+        final List<String> list = listAppender.getMessages();
+
+        String expect1 = "DEBUG org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest$ParameterizedLoggingThing getX: values x=3 y=4 z=5";
+        String expect2 = "INFO org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest main: argCount=2 it=[ParameterizedLoggingThing x=3 y=4 z=5]";
+        assertEquals(expect1, list.get(0));
+        assertEquals(expect2, list.get(1));
+    }
+
+    @Test
+    public void testNestedLoggingInFirstArgument() {
+        ParameterizedLoggingThing it = new ParameterizedLoggingThing();
+        logger.info("next: it={} some{} other{}", it, "AA", "BB");
+        final List<String> list = listAppender.getMessages();
+
+        String expect1 = "DEBUG org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest$ParameterizedLoggingThing getX: values x=3 y=4 z=5";
+        String expect2 = "INFO org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest next: it=[ParameterizedLoggingThing x=3 y=4 z=5] someAA otherBB";
+        assertEquals(expect1, list.get(0));
+        assertEquals(expect2, list.get(1));
+    }
+
+    @Test
+    public void testDoublyNestedLogging() {
+        logger.info(new ObjectLoggingThing1());
+        final List<String> list = listAppender.getMessages();
+
+        String expect1 = "DEBUG org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest$ParameterizedLoggingThing getX: values x=3 y=4 z=5";
+        String expect2 = "TRACE org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest$ObjectLoggingThing2 [ParameterizedLoggingThing x=3 y=4 z=5]";
+        String expect3 = "TRACE org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest$ObjectLoggingThing1 [ObjectLoggingThing2 x=123]";
+        String expect4 = "INFO org.apache.logging.log4j.core.impl.NestedLoggingFromToStringTest [ObjectLoggingThing1 y=999]";
+        assertEquals(expect1, list.get(0));
+        assertEquals(expect2, list.get(1));
+        assertEquals(expect3, list.get(2));
+        assertEquals(expect4, list.get(3));
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ae87a679/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java
index cd41263..dfc1e90 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java
@@ -30,11 +30,27 @@ import static org.junit.Assert.*;
 public class ReusableLogEventFactoryTest {
 
     @Test
+    public void testCreateEventReturnsDifferentInstanceIfNotReleased() throws Exception {
+        final ReusableLogEventFactory factory = new ReusableLogEventFactory();
+        final LogEvent event1 = callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null);
+        final LogEvent event2 = callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null);
+        assertNotSame(event1, event2);
+        ReusableLogEventFactory.release(event1);
+        ReusableLogEventFactory.release(event2);
+    }
+
+    @Test
     public void testCreateEventReturnsSameInstance() throws Exception {
         final ReusableLogEventFactory factory = new ReusableLogEventFactory();
         final LogEvent event1 = callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null);
+        ReusableLogEventFactory.release(event1);
         final LogEvent event2 = callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null);
         assertSame(event1, event2);
+
+        ReusableLogEventFactory.release(event2);
+        final LogEvent event3 = callCreateEvent(factory, "c", Level.INFO, new SimpleMessage("123"), null);
+        assertSame(event2, event3);
+        ReusableLogEventFactory.release(event3);
     }
 
     @Test
@@ -45,6 +61,7 @@ public class ReusableLogEventFactoryTest {
         assertEquals("level", Level.DEBUG, event1.getLevel());
         assertEquals("msg", new SimpleMessage("abc"), event1.getMessage());
 
+        ReusableLogEventFactory.release(event1);
         final LogEvent event2 = callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null);
         assertSame(event1, event2);
 
@@ -96,12 +113,15 @@ public class ReusableLogEventFactoryTest {
         assertEquals("msg", new SimpleMessage("xyz"), event2[0].getMessage());
         assertEquals("thread name", "Thread 2", event2[0].getThreadName());
         assertEquals("tid", t2.getId(), event2[0].getThreadId());
+        ReusableLogEventFactory.release(event1[0]);
+        ReusableLogEventFactory.release(event2[0]);
     }
 
     @Test
     public void testCreateEventInitFieldsProperly() throws Exception {
         final ReusableLogEventFactory factory = new ReusableLogEventFactory();
         final LogEvent event = callCreateEvent(factory, "logger", Level.INFO, new SimpleMessage("xyz"), null);
+        ReusableLogEventFactory.release(event);
         assertNotNull(event.getContextMap());
         assertNotNull(event.getContextStack());
     }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ae87a679/log4j-core/src/test/resources/log4j-sync-to-list.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/log4j-sync-to-list.xml b/log4j-core/src/test/resources/log4j-sync-to-list.xml
new file mode 100644
index 0000000..6030bca
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-sync-to-list.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+
+-->
+<Configuration status="OFF" name="NestedLoggingInToString">
+
+  <Appenders>
+    <Console name="STDOUT">
+      <PatternLayout pattern="%c %m%n"/>
+    </Console>
+    <List name="List">
+      <PatternLayout pattern="%level %logger %m"/>
+    </List>
+  </Appenders>
+
+  <Loggers>
+    <Root level="trace">
+      <AppenderRef ref="List"/>
+    </Root>
+  </Loggers>
+</Configuration>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/ae87a679/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index d4f9371..f1df943 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -24,8 +24,11 @@
   </properties>
   <body>
     <release version="2.7" date="2016-MM-DD" description="GA Release 2.7">
+      <action issue="LOG4J2-1583" dev="rpopma" type="fix" due-to="Larry West">
+        Fixed scrambled log messages triggered by nested logging from toString() method of a logging parameter object.
+      </action>
       <action issue="LOG4J2-1259" dev="ggregory" type="fix" due-to="Misagh Moayyed, Steffen Offermann">
-        Log4j threads are leaking on Tomcat shutdown.
+        Log4j threads are no longer leaking on Tomcat shutdown.
       </action>
       <action issue="LOG4J2-1051" dev="rpopma" type="fix" due-to="Lukasz Lenart">
         When starting on Google App Engine, Interpolator now suppresses the NoClassDefFoundError stack trace  for the jvmrunargs lookup.