Skip to content

Commit b55c4d3

Browse files
alan0428appkarwaszvy
authored
Consolidate stack trace rendering in Pattern Layout (apache#2691)
Co-authored-by: Piotr P. Karwasz <[email protected]> Co-authored-by: Volkan Yazıcı <[email protected]>
1 parent 15ee737 commit b55c4d3

File tree

47 files changed

+2285
-1571
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2285
-1571
lines changed

Diff for: log4j-api-test/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@
144144
<artifactId>mockito-inline</artifactId>
145145
<scope>test</scope>
146146
</dependency>
147+
<dependency>
148+
<groupId>org.jspecify</groupId>
149+
<artifactId>jspecify</artifactId>
150+
<scope>test</scope>
151+
</dependency>
147152
<!-- Used by ServiceLoaderUtilTest -->
148153
<dependency>
149154
<groupId>org.osgi</groupId>

Diff for: log4j-core-test/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,11 @@
225225
<artifactId>javax.jms-api</artifactId>
226226
<scope>test</scope>
227227
</dependency>
228+
<dependency>
229+
<groupId>org.jspecify</groupId>
230+
<artifactId>jspecify</artifactId>
231+
<scope>test</scope>
232+
</dependency>
228233
<dependency>
229234
<groupId>com.sun.mail</groupId>
230235
<artifactId>javax.mail</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package foo;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
import java.net.Socket;
22+
import java.util.Arrays;
23+
import java.util.stream.Stream;
24+
import org.apache.logging.log4j.util.Constants;
25+
26+
/**
27+
* A testing friendly exception featuring
28+
* <ul>
29+
* <li>Distinct localized message</li>
30+
* <li>Non-Log4j package origin<sup>1</sup></li>
31+
* <li>Sufficient causal chain depth</li>
32+
* <li>Cyclic causal chain</li>
33+
* <li>Suppressed exceptions</li>
34+
* <li>Clutter-free stack trace (i.e., elements from JUnit, JDK, etc.)</li>
35+
* <li>Stack trace elements from named modules<sup>2</sup></li>
36+
* </ul>
37+
* <p>
38+
* <sup>1</sup> Helps with observing stack trace manipulation effects of Log4j.
39+
* </p>
40+
* <p>
41+
* <sup>2</sup> Helps with testing module name serialization.
42+
* </p>
43+
*/
44+
public final class TestFriendlyException extends RuntimeException {
45+
46+
static {
47+
// Ensure the distinct packaging
48+
assertThat(TestFriendlyException.class.getPackage().getName()).doesNotStartWith("org.apache");
49+
}
50+
51+
public static final StackTraceElement NAMED_MODULE_STACK_TRACE_ELEMENT = namedModuleStackTraceElement();
52+
53+
@SuppressWarnings("resource")
54+
private static StackTraceElement namedModuleStackTraceElement() {
55+
try {
56+
new Socket("0.0.0.0", -1);
57+
} catch (final Exception error) {
58+
final StackTraceElement[] stackTraceElements = error.getStackTrace();
59+
final String socketClassName = Socket.class.getCanonicalName();
60+
for (final StackTraceElement stackTraceElement : stackTraceElements) {
61+
if (stackTraceElement.getClassName().equals(socketClassName)) {
62+
if (Constants.JAVA_MAJOR_VERSION > 8) {
63+
final String stackTraceElementString = stackTraceElement.toString();
64+
assertThat(stackTraceElementString).startsWith("java.base/");
65+
}
66+
return stackTraceElement;
67+
}
68+
}
69+
}
70+
throw new IllegalStateException("should not have reached here");
71+
}
72+
73+
private static final String[] EXCLUDED_CLASS_NAME_PREFIXES = {
74+
"java.lang", "jdk.internal", "org.junit", "sun.reflect"
75+
};
76+
77+
public static final TestFriendlyException INSTANCE = create("r", 0, 2, new boolean[] {false}, new boolean[] {true});
78+
79+
private static TestFriendlyException create(
80+
final String name,
81+
final int depth,
82+
final int maxDepth,
83+
final boolean[] circular,
84+
final boolean[] namedModuleAllowed) {
85+
final TestFriendlyException error = new TestFriendlyException(name, namedModuleAllowed);
86+
if (depth < maxDepth) {
87+
final TestFriendlyException cause = create(name + "_c", depth + 1, maxDepth, circular, namedModuleAllowed);
88+
error.initCause(cause);
89+
final TestFriendlyException suppressed =
90+
create(name + "_s", depth + 1, maxDepth, circular, namedModuleAllowed);
91+
error.addSuppressed(suppressed);
92+
final boolean circularAllowed = depth + 1 == maxDepth && !circular[0];
93+
if (circularAllowed) {
94+
cause.initCause(error);
95+
suppressed.initCause(error);
96+
circular[0] = true;
97+
}
98+
}
99+
return error;
100+
}
101+
102+
private TestFriendlyException(final String message, final boolean[] namedModuleAllowed) {
103+
super(message);
104+
removeExcludedStackTraceElements(namedModuleAllowed);
105+
}
106+
107+
private void removeExcludedStackTraceElements(final boolean[] namedModuleAllowed) {
108+
final StackTraceElement[] oldStackTrace = getStackTrace();
109+
final boolean[] seenExcludedStackTraceElement = {false};
110+
final StackTraceElement[] newStackTrace = Arrays.stream(oldStackTrace)
111+
.flatMap(stackTraceElement ->
112+
mapStackTraceElement(stackTraceElement, namedModuleAllowed, seenExcludedStackTraceElement))
113+
.toArray(StackTraceElement[]::new);
114+
setStackTrace(newStackTrace);
115+
}
116+
117+
private static Stream<StackTraceElement> mapStackTraceElement(
118+
final StackTraceElement stackTraceElement,
119+
final boolean[] namedModuleAllowed,
120+
final boolean[] seenExcludedStackTraceElement) {
121+
final Stream<StackTraceElement> filteredStackTraceElement =
122+
filterStackTraceElement(stackTraceElement, seenExcludedStackTraceElement);
123+
final Stream<StackTraceElement> javaBaseIncludedStackTraceElement =
124+
namedModuleIncludedStackTraceElement(namedModuleAllowed);
125+
return Stream.concat(javaBaseIncludedStackTraceElement, filteredStackTraceElement);
126+
}
127+
128+
private static Stream<StackTraceElement> filterStackTraceElement(
129+
final StackTraceElement stackTraceElement, final boolean[] seenExcludedStackTraceElement) {
130+
if (seenExcludedStackTraceElement[0]) {
131+
return Stream.empty();
132+
}
133+
final String className = stackTraceElement.getClassName();
134+
for (final String excludedClassNamePrefix : EXCLUDED_CLASS_NAME_PREFIXES) {
135+
if (className.startsWith(excludedClassNamePrefix)) {
136+
seenExcludedStackTraceElement[0] = true;
137+
return Stream.empty();
138+
}
139+
}
140+
return Stream.of(stackTraceElement);
141+
}
142+
143+
private static Stream<StackTraceElement> namedModuleIncludedStackTraceElement(final boolean[] namedModuleAllowed) {
144+
if (!namedModuleAllowed[0]) {
145+
return Stream.of();
146+
}
147+
namedModuleAllowed[0] = false;
148+
return Stream.of(NAMED_MODULE_STACK_TRACE_ELEMENT);
149+
}
150+
151+
@Override
152+
public String getLocalizedMessage() {
153+
return getMessage() + " [localized]";
154+
}
155+
}

Diff for: log4j-core-test/src/test/java/org/apache/logging/log4j/core/EventParameterMemoryLeakTest.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static org.hamcrest.MatcherAssert.assertThat;
2020
import static org.hamcrest.Matchers.containsString;
21+
import static org.hamcrest.Matchers.is;
2122
import static org.junit.jupiter.api.Assertions.assertNull;
2223
import static org.junit.jupiter.api.Assertions.assertTrue;
2324

@@ -63,15 +64,20 @@ public void testParametersAreNotLeaked() throws Exception {
6364
final String line1 = reader.readLine();
6465
final String line2 = reader.readLine();
6566
final String line3 = reader.readLine();
67+
// line4 is empty line because of the line separator after throwable pattern
6668
final String line4 = reader.readLine();
6769
final String line5 = reader.readLine();
70+
final String line6 = reader.readLine();
71+
final String line7 = reader.readLine();
6872
reader.close();
6973
file.delete();
7074
assertThat(line1, containsString("Message with parameter paramValue"));
7175
assertThat(line2, containsString("paramValue"));
7276
assertThat(line3, containsString("paramValue"));
73-
assertThat(line4, containsString("paramValue"));
74-
assertNull(line5, "Expected only three lines");
77+
assertThat(line4, is(""));
78+
assertThat(line5, containsString("paramValue"));
79+
assertThat(line6, is(""));
80+
assertNull(line7, "Expected only six lines");
7581
final GarbageCollectionHelper gcHelper = new GarbageCollectionHelper();
7682
gcHelper.run();
7783
try {

Diff for: log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public class RollingAppenderOnStartupTest {
5151
@BeforeAll
5252
public static void setup() throws Exception {
5353
final Path target = loggingPath.resolve(FILENAME);
54-
Files.copy(Paths.get(SOURCE, FILENAME), target, StandardCopyOption.COPY_ATTRIBUTES);
54+
Files.copy(Paths.get(SOURCE, FILENAME), target, StandardCopyOption.REPLACE_EXISTING);
5555
final FileTime newTime = FileTime.from(Instant.now().minus(1, ChronoUnit.DAYS));
5656
final BasicFileAttributeView attrs = Files.getFileAttributeView(target, BasicFileAttributeView.class);
5757
attrs.setTimes(newTime, newTime, newTime);

Diff for: log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromThrowableMessageTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public void testNestedLoggingInLastArgument() throws Exception {
8080
final Set<String> lines2 = readUniqueLines(file2);
8181

8282
assertEquals("Expected the same data from both appenders", lines1, lines2);
83-
assertEquals(2, lines1.size());
83+
assertEquals(3, lines1.size());
8484
assertTrue(lines1.contains("INFO NestedLoggingFromThrowableMessageTest Logging in getMessage "));
8585
assertTrue(lines1.contains("ERROR NestedLoggingFromThrowableMessageTest Test message"));
8686
}

Diff for: log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyHelperTest.java

-79
This file was deleted.

0 commit comments

Comments
 (0)