Skip to content

Commit 94fc126

Browse files
ppkarwaszvy
authored andcommitted
Recognize nested converters in alwaysWriteExceptions (#3920)
1 parent 427fa60 commit 94fc126

File tree

11 files changed

+109
-21
lines changed

11 files changed

+109
-21
lines changed

log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.junit.jupiter.api.Test;
4545
import org.junit.jupiter.params.ParameterizedTest;
4646
import org.junit.jupiter.params.provider.MethodSource;
47+
import org.junit.jupiter.params.provider.ValueSource;
4748

4849
class PatternParserTest {
4950

@@ -93,6 +94,50 @@ void defaultPattern() {
9394
validateConverter(formatters, 1, "Line Sep");
9495
}
9596

97+
@ParameterizedTest
98+
@ValueSource(strings = {"%m", "%p", "%c", "%d"})
99+
void testAlwaysWriteExceptions_appendsThrowable(String pattern) {
100+
// With alwaysWriteExceptions=true, parser should auto-append a %throwable
101+
final List<PatternFormatter> formatters = parser.parse(pattern + "%n", true, false, false);
102+
assertThat(formatters).hasSize(3);
103+
assertThat(formatters.get(2).getConverter()).isInstanceOf(ThrowablePatternConverter.class);
104+
105+
// With alwaysWriteExceptions=false, parser should leave the pattern unchanged
106+
final List<PatternFormatter> formatters2 = parser.parse(pattern + "%n", false, false, false);
107+
assertThat(formatters2).hasSize(2);
108+
assertThat(formatters2.get(1).getConverter()).isNotInstanceOf(ThrowablePatternConverter.class);
109+
}
110+
111+
@ParameterizedTest
112+
@ValueSource(
113+
strings = {
114+
"%black{%throwable}",
115+
"%blue{%throwable}",
116+
"%cyan{%throwable}",
117+
"%green{%throwable}",
118+
"%magenta{%throwable}",
119+
"%red{%throwable}",
120+
"%white{%throwable}",
121+
"%yellow{%throwable}",
122+
"%encode{%throwable}{JSON}",
123+
"%equals{%throwable}{java.lang.Throwable}{T}",
124+
"%equalsIgnoreCase{%throwable}{java.lang.Throwable}{T}",
125+
"%highlight{%throwable}",
126+
"%maxLen{%throwable}{1024}",
127+
"%replace{%throwable}{\n}{ }",
128+
"%style{%throwable}{red bold}",
129+
"%notEmpty{%throwable}",
130+
})
131+
void testAlwaysWriteExceptions_recognizesNestedPatterns(String pattern) {
132+
// With alwaysWriteExceptions=true, parser must detect the nested %throwable
133+
// and NOT auto-append another one at the top level
134+
final List<PatternFormatter> formatters = parser.parse(pattern, true, false, false);
135+
136+
// Only one top-level formatter is expected (the wrapper itself), not a trailing ThrowablePatternConverter
137+
assertThat(formatters).hasSize(1);
138+
assertThat(formatters.get(0).getConverter()).isNotInstanceOf(ThrowablePatternConverter.class);
139+
}
140+
96141
/**
97142
* Test the custom pattern
98143
*/
@@ -103,7 +148,7 @@ void testCustomPattern() {
103148
final StringMap mdc = ContextDataFactory.createContextData();
104149
mdc.putValue("loginId", "Fred");
105150
// The line number of the Throwable definition
106-
final int nextLineNumber = 107;
151+
final int nextLineNumber = 152; // Adjust this when the code above changes!
107152
final Throwable t = new Throwable();
108153
final StackTraceElement[] elements = t.getStackTrace();
109154
final Log4jLogEvent event = Log4jLogEvent.newBuilder() //

log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AbstractStyleNameConverter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,4 +376,11 @@ public void format(final LogEvent event, final StringBuilder toAppendTo) {
376376
toAppendTo.append(AnsiEscape.getDefaultStyle());
377377
}
378378
}
379+
380+
@Override
381+
public boolean handlesThrowable() {
382+
return formatters.stream()
383+
.map(PatternFormatter::getConverter)
384+
.anyMatch(LogEventPatternConverter::handlesThrowable);
385+
}
379386
}

log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverter.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,9 @@ private EncodingPatternConverter(final List<PatternFormatter> formatters, final
5454

5555
@Override
5656
public boolean handlesThrowable() {
57-
return formatters != null
58-
&& formatters.stream()
59-
.map(PatternFormatter::getConverter)
60-
.anyMatch(LogEventPatternConverter::handlesThrowable);
57+
return formatters.stream()
58+
.map(PatternFormatter::getConverter)
59+
.anyMatch(LogEventPatternConverter::handlesThrowable);
6160
}
6261

6362
/**

log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsBaseReplacementConverter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,11 @@ void parseSubstitution(final LogEvent event, final StringBuilder substitutionBuf
9999
substitutionBuffer.append(substitution);
100100
}
101101
}
102+
103+
@Override
104+
public boolean handlesThrowable() {
105+
return formatters.stream()
106+
.map(PatternFormatter::getConverter)
107+
.anyMatch(LogEventPatternConverter::handlesThrowable);
108+
}
102109
}

log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HighlightConverter.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -265,11 +265,8 @@ String getLevelStyle(final Level level) {
265265

266266
@Override
267267
public boolean handlesThrowable() {
268-
for (final PatternFormatter formatter : patternFormatters) {
269-
if (formatter.handlesThrowable()) {
270-
return true;
271-
}
272-
}
273-
return false;
268+
return patternFormatters.stream()
269+
.map(PatternFormatter::getConverter)
270+
.anyMatch(LogEventPatternConverter::handlesThrowable);
274271
}
275272
}

log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LogEventPatternConverter.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,16 @@ public void format(final Object obj, final StringBuilder output) {
5353
}
5454

5555
/**
56-
* Normally pattern converters are not meant to handle Exceptions although few pattern converters might.
56+
* Tests whether this pattern converter is renders a {@link Throwable}.
57+
*
5758
* <p>
58-
* By examining the return values for this method, the containing layout will determine whether it handles
59-
* throwables or not.
59+
* The {@link PatternParser} checks this flag when processing the
60+
* {@code alwaysWriteExceptions} option: if no converter in the pattern handles
61+
* throwables, the parser automatically appends a converter to ensure exceptions are still written.
6062
* </p>
6163
*
62-
* @return true if this PatternConverter handles throwables
64+
* @return {@code true} if this converter consumes and renders a {@link Throwable},
65+
* {@code false} otherwise
6366
*/
6467
public boolean handlesThrowable() {
6568
return false;

log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MaxLengthConverter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,11 @@ public void format(final LogEvent event, final StringBuilder toAppendTo) {
9999
}
100100
}
101101
}
102+
103+
@Override
104+
public boolean handlesThrowable() {
105+
return formatters.stream()
106+
.map(PatternFormatter::getConverter)
107+
.anyMatch(LogEventPatternConverter::handlesThrowable);
108+
}
102109
}

log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,11 @@ public void format(final LogEvent event, final StringBuilder toAppendTo) {
9292
}
9393
toAppendTo.append(pattern.matcher(buf.toString()).replaceAll(substitution));
9494
}
95+
96+
@Override
97+
public boolean handlesThrowable() {
98+
return formatters.stream()
99+
.map(PatternFormatter::getConverter)
100+
.anyMatch(LogEventPatternConverter::handlesThrowable);
101+
}
95102
}

log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/StyleConverter.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,9 @@ public void format(final LogEvent event, final StringBuilder toAppendTo) {
129129

130130
@Override
131131
public boolean handlesThrowable() {
132-
for (final PatternFormatter formatter : patternFormatters) {
133-
if (formatter.handlesThrowable()) {
134-
return true;
135-
}
136-
}
137-
return false;
132+
return patternFormatters.stream()
133+
.map(PatternFormatter::getConverter)
134+
.anyMatch(LogEventPatternConverter::handlesThrowable);
138135
}
139136

140137
/**

log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,11 @@ private static boolean sequenceRegionMatches(
119119
}
120120
return true;
121121
}
122+
123+
@Override
124+
public boolean handlesThrowable() {
125+
return formatters.stream()
126+
.map(PatternFormatter::getConverter)
127+
.anyMatch(LogEventPatternConverter::handlesThrowable);
128+
}
122129
}

0 commit comments

Comments
 (0)