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

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


The following commit(s) were added to refs/heads/2.x by this push:
     new c41feeddad fix: make `%ex` behavior context-independent (#3919)
c41feeddad is described below

commit c41feeddad05f96fc73d3c0fb6a9da6763431aec
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Fri Sep 12 13:00:06 2025 +0200

    fix: make `%ex` behavior context-independent (#3919)
    
    Historically, throwable pattern converters (`%ex`, `%xEx`, etc.) behaved in 
a context-sensitive way:
    
    * If the **preceding formatter’s expansion** did not end with whitespace, 
the converter automatically inserted a space before rendering the exception.
    
    In version `2.25.0`, this was changed to insert a **newline** instead of a 
space, but the behavior was still dependent on surrounding context.
    
    #### What this change does
    
    This PR removes the context-dependent behavior altogether and makes `%ex` 
expansion fully predictable, while remaining backward-compatible:
    
    * When `%ex` is **added implicitly** because `alwaysWriteExceptions=true`:
    
      * If the pattern already ends with `%n`, a plain `%ex` is appended.
      * Otherwise, `%notEmpty{%n%ex}` is appended.
        This ensures exceptions are always clearly separated from the main log 
message by a newline, without adding extra characters when no exception is 
present.
    * When `%ex` is **explicitly included** in the pattern by the user:
    
      * Its expansion is rendered exactly as written in the pattern.
      * It will **never** prepend a newline on its own.
    
    #### Why
    
    * Eliminates confusing context-sensitive behavior.
    * Makes output consistent and predictable.
    * Preserves legacy expectations by only modifying implicitly added `%ex`.
    
    Closes #3873
    
    Co-authored-by: Volkan Yazıcı <[email protected]>
---
 .../log4j/core/pattern/PatternParserTest.java      | 44 ++++++++++++++++++++-
 .../pattern/RootThrowablePatternConverterTest.java |  4 +-
 .../pattern/ThrowablePatternConverterTest.java     |  6 ++-
 .../logging/log4j/core/pattern/PatternParser.java  | 21 +++++++++-
 .../core/pattern/ThrowableStackTraceRenderer.java  | 10 -----
 .../VariablesNotEmptyReplacementConverter.java     |  3 +-
 .../logging/log4j/taglib/CatchingTagTest.java      |  7 ++--
 .../apache/logging/log4j/taglib/EnterTagTest.java  |  4 +-
 .../apache/logging/log4j/taglib/ExitTagTest.java   |  6 +--
 .../log4j/taglib/LoggingMessageTagSupportTest.java | 46 ++++++++++++----------
 log4j-taglib/src/test/resources/log4j-test1.xml    |  2 +-
 .../.2.x.x/3873_throwable_converter_new_line.xml   | 13 ++++++
 12 files changed, 117 insertions(+), 49 deletions(-)

diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java
index f493e25bb6..7a82831b96 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.logging.log4j.core.pattern;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -23,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.Calendar;
 import java.util.List;
+import java.util.stream.Stream;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.MarkerManager;
 import org.apache.logging.log4j.core.LogEvent;
@@ -40,6 +42,8 @@ import org.apache.logging.log4j.util.StringMap;
 import org.apache.logging.log4j.util.Strings;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
 
 class PatternParserTest {
 
@@ -98,6 +102,8 @@ class PatternParserTest {
         assertNotNull(formatters);
         final StringMap mdc = ContextDataFactory.createContextData();
         mdc.putValue("loginId", "Fred");
+        // The line number of the Throwable definition
+        final int nextLineNumber = 107;
         final Throwable t = new Throwable();
         final StackTraceElement[] elements = t.getStackTrace();
         final Log4jLogEvent event = Log4jLogEvent.newBuilder() //
@@ -116,8 +122,9 @@ class PatternParserTest {
             formatter.format(event, buf);
         }
         final String str = buf.toString();
-        final String expected = "INFO  [PatternParserTest        :101 ] - 
Hello, world" + Strings.LINE_SEPARATOR;
-        assertTrue(str.endsWith(expected), "Expected to end with: " + expected 
+ ". Actual: " + str);
+        final String expected =
+                "INFO  [PatternParserTest        :" + nextLineNumber + " ] - 
Hello, world" + Strings.LINE_SEPARATOR;
+        assertThat(str).endsWith(expected);
     }
 
     @Test
@@ -369,6 +376,39 @@ class PatternParserTest {
         validateConverter(formatters, 1, "Date");
     }
 
+    static Stream<String> testAlwaysWriteExceptions_ensuresPrecededByNewline() 
{
+        return Stream.of("", "%m", "%n", "%m%n");
+    }
+
+    @ParameterizedTest
+    @MethodSource
+    void testAlwaysWriteExceptions_ensuresPrecededByNewline(final String 
pattern) {
+        final List<PatternFormatter> formatters = parser.parse(pattern, true, 
false, false);
+        assertNotNull(formatters);
+        if (pattern.endsWith("%n")) {
+            // Case 1: the original pattern ends with a new line, so the last 
converter is a ThrowablePatternConverter
+            assertThat(formatters).hasSizeGreaterThan(1);
+            final LogEventPatternConverter lastConverter =
+                    formatters.get(formatters.size() - 1).getConverter();
+            
assertThat(lastConverter).isInstanceOf(ThrowablePatternConverter.class);
+            LogEventPatternConverter secondLastConverter =
+                    formatters.get(formatters.size() - 2).getConverter();
+            
assertThat(secondLastConverter).isInstanceOf(LineSeparatorPatternConverter.class);
+        } else {
+            // Case 2: the original pattern does not end with a new line, so 
we add a composite converter
+            // that appends a new line and the exception if an exception is 
present.
+            assertThat(formatters).hasSizeGreaterThan(0);
+            final LogEventPatternConverter lastConverter =
+                    formatters.get(formatters.size() - 1).getConverter();
+            
assertThat(lastConverter).isInstanceOf(VariablesNotEmptyReplacementConverter.class);
+            final List<PatternFormatter> nestedFormatters =
+                    ((VariablesNotEmptyReplacementConverter) 
lastConverter).formatters;
+            assertThat(nestedFormatters).hasSize(2);
+            
assertThat(nestedFormatters.get(0).getConverter()).isInstanceOf(LineSeparatorPatternConverter.class);
+            
assertThat(nestedFormatters.get(1).getConverter()).isInstanceOf(ThrowablePatternConverter.class);
+        }
+    }
+
     @Test
     void testExceptionWithFilters() {
         final List<PatternFormatter> formatters =
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverterTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverterTest.java
index b7cee50b3a..80e4f008bf 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverterTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverterTest.java
@@ -91,11 +91,11 @@ class RootThrowablePatternConverterTest {
 
         @Test
         @Override
-        void output_should_be_newline_prefixed() {
+        void output_should_not_be_newline_prefixed() {
             final String pattern = "%p" + patternPrefix;
             final String stackTrace = convert(pattern);
             final String expectedStart = String.format(
-                    "%s%n[CIRCULAR REFERENCE: %s", LEVEL, 
EXCEPTION.getClass().getCanonicalName());
+                    "%s[CIRCULAR REFERENCE: %s", LEVEL, 
EXCEPTION.getClass().getCanonicalName());
             assertThat(stackTrace).as("pattern=`%s`", 
pattern).startsWith(expectedStart);
         }
 
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverterTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverterTest.java
index e178526fd9..24230dd23b 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverterTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverterTest.java
@@ -37,6 +37,7 @@ import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.junitpioneer.jupiter.Issue;
 
 /**
  * {@link ThrowablePatternConverter} tests.
@@ -384,11 +385,12 @@ public class ThrowablePatternConverterTest {
         }
 
         @Test
-        void output_should_be_newline_prefixed() {
+        @Issue("https://github.com/apache/logging-log4j2/issues/3873";)
+        void output_should_not_be_newline_prefixed() {
             final String pattern = "%p" + patternPrefix;
             final String stackTrace = convert(pattern);
             final String expectedStart =
-                    String.format("%s%n%s", LEVEL, 
EXCEPTION.getClass().getCanonicalName());
+                    String.format("%s%s", LEVEL, 
EXCEPTION.getClass().getCanonicalName());
             assertThat(stackTrace).as("pattern=`%s`", 
pattern).startsWith(expectedStart);
         }
 
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java
index 767bbda01a..332ee535c4 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java
@@ -212,7 +212,26 @@ public final class PatternParser {
             list.add(new PatternFormatter(pc, field));
         }
         if (alwaysWriteExceptions && !handlesThrowable) {
-            final LogEventPatternConverter pc = 
ThrowablePatternConverter.newInstance(config, new String[0]);
+            // We need to guarantee that an exception is always written,
+            // and that it is cleanly separated from the main log line by a 
newline.
+            final LogEventPatternConverter pc;
+            // Look at the last converter in the existing pattern.
+            final PatternFormatter lastFormatter = list.isEmpty() ? null : 
list.get(list.size() - 1);
+
+            if (lastFormatter == null || !(lastFormatter.getConverter() 
instanceof LineSeparatorPatternConverter)) {
+                // Case 1: The pattern does NOT already end with a newline.
+                // In this case, we append a composite converter 
`%notEmpty{%n%ex}`.
+                // - If no exception is present, it renders nothing (so the 
pattern behaves exactly as before).
+                // - If an exception is present, it renders a newline followed 
by the stack trace.
+                pc = VariablesNotEmptyReplacementConverter.newInstance(config, 
new String[] {"%n%ex"});
+            } else {
+                // Case 2: The pattern already ends with a newline.
+                // In this case, we only need to add `%ex`:
+                // - If no exception is present, nothing changes.
+                // - If an exception is present, it is written immediately 
after the newline already in the pattern.
+                pc = ThrowablePatternConverter.newInstance(config, 
Strings.EMPTY_ARRAY);
+            }
+            // Finally, add the chosen converter to the end of the pattern.
             list.add(new PatternFormatter(pc, FormattingInfo.getDefault()));
         }
         return list;
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowableStackTraceRenderer.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowableStackTraceRenderer.java
index a6211147a8..b16e9b9836 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowableStackTraceRenderer.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowableStackTraceRenderer.java
@@ -16,8 +16,6 @@
  */
 package org.apache.logging.log4j.core.pattern;
 
-import static org.apache.logging.log4j.util.Strings.LINE_SEPARATOR;
-
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -55,7 +53,6 @@ class ThrowableStackTraceRenderer<C extends 
ThrowableStackTraceRenderer.Context>
         if (maxLineCount > 0) {
             try {
                 C context = createContext(throwable);
-                ensureNewlineSuffix(buffer);
                 renderThrowable(buffer, throwable, context, new HashSet<>(), 
lineSeparator);
             } catch (final Exception error) {
                 if (error != MAX_LINE_COUNT_EXCEEDED) {
@@ -65,13 +62,6 @@ class ThrowableStackTraceRenderer<C extends 
ThrowableStackTraceRenderer.Context>
         }
     }
 
-    private static void ensureNewlineSuffix(final StringBuilder buffer) {
-        final int bufferLength = buffer.length();
-        if (bufferLength > 0 && buffer.charAt(bufferLength - 1) != '\n') {
-            buffer.append(LINE_SEPARATOR);
-        }
-    }
-
     @SuppressWarnings("unchecked")
     C createContext(final Throwable throwable) {
         final Map<Throwable, Context.Metadata> metadataByThrowable = 
Context.Metadata.ofThrowable(throwable);
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverter.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverter.java
index e5d2ba9ecf..7fda5791d5 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverter.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverter.java
@@ -35,7 +35,8 @@ import org.apache.logging.log4j.util.PerformanceSensitive;
 @PerformanceSensitive("allocation")
 public final class VariablesNotEmptyReplacementConverter extends 
LogEventPatternConverter {
 
-    private final List<PatternFormatter> formatters;
+    // package private for testing
+    final List<PatternFormatter> formatters;
 
     /**
      * Constructs the converter.
diff --git 
a/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/CatchingTagTest.java
 
b/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/CatchingTagTest.java
index 10ff9c1b2c..9134c16e34 100644
--- 
a/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/CatchingTagTest.java
+++ 
b/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/CatchingTagTest.java
@@ -57,7 +57,7 @@ public class CatchingTagTest {
         this.tag.setException(new Exception("This is a test."));
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Catching ERROR M-CATCHING[ EXCEPTION ] E" + LINE_SEPARATOR + 
"java.lang.Exception: This is a test.");
+        verify("Catching ERROR M-CATCHING[ EXCEPTION ] E-java.lang.Exception: 
This is a test.");
     }
 
     @Test
@@ -66,8 +66,7 @@ public class CatchingTagTest {
         this.tag.setException(new RuntimeException("This is another test."));
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Catching INFO M-CATCHING[ EXCEPTION ] E" + LINE_SEPARATOR
-                + "java.lang.RuntimeException: This is another test.");
+        verify("Catching INFO M-CATCHING[ EXCEPTION ] 
E-java.lang.RuntimeException: This is another test.");
     }
 
     @Test
@@ -76,7 +75,7 @@ public class CatchingTagTest {
         this.tag.setException(new Error("This is the last test."));
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Catching WARN M-CATCHING[ EXCEPTION ] E" + LINE_SEPARATOR + 
"java.lang.Error: This is the last test.");
+        verify("Catching WARN M-CATCHING[ EXCEPTION ] E-java.lang.Error: This 
is the last test.");
     }
 
     private void verify(final String expected) {
diff --git 
a/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/EnterTagTest.java 
b/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/EnterTagTest.java
index 3cae73434c..5f864ee1fb 100644
--- 
a/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/EnterTagTest.java
+++ 
b/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/EnterTagTest.java
@@ -54,7 +54,7 @@ public class EnterTagTest {
     @Test
     public void testDoEndTag() throws Exception {
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Enter TRACE M-ENTER[ FLOW ] E");
+        verify("Enter TRACE M-ENTER[ FLOW ] E-");
     }
 
     @Test
@@ -63,7 +63,7 @@ public class EnterTagTest {
         this.tag.setDynamicAttribute(null, null, 5792);
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Enter params(log4j-test1.xml, 5792) TRACE M-ENTER[ FLOW ] E");
+        verify("Enter params(log4j-test1.xml, 5792) TRACE M-ENTER[ FLOW ] E-");
     }
 
     private void verify(final String expected) {
diff --git 
a/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/ExitTagTest.java 
b/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/ExitTagTest.java
index 1e2fa7d79f..3f387b15cf 100644
--- 
a/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/ExitTagTest.java
+++ 
b/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/ExitTagTest.java
@@ -53,7 +53,7 @@ public class ExitTagTest {
     @Test
     public void testDoEndTag() throws Exception {
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Exit TRACE M-EXIT[ FLOW ] E");
+        verify("Exit TRACE M-EXIT[ FLOW ] E-");
     }
 
     @Test
@@ -61,7 +61,7 @@ public class ExitTagTest {
         this.tag.setResult(CONFIG);
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Exit with(log4j-test1.xml) TRACE M-EXIT[ FLOW ] E");
+        verify("Exit with(log4j-test1.xml) TRACE M-EXIT[ FLOW ] E-");
     }
 
     @Test
@@ -69,7 +69,7 @@ public class ExitTagTest {
         this.tag.setResult(5792);
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Exit with(5792) TRACE M-EXIT[ FLOW ] E");
+        verify("Exit with(5792) TRACE M-EXIT[ FLOW ] E-");
     }
 
     private void verify(final String expected) {
diff --git 
a/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/LoggingMessageTagSupportTest.java
 
b/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/LoggingMessageTagSupportTest.java
index 5fb2b419b4..ad9ab5d9ef 100644
--- 
a/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/LoggingMessageTagSupportTest.java
+++ 
b/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/LoggingMessageTagSupportTest.java
@@ -118,7 +118,7 @@ public class LoggingMessageTagSupportTest {
         this.tag.setMessage("Hello message for 
testDoEndTagStringMessageNoMarkerNoException");
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Hello message for testDoEndTagStringMessageNoMarkerNoException 
WARN M- E");
+        verify("Hello message for testDoEndTagStringMessageNoMarkerNoException 
WARN M- E-");
     }
 
     @Test
@@ -129,7 +129,7 @@ public class LoggingMessageTagSupportTest {
         this.tag.setMessage("Goodbye message for 
testDoEndTagStringMessageMarkerNoException");
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Goodbye message for testDoEndTagStringMessageMarkerNoException 
INFO M-E01 E");
+        verify("Goodbye message for testDoEndTagStringMessageMarkerNoException 
INFO M-E01 E-");
     }
 
     @Test
@@ -140,8 +140,9 @@ public class LoggingMessageTagSupportTest {
         this.tag.setMessage("Another message for 
testDoEndTagStringMessageNoMarkerException");
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Another message for testDoEndTagStringMessageNoMarkerException 
ERROR M- E" + LINE_SEPARATOR
-                + "java.lang.Exception: This is a test" + LINE_SEPARATOR);
+        verify(
+                "Another message for 
testDoEndTagStringMessageNoMarkerException ERROR M- E-java.lang.Exception: This 
is a test"
+                        + LINE_SEPARATOR);
     }
 
     @Test
@@ -153,8 +154,9 @@ public class LoggingMessageTagSupportTest {
         this.tag.setMessage("Final message for 
testDoEndTagStringMessageMarkerException");
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Final message for testDoEndTagStringMessageMarkerException 
TRACE M-F02 E" + LINE_SEPARATOR
-                + "java.lang.RuntimeException: This is another test" + 
Strings.LINE_SEPARATOR);
+        verify(
+                "Final message for testDoEndTagStringMessageMarkerException 
TRACE M-F02 E-java.lang.RuntimeException: This is another test"
+                        + Strings.LINE_SEPARATOR);
     }
 
     @Test
@@ -166,7 +168,7 @@ public class LoggingMessageTagSupportTest {
         this.tag.setMessage("Test message with [{}] parameter of [{}]");
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Test message with [A] parameter of [HOURS] FATAL M- E");
+        verify("Test message with [A] parameter of [HOURS] FATAL M- E-");
     }
 
     @Test
@@ -180,8 +182,8 @@ public class LoggingMessageTagSupportTest {
         this.tag.setMessage("Final message with [{}] parameter of [{}]");
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Final message with [Z] parameter of [SECONDS] DEBUG M-N03 E" + 
LINE_SEPARATOR
-                + "java.lang.Error: This is the last test" + LINE_SEPARATOR);
+        verify("Final message with [Z] parameter of [SECONDS] DEBUG M-N03 
E-java.lang.Error: This is the last test"
+                + LINE_SEPARATOR);
     }
 
     @Test
@@ -192,7 +194,7 @@ public class LoggingMessageTagSupportTest {
                 logger.getMessageFactory().newMessage("First message for 
testDoEndTagMessageNoMarkerNoException"));
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("First message for testDoEndTagMessageNoMarkerNoException INFO 
M- E");
+        verify("First message for testDoEndTagMessageNoMarkerNoException INFO 
M- E-");
     }
 
     @Test
@@ -204,7 +206,7 @@ public class LoggingMessageTagSupportTest {
                 logger.getMessageFactory().newMessage("Another message for 
testDoEndTagMessageMarkerNoException"));
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Another message for testDoEndTagMessageMarkerNoException WARN 
M-E01 E");
+        verify("Another message for testDoEndTagMessageMarkerNoException WARN 
M-E01 E-");
     }
 
     @Test
@@ -216,8 +218,8 @@ public class LoggingMessageTagSupportTest {
                 logger.getMessageFactory().newMessage("Third message for 
testDoEndTagMessageNoMarkerException"));
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Third message for testDoEndTagMessageNoMarkerException TRACE 
M- E" + LINE_SEPARATOR
-                + "java.lang.Exception: This is a test" + LINE_SEPARATOR);
+        verify("Third message for testDoEndTagMessageNoMarkerException TRACE 
M- E-java.lang.Exception: This is a test"
+                + LINE_SEPARATOR);
     }
 
     @Test
@@ -230,8 +232,9 @@ public class LoggingMessageTagSupportTest {
                 logger.getMessageFactory().newMessage("Final message for 
testDoEndTagMessageMarkerException"));
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Final message for testDoEndTagMessageMarkerException ERROR 
M-F02 E" + LINE_SEPARATOR
-                + "java.lang.RuntimeException: " + "This is another test" + 
LINE_SEPARATOR);
+        verify(
+                "Final message for testDoEndTagMessageMarkerException ERROR 
M-F02 E-java.lang.RuntimeException: This is another test"
+                        + LINE_SEPARATOR);
     }
 
     @Test
@@ -241,7 +244,7 @@ public class LoggingMessageTagSupportTest {
         this.tag.setMessage(new MyMessage("First message for 
testDoEndTagObjectNoMarkerNoException"));
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("First message for testDoEndTagObjectNoMarkerNoException INFO 
M- E");
+        verify("First message for testDoEndTagObjectNoMarkerNoException INFO 
M- E-");
     }
 
     @Test
@@ -252,7 +255,7 @@ public class LoggingMessageTagSupportTest {
         this.tag.setMessage(new MyMessage("Another message for 
testDoEndTagObjectMarkerNoException"));
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Another message for testDoEndTagObjectMarkerNoException WARN 
M-E01 E");
+        verify("Another message for testDoEndTagObjectMarkerNoException WARN 
M-E01 E-");
     }
 
     @Test
@@ -263,8 +266,8 @@ public class LoggingMessageTagSupportTest {
         this.tag.setMessage(new MyMessage("Third message for 
testDoEndTagObjectNoMarkerException"));
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Third message for testDoEndTagObjectNoMarkerException TRACE M- 
E" + LINE_SEPARATOR
-                + "java.lang.Exception: This is a test" + LINE_SEPARATOR);
+        verify("Third message for testDoEndTagObjectNoMarkerException TRACE M- 
E-java.lang.Exception: This is a test"
+                + LINE_SEPARATOR);
     }
 
     @Test
@@ -276,8 +279,9 @@ public class LoggingMessageTagSupportTest {
         this.tag.setMessage(new MyMessage("Final message for 
testDoEndTagObjectMarkerException"));
 
         assertEquals(Tag.EVAL_PAGE, this.tag.doEndTag(), "The return value is 
not correct.");
-        verify("Final message for testDoEndTagObjectMarkerException ERROR 
M-F02 E" + LINE_SEPARATOR
-                + "java.lang.RuntimeException: " + "This is another test" + 
LINE_SEPARATOR);
+        verify(
+                "Final message for testDoEndTagObjectMarkerException ERROR 
M-F02 E-java.lang.RuntimeException: This is another test"
+                        + LINE_SEPARATOR);
     }
 
     private void verify(final String expected) {
diff --git a/log4j-taglib/src/test/resources/log4j-test1.xml 
b/log4j-taglib/src/test/resources/log4j-test1.xml
index 31de372033..6cd3a4eb99 100644
--- a/log4j-taglib/src/test/resources/log4j-test1.xml
+++ b/log4j-taglib/src/test/resources/log4j-test1.xml
@@ -34,7 +34,7 @@
       </PatternLayout>
     </File>
     <List name="List">
-      <PatternLayout pattern="%C{1.} %m %level M-%marker E%ex{1}"/>
+      <PatternLayout pattern="%C{1.} %m %level M-%marker E-%ex{1}"/>
     </List>
   </Appenders>
 
diff --git a/src/changelog/.2.x.x/3873_throwable_converter_new_line.xml 
b/src/changelog/.2.x.x/3873_throwable_converter_new_line.xml
new file mode 100644
index 0000000000..30011263e5
--- /dev/null
+++ b/src/changelog/.2.x.x/3873_throwable_converter_new_line.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns="https://logging.apache.org/xml/ns";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="
+           https://logging.apache.org/xml/ns
+           https://logging.apache.org/xml/ns/log4j-changelog-0.xsd";
+       type="fixed">
+    <issue id="3873" 
link="https://github.com/apache/logging-log4j2/issues/3873"/>
+    <issue id="3919" 
link="https://github.com/apache/logging-log4j2/pull/3919"/>
+    <description format="asciidoc">
+        Fix Pattern Layout exception stack trace converters to no longer 
prepend newlines based on context
+    </description>
+</entry>

Reply via email to