Skip to content

Commit fdeaddd

Browse files
authored
Port %xEx performance regression fix to main (#3154)
1 parent 025b4bd commit fdeaddd

File tree

6 files changed

+83
-67
lines changed

6 files changed

+83
-67
lines changed

log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicyTest.java

-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ private void checkUpdated(final Map<String, String> updatedMap) {
162162
assertThat("wrong size", updatedMap, hasSize(2));
163163
}
164164

165-
@SuppressWarnings("deprecation")
166165
private void compareLogEvents(final LogEvent orig, final LogEvent changed) {
167166
// Ensure that everything but the Mapped Data is still the same
168167
assertEquals(orig.getLoggerName(), changed.getLoggerName(), "LoggerName changed");

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

+15-12
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.net.URL;
2020
import java.security.CodeSource;
21+
import java.util.function.Consumer;
2122

2223
/**
2324
* Resource information (i.e., the enclosing JAR file and its version) of a class.
@@ -26,32 +27,35 @@ final class ClassResourceInfo {
2627

2728
static final ClassResourceInfo UNKNOWN = new ClassResourceInfo();
2829

29-
private final String text;
30+
private final Consumer<StringBuilder> renderer;
3031

3132
final Class<?> clazz;
3233

3334
/**
3435
* Constructs an instance modelling an unknown class resource.
3536
*/
3637
private ClassResourceInfo() {
37-
this.text = "~[?:?]";
38-
this.clazz = null;
38+
this.renderer = (buffer) -> buffer.append("~[?:?]");
39+
clazz = null;
3940
}
4041

4142
/**
4243
* @param clazz the class
4344
* @param exact {@code true}, if the class was obtained via reflection; {@code false}, otherwise
4445
*/
4546
ClassResourceInfo(final Class<?> clazz, final boolean exact) {
46-
this.clazz = clazz;
47-
this.text = getText(clazz, exact);
48-
}
49-
50-
private static String getText(final Class<?> clazz, final boolean exact) {
5147
final String exactnessPrefix = exact ? "" : "~";
5248
final String location = getLocation(clazz);
5349
final String version = getVersion(clazz);
54-
return String.format("%s[%s:%s]", exactnessPrefix, location, version);
50+
this.renderer = (buffer) -> {
51+
buffer.append(exactnessPrefix);
52+
buffer.append("[");
53+
buffer.append(location);
54+
buffer.append(":");
55+
buffer.append(version);
56+
buffer.append("]");
57+
};
58+
this.clazz = clazz;
5559
}
5660

5761
private static String getLocation(final Class<?> clazz) {
@@ -85,8 +89,7 @@ private static String getVersion(final Class<?> clazz) {
8589
return "?";
8690
}
8791

88-
@Override
89-
public String toString() {
90-
return text;
92+
void render(final StringBuilder buffer) {
93+
renderer.accept(buffer);
9194
}
9295
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ public List<PatternFormatter> parse(
210210
list.add(new PatternFormatter(pc, field));
211211
}
212212
if (alwaysWriteExceptions && !handlesThrowable) {
213-
final LogEventPatternConverter pc = ExtendedThrowablePatternConverter.newInstance(config, new String[0]);
213+
final LogEventPatternConverter pc = ThrowablePatternConverter.newInstance(config, new String[0]);
214214
list.add(new PatternFormatter(pc, FormattingInfo.getDefault()));
215215
}
216216
return list;

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

+65-52
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,13 @@
1818

1919
import java.util.ArrayDeque;
2020
import java.util.Collections;
21+
import java.util.Deque;
22+
import java.util.HashMap;
2123
import java.util.HashSet;
2224
import java.util.List;
2325
import java.util.Map;
24-
import java.util.Objects;
2526
import java.util.Queue;
2627
import java.util.Set;
27-
import java.util.stream.Collectors;
28-
import java.util.stream.Stream;
2928
import org.apache.logging.log4j.util.LoaderUtil;
3029
import org.apache.logging.log4j.util.StackLocatorUtil;
3130

@@ -74,7 +73,7 @@ void renderStackTraceElement(
7473
context.classResourceInfoByName.get(stackTraceElement.getClassName());
7574
if (classResourceInfo != null) {
7675
buffer.append(' ');
77-
buffer.append(classResourceInfo);
76+
classResourceInfo.render(buffer);
7877
}
7978
buffer.append(lineSeparator);
8079
}
@@ -102,15 +101,15 @@ private static Map<String, ClassResourceInfo> createClassResourceInfoByName(
102101
final Throwable rootThrowable, final Map<Throwable, Metadata> metadataByThrowable) {
103102

104103
// Stack trace elements of a `Throwable` only contain the class name.
105-
// But we need the associated `Class<?>` to extract its resource information, i.e., JAR file and version.
104+
// But we need the associated `Class` to extract its resource information, i.e., JAR file and version.
106105
// We are capturing the current stack to find suitable class loaders.
107-
// We will use this as a bootstrap to go from a class name in a stack trace to a `Class<?>`.
108-
final Map<String, ClassResourceInfo> classResourceInfoByName =
109-
StackLocatorUtil.getCurrentStackTrace().stream()
110-
.collect(Collectors.toMap(
111-
Class::getName,
112-
clazz -> new ClassResourceInfo(clazz, true),
113-
(classResourceInfo1, classResourceInfo2) -> classResourceInfo1));
106+
// We will use this as a bootstrap to go from a class name in a stack trace to a `Class`.
107+
final Deque<Class<?>> executionStackTrace = StackLocatorUtil.getCurrentStackTrace();
108+
109+
// Mapping a class name to a `ClassResourceInfo` is an expensive operation.
110+
// Next to `ClassResourceInfo` allocation, it requires extraction of the associated `Class`.
111+
// We will use this lookup table to speed things up.
112+
final Map<String, ClassResourceInfo> classResourceInfoByName = new HashMap<>();
114113

115114
// Walk over the causal chain
116115
final Set<Throwable> visitedThrowables = new HashSet<>();
@@ -130,64 +129,78 @@ private static Map<String, ClassResourceInfo> createClassResourceInfoByName(
130129
continue;
131130
}
132131

132+
Class<?> executionStackTraceElementClass =
133+
executionStackTrace.isEmpty() ? null : executionStackTrace.peekLast();
133134
ClassLoader lastLoader = null;
134135
final StackTraceElement[] stackTraceElements = throwable.getStackTrace();
135136
for (int throwableStackIndex = metadata.stackLength - 1;
136137
throwableStackIndex >= 0;
137138
--throwableStackIndex) {
138139

139-
// Skip if the current class name is either known, or already visited and is unknown
140-
final StackTraceElement stackTraceElement = stackTraceElements[throwableStackIndex];
141-
final String stackTraceElementClassName = stackTraceElement.getClassName();
142-
ClassResourceInfo classResourceInfo = classResourceInfoByName.get(stackTraceElementClassName);
140+
// Get the exception's stack trace element
141+
final StackTraceElement throwableStackTraceElement = stackTraceElements[throwableStackIndex];
142+
final String throwableStackTraceElementClassName = throwableStackTraceElement.getClassName();
143+
144+
// Skip if the current class name is already registered
145+
ClassResourceInfo classResourceInfo =
146+
classResourceInfoByName.get(throwableStackTraceElementClassName);
143147
if (classResourceInfo != null) {
144148
if (classResourceInfo.clazz != null) {
145149
lastLoader = classResourceInfo.clazz.getClassLoader();
146150
}
147-
continue;
148151
}
149152

150-
// Try to determine the stack trace element class, and register the result to the lookup table
151-
final Class<?> stackTraceElementClass = loadClass(lastLoader, stackTraceElementClassName);
152-
classResourceInfo = stackTraceElementClass != null
153-
? new ClassResourceInfo(stackTraceElementClass, false)
154-
: ClassResourceInfo.UNKNOWN;
155-
classResourceInfoByName.put(stackTraceElementClassName, classResourceInfo);
153+
// See if we get a match from the execution stack trace
154+
else if (executionStackTraceElementClass != null
155+
&& throwableStackTraceElementClassName.equals(executionStackTraceElementClass.getName())) {
156+
classResourceInfo = new ClassResourceInfo(executionStackTraceElementClass, true);
157+
classResourceInfoByName.put(throwableStackTraceElementClassName, classResourceInfo);
158+
lastLoader = classResourceInfo.clazz.getClassLoader();
159+
executionStackTrace.pollLast();
160+
executionStackTraceElementClass = executionStackTrace.peekLast();
161+
}
162+
163+
// We don't know this class name, try to load it using the last found loader
164+
else {
165+
final Class<?> stackTraceElementClass =
166+
loadClass(lastLoader, throwableStackTraceElementClassName);
167+
classResourceInfo = stackTraceElementClass != null
168+
? new ClassResourceInfo(stackTraceElementClass, false)
169+
: ClassResourceInfo.UNKNOWN;
170+
classResourceInfoByName.put(throwableStackTraceElementClassName, classResourceInfo);
171+
}
156172
}
157173
}
158174
return classResourceInfoByName;
159175
}
160176

161-
@FunctionalInterface
162-
private interface ThrowingSupplier<V> {
163-
164-
V supply() throws Exception;
165-
}
166-
167177
private static Class<?> loadClass(final ClassLoader loader, final String className) {
168-
return Stream.<ThrowingSupplier<Class<?>>>of(
169-
// 1. Try the passed class loader
170-
() -> loader != null ? loader.loadClass(className) : null,
171-
// 2. Try the `LoaderUtil` magic
172-
() -> LoaderUtil.loadClass(className),
173-
// 3. Try the current class loader
174-
() -> ThrowableExtendedStackTraceRenderer.class
175-
.getClassLoader()
176-
.loadClass(className))
177-
.map(provider -> {
178-
try {
179-
final Class<?> clazz = provider.supply();
180-
if (clazz != null) {
181-
return clazz;
182-
}
183-
} catch (final Exception ignored) {
184-
// Do nothing
185-
}
186-
return null;
187-
})
188-
.filter(Objects::nonNull)
189-
.findFirst()
190-
.orElse(null);
178+
for (final ClassLoadingStrategy strategy : CLASS_LOADING_STRATEGIES) {
179+
try {
180+
final Class<?> clazz = strategy.run(loader, className);
181+
if (clazz != null) {
182+
return clazz;
183+
}
184+
} catch (final Exception ignored) {
185+
// Do nothing
186+
}
187+
}
188+
return null;
191189
}
192190
}
191+
192+
private static final ClassLoadingStrategy[] CLASS_LOADING_STRATEGIES = {
193+
// 1. Try the passed class loader
194+
(loader, className) -> loader != null ? loader.loadClass(className) : null,
195+
// 2. Try the `LoaderUtil` magic
196+
(loader, className) -> LoaderUtil.loadClass(className),
197+
// 3. Try the current class loader
198+
(loader, className) ->
199+
ThrowableExtendedStackTraceRenderer.class.getClassLoader().loadClass(className)
200+
};
201+
202+
private interface ClassLoadingStrategy {
203+
204+
Class<?> run(final ClassLoader loader, final String className) throws Exception;
205+
}
193206
}

log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayout.java

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ public String toSerializable(final LogEvent event) {
101101
format.print(event.getLoggerFqcn(), buffer, false);
102102
format.print(event.getLoggerName(), buffer, false);
103103
format.print(event.getMarker(), buffer, false);
104+
format.print(event.getThrown(), buffer, false);
104105
format.print(event.getSource(), buffer, false);
105106
format.print(event.getContextData(), buffer, false);
106107
format.print(event.getContextStack(), buffer, false);

src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ If this mode is employed without any configuration, the output will be identical
588588
589589
`none`:: Suppress the output of the converter
590590
591-
`short`:: Outputs the first line of the stack trace (analogous to `%ex\{1}`)
591+
`short`:: Outputs the first two lines of the stack trace (analogous to `%ex\{2}`)
592592
593593
`depth`:: Outputs the first `depth` lines of the stack trace (`%ex\{0}` is analogous to `%ex\{none}`)
594594

0 commit comments

Comments
 (0)