You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by ck...@apache.org on 2018/09/03 23:37:22 UTC
logging-log4j2 git commit: [LOG4J2-2391] Refactor as much logic as
possible out of ThrowableProxy
Repository: logging-log4j2
Updated Branches:
refs/heads/release-2.x 4d76d3e34 -> 323942383
[LOG4J2-2391] Refactor as much logic as possible out of ThrowableProxy
This change isolates initialization logic into a new
ThrowableProxyHelper class, and rendering logic
into ThrowableProxyRenderer.
Both new utility classes are package private. ThrowableProxy public
API has not changed.
Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/32394238
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/32394238
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/32394238
Branch: refs/heads/release-2.x
Commit: 32394238398d0ce3c01dcdebe0dca9e396c69cbc
Parents: 4d76d3e
Author: Carter Kozak <ck...@apache.org>
Authored: Tue Aug 21 11:58:20 2018 -0400
Committer: Carter Kozak <ck...@apache.org>
Committed: Mon Sep 3 19:34:07 2018 -0400
----------------------------------------------------------------------
.../logging/log4j/core/impl/ThrowableProxy.java | 379 ++-----------------
.../log4j/core/impl/ThrowableProxyHelper.java | 234 ++++++++++++
.../log4j/core/impl/ThrowableProxyRenderer.java | 217 +++++++++++
.../log4j/core/impl/ThrowableProxyTest.java | 8 +-
4 files changed, 478 insertions(+), 360 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/32394238/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java
index 42ed186..c948a0c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java
@@ -17,9 +17,6 @@
package org.apache.logging.log4j.core.impl;
import java.io.Serializable;
-import java.net.URL;
-import java.security.CodeSource;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -30,9 +27,6 @@ import java.util.Stack;
import org.apache.logging.log4j.core.pattern.PlainTextRenderer;
import org.apache.logging.log4j.core.pattern.TextRenderer;
-import org.apache.logging.log4j.core.util.Loader;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.LoaderUtil;
import org.apache.logging.log4j.util.StackLocatorUtil;
import org.apache.logging.log4j.util.Strings;
@@ -54,29 +48,6 @@ import org.apache.logging.log4j.util.Strings;
*/
public class ThrowableProxy implements Serializable {
- private static final String TAB = "\t";
- private static final String CAUSED_BY_LABEL = "Caused by: ";
- private static final String SUPPRESSED_LABEL = "Suppressed: ";
- private static final String WRAPPED_BY_LABEL = "Wrapped by: ";
-
- /**
- * Cached StackTracePackageElement and ClassLoader.
- * <p>
- * Consider this class private.
- * </p>
- */
- static class CacheEntry {
- private final ExtendedClassInfo element;
- private final ClassLoader loader;
-
- public CacheEntry(final ExtendedClassInfo element, final ClassLoader loader) {
- this.element = element;
- this.loader = loader;
- }
- }
-
- private static final ThrowableProxy[] EMPTY_THROWABLE_PROXY_ARRAY = new ThrowableProxy[0];
-
private static final char EOL = '\n';
private static final String EOL_STR = String.valueOf(EOL);
@@ -110,7 +81,7 @@ public class ThrowableProxy implements Serializable {
this.causeProxy = null;
this.message = null;
this.localizedMessage = null;
- this.suppressedProxies = EMPTY_THROWABLE_PROXY_ARRAY;
+ this.suppressedProxies = ThrowableProxyHelper.EMPTY_THROWABLE_PROXY_ARRAY;
}
/**
@@ -128,19 +99,19 @@ public class ThrowableProxy implements Serializable {
* @param throwable The Throwable to wrap, must not be null.
* @param visited The set of visited suppressed exceptions.
*/
- private ThrowableProxy(final Throwable throwable, final Set<Throwable> visited) {
+ ThrowableProxy(final Throwable throwable, final Set<Throwable> visited) {
this.throwable = throwable;
this.name = throwable.getClass().getName();
this.message = throwable.getMessage();
this.localizedMessage = throwable.getLocalizedMessage();
- final Map<String, CacheEntry> map = new HashMap<>();
+ final Map<String, ThrowableProxyHelper.CacheEntry> map = new HashMap<>();
final Stack<Class<?>> stack = StackLocatorUtil.getCurrentStackTrace();
- this.extendedStackTrace = this.toExtendedStackTrace(stack, map, null, throwable.getStackTrace());
+ this.extendedStackTrace = ThrowableProxyHelper.toExtendedStackTrace(this, stack, map, null, throwable.getStackTrace());
final Throwable throwableCause = throwable.getCause();
final Set<Throwable> causeVisited = new HashSet<>(1);
this.causeProxy = throwableCause == null ? null : new ThrowableProxy(throwable, stack, map, throwableCause,
visited, causeVisited);
- this.suppressedProxies = this.toSuppressedProxies(throwable, visited);
+ this.suppressedProxies = ThrowableProxyHelper.toSuppressedProxies(throwable, visited);
}
/**
@@ -153,7 +124,8 @@ public class ThrowableProxy implements Serializable {
* @param suppressedVisited TODO
* @param causeVisited TODO
*/
- private ThrowableProxy(final Throwable parent, final Stack<Class<?>> stack, final Map<String, CacheEntry> map,
+ private ThrowableProxy(final Throwable parent, final Stack<Class<?>> stack,
+ final Map<String, ThrowableProxyHelper.CacheEntry> map,
final Throwable cause, final Set<Throwable> suppressedVisited,
final Set<Throwable> causeVisited) {
causeVisited.add(cause);
@@ -161,11 +133,11 @@ public class ThrowableProxy implements Serializable {
this.name = cause.getClass().getName();
this.message = this.throwable.getMessage();
this.localizedMessage = this.throwable.getLocalizedMessage();
- this.extendedStackTrace = this.toExtendedStackTrace(stack, map, parent.getStackTrace(), cause.getStackTrace());
+ this.extendedStackTrace = ThrowableProxyHelper.toExtendedStackTrace(this, stack, map, parent.getStackTrace(), cause.getStackTrace());
final Throwable causeCause = cause.getCause();
this.causeProxy = causeCause == null || causeVisited.contains(causeCause) ? null : new ThrowableProxy(parent,
stack, map, causeCause, suppressedVisited, causeVisited);
- this.suppressedProxies = this.toSuppressedProxies(cause, suppressedVisited);
+ this.suppressedProxies = ThrowableProxyHelper.toSuppressedProxies(cause, suppressedVisited);
}
@Override
@@ -206,111 +178,6 @@ public class ThrowableProxy implements Serializable {
return true;
}
- private void formatCause(final StringBuilder sb, final String prefix, final ThrowableProxy cause,
- final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
- formatThrowableProxy(sb, prefix, CAUSED_BY_LABEL, cause, ignorePackages, textRenderer, suffix, lineSeparator);
- }
-
- private void formatThrowableProxy(final StringBuilder sb, final String prefix, final String causeLabel,
- final ThrowableProxy throwableProxy, final List<String> ignorePackages,
- final TextRenderer textRenderer, final String suffix, String lineSeparator) {
- if (throwableProxy == null) {
- return;
- }
- textRenderer.render(prefix, sb, "Prefix");
- textRenderer.render(causeLabel, sb, "CauseLabel");
- throwableProxy.renderOn(sb, textRenderer);
- renderSuffix(suffix, sb, textRenderer);
- textRenderer.render(lineSeparator, sb, "Text");
- this.formatElements(sb, prefix, throwableProxy.commonElementCount,
- throwableProxy.getStackTrace(), throwableProxy.extendedStackTrace, ignorePackages, textRenderer, suffix, lineSeparator);
- this.formatSuppressed(sb, prefix + TAB, throwableProxy.suppressedProxies, ignorePackages, textRenderer, suffix, lineSeparator);
- this.formatCause(sb, prefix, throwableProxy.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator);
- }
-
- void renderOn(final StringBuilder output, final TextRenderer textRenderer) {
- final String msg = this.message;
- textRenderer.render(this.name, output, "Name");
- if (msg != null) {
- textRenderer.render(": ", output, "NameMessageSeparator");
- textRenderer.render(msg, output, "Message");
- }
- }
-
- private void formatSuppressed(final StringBuilder sb, final String prefix, final ThrowableProxy[] suppressedProxies,
- final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
- if (suppressedProxies == null) {
- return;
- }
- for (final ThrowableProxy suppressedProxy : suppressedProxies) {
- formatThrowableProxy(sb, prefix, SUPPRESSED_LABEL, suppressedProxy, ignorePackages, textRenderer, suffix, lineSeparator);
- }
- }
-
- private void formatElements(final StringBuilder sb, final String prefix, final int commonCount,
- final StackTraceElement[] causedTrace, final ExtendedStackTraceElement[] extStackTrace,
- final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
- if (ignorePackages == null || ignorePackages.isEmpty()) {
- for (final ExtendedStackTraceElement element : extStackTrace) {
- this.formatEntry(element, sb, prefix, textRenderer, suffix, lineSeparator);
- }
- } else {
- int count = 0;
- for (int i = 0; i < extStackTrace.length; ++i) {
- if (!this.ignoreElement(causedTrace[i], ignorePackages)) {
- if (count > 0) {
- appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator);
- count = 0;
- }
- this.formatEntry(extStackTrace[i], sb, prefix, textRenderer, suffix, lineSeparator);
- } else {
- ++count;
- }
- }
- if (count > 0) {
- appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator);
- }
- }
- if (commonCount != 0) {
- textRenderer.render(prefix, sb, "Prefix");
- textRenderer.render("\t... ", sb, "More");
- textRenderer.render(Integer.toString(commonCount), sb, "More");
- textRenderer.render(" more", sb, "More");
- renderSuffix(suffix, sb, textRenderer);
- textRenderer.render(lineSeparator, sb, "Text");
- }
- }
-
- private void renderSuffix(final String suffix, final StringBuilder sb, final TextRenderer textRenderer) {
- if (!suffix.isEmpty()) {
- textRenderer.render(" ", sb, "Suffix");
- textRenderer.render(suffix, sb, "Suffix");
- }
- }
-
- private void appendSuppressedCount(final StringBuilder sb, final String prefix, final int count,
- final TextRenderer textRenderer, final String suffix, String lineSeparator) {
- textRenderer.render(prefix, sb, "Prefix");
- if (count == 1) {
- textRenderer.render("\t... ", sb, "Suppressed");
- } else {
- textRenderer.render("\t... suppressed ", sb, "Suppressed");
- textRenderer.render(Integer.toString(count), sb, "Suppressed");
- textRenderer.render(" lines", sb, "Suppressed");
- }
- renderSuffix(suffix, sb, textRenderer);
- textRenderer.render(lineSeparator, sb, "Text");
- }
-
- private void formatEntry(final ExtendedStackTraceElement extStackTraceElement, final StringBuilder sb,
- final String prefix, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
- textRenderer.render(prefix, sb, "Prefix");
- textRenderer.render("\tat ", sb, "At");
- extStackTraceElement.renderOn(sb, textRenderer);
- renderSuffix(suffix, sb, textRenderer);
- textRenderer.render(lineSeparator, sb, "Text");
- }
-
/**
* Formats the specified Throwable.
* @param sb StringBuilder to contain the formatted Throwable.
@@ -359,17 +226,7 @@ public class ThrowableProxy implements Serializable {
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages,
final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
- final Throwable caused = cause.getCauseProxy() != null ? cause.getCauseProxy().getThrowable() : null;
- if (caused != null) {
- this.formatWrapper(sb, cause.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator);
- sb.append(WRAPPED_BY_LABEL);
- renderSuffix(suffix, sb, textRenderer);
- }
- cause.renderOn(sb, textRenderer);
- renderSuffix(suffix, sb, textRenderer);
- textRenderer.render(lineSeparator, sb, "Text");
- this.formatElements(sb, Strings.EMPTY, cause.commonElementCount,
- cause.getThrowable().getStackTrace(), cause.extendedStackTrace, ignorePackages, textRenderer, suffix, lineSeparator);
+ ThrowableProxyRenderer.formatWrapper(sb, cause, ignorePackages, textRenderer, suffix, lineSeparator);
}
public ThrowableProxy getCauseProxy() {
@@ -420,16 +277,7 @@ public class ThrowableProxy implements Serializable {
*/
public String getCauseStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
final StringBuilder sb = new StringBuilder();
- if (this.causeProxy != null) {
- this.formatWrapper(sb, this.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator);
- sb.append(WRAPPED_BY_LABEL);
- renderSuffix(suffix, sb, textRenderer);
- }
- this.renderOn(sb, textRenderer);
- renderSuffix(suffix, sb, textRenderer);
- textRenderer.render(lineSeparator, sb, "Text");
- this.formatElements(sb, Strings.EMPTY, 0, this.throwable.getStackTrace(), this.extendedStackTrace,
- ignorePackages, textRenderer, suffix, lineSeparator);
+ ThrowableProxyRenderer.formatCauseStackTrace(this, sb, ignorePackages, textRenderer, suffix, lineSeparator);
return sb.toString();
}
@@ -444,6 +292,17 @@ public class ThrowableProxy implements Serializable {
}
/**
+ * Set the value of {@link ThrowableProxy#commonElementCount}.
+ *
+ * Method is package-private, to be used internally for initialization.
+ *
+ * @param value New value of commonElementCount.
+ */
+ void setCommonElementCount(int value) {
+ this.commonElementCount = value;
+ }
+
+ /**
* Gets the stack trace including packaging information.
*
* @return The stack trace including packaging information.
@@ -519,15 +378,7 @@ public class ThrowableProxy implements Serializable {
* @param lineSeparator The end-of-line separator.
*/
public void formatExtendedStackTraceTo(final StringBuilder sb, final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
- textRenderer.render(name, sb, "Name");
- textRenderer.render(": ", sb, "NameMessageSeparator");
- textRenderer.render(this.message, sb, "Message");
- renderSuffix(suffix, sb, textRenderer);
- textRenderer.render(lineSeparator, sb, "Text");
- final StackTraceElement[] causedTrace = this.throwable != null ? this.throwable.getStackTrace() : null;
- this.formatElements(sb, Strings.EMPTY, 0, causedTrace, this.extendedStackTrace, ignorePackages, textRenderer, suffix, lineSeparator);
- this.formatSuppressed(sb, TAB, this.suppressedProxies, ignorePackages, textRenderer, suffix, lineSeparator);
- this.formatCause(sb, Strings.EMPTY, this.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator);
+ ThrowableProxyRenderer.formatExtendedStackTraceTo(this, sb, ignorePackages, textRenderer, suffix, lineSeparator);
}
public String getLocalizedMessage() {
@@ -599,193 +450,9 @@ public class ThrowableProxy implements Serializable {
return result;
}
- private boolean ignoreElement(final StackTraceElement element, final List<String> ignorePackages) {
- if (ignorePackages != null) {
- final String className = element.getClassName();
- for (final String pkg : ignorePackages) {
- if (className.startsWith(pkg)) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Loads classes not located via Reflection.getCallerClass.
- *
- * @param lastLoader The ClassLoader that loaded the Class that called this Class.
- * @param className The name of the Class.
- * @return The Class object for the Class or null if it could not be located.
- */
- private Class<?> loadClass(final ClassLoader lastLoader, final String className) {
- // XXX: this is overly complicated
- Class<?> clazz;
- if (lastLoader != null) {
- try {
- clazz = lastLoader.loadClass(className);
- if (clazz != null) {
- return clazz;
- }
- } catch (final Throwable ignore) {
- // Ignore exception.
- }
- }
- try {
- clazz = LoaderUtil.loadClass(className);
- } catch (final ClassNotFoundException | NoClassDefFoundError e) {
- return loadClass(className);
- } catch (final SecurityException e) {
- return null;
- }
- return clazz;
- }
-
- private Class<?> loadClass(final String className) {
- try {
- return Loader.loadClass(className, this.getClass().getClassLoader());
- } catch (final ClassNotFoundException | NoClassDefFoundError | SecurityException e) {
- return null;
- }
- }
-
- /**
- * Construct the CacheEntry from the Class's information.
- *
- * @param stackTraceElement The stack trace element
- * @param callerClass The Class.
- * @param exact True if the class was obtained via Reflection.getCallerClass.
- * @return The CacheEntry.
- */
- private CacheEntry toCacheEntry(final Class<?> callerClass, final boolean exact) {
- String location = "?";
- String version = "?";
- ClassLoader lastLoader = null;
- if (callerClass != null) {
- try {
- final CodeSource source = callerClass.getProtectionDomain().getCodeSource();
- if (source != null) {
- final URL locationURL = source.getLocation();
- if (locationURL != null) {
- final String str = locationURL.toString().replace('\\', '/');
- int index = str.lastIndexOf("/");
- if (index >= 0 && index == str.length() - 1) {
- index = str.lastIndexOf("/", index - 1);
- location = str.substring(index + 1);
- } else {
- location = str.substring(index + 1);
- }
- }
- }
- } catch (final Exception ex) {
- // Ignore the exception.
- }
- final Package pkg = callerClass.getPackage();
- if (pkg != null) {
- final String ver = pkg.getImplementationVersion();
- if (ver != null) {
- version = ver;
- }
- }
- try {
- lastLoader = callerClass.getClassLoader();
- } catch (final SecurityException e) {
- lastLoader = null;
- }
- }
- return new CacheEntry(new ExtendedClassInfo(exact, location, version), lastLoader);
- }
-
- /**
- * Resolve all the stack entries in this stack trace that are not common with the parent.
- *
- * @param stack The callers Class stack.
- * @param map The cache of CacheEntry objects.
- * @param rootTrace The first stack trace resolve or null.
- * @param stackTrace The stack trace being resolved.
- * @return The StackTracePackageElement array.
- */
- ExtendedStackTraceElement[] toExtendedStackTrace(final Stack<Class<?>> stack, final Map<String, CacheEntry> map,
- final StackTraceElement[] rootTrace,
- final StackTraceElement[] stackTrace) {
- int stackLength;
- if (rootTrace != null) {
- int rootIndex = rootTrace.length - 1;
- int stackIndex = stackTrace.length - 1;
- while (rootIndex >= 0 && stackIndex >= 0 && rootTrace[rootIndex].equals(stackTrace[stackIndex])) {
- --rootIndex;
- --stackIndex;
- }
- this.commonElementCount = stackTrace.length - 1 - stackIndex;
- stackLength = stackIndex + 1;
- } else {
- this.commonElementCount = 0;
- stackLength = stackTrace.length;
- }
- final ExtendedStackTraceElement[] extStackTrace = new ExtendedStackTraceElement[stackLength];
- Class<?> clazz = stack.isEmpty() ? null : stack.peek();
- ClassLoader lastLoader = null;
- for (int i = stackLength - 1; i >= 0; --i) {
- final StackTraceElement stackTraceElement = stackTrace[i];
- final String className = stackTraceElement.getClassName();
- // The stack returned from getCurrentStack may be missing entries for java.lang.reflect.Method.invoke()
- // and its implementation. The Throwable might also contain stack entries that are no longer
- // present as those methods have returned.
- ExtendedClassInfo extClassInfo;
- if (clazz != null && className.equals(clazz.getName())) {
- final CacheEntry entry = this.toCacheEntry(clazz, true);
- extClassInfo = entry.element;
- lastLoader = entry.loader;
- stack.pop();
- clazz = stack.isEmpty() ? null : stack.peek();
- } else {
- final CacheEntry cacheEntry = map.get(className);
- if (cacheEntry != null) {
- final CacheEntry entry = cacheEntry;
- extClassInfo = entry.element;
- if (entry.loader != null) {
- lastLoader = entry.loader;
- }
- } else {
- final CacheEntry entry = this.toCacheEntry(this.loadClass(lastLoader, className), false);
- extClassInfo = entry.element;
- map.put(className, entry);
- if (entry.loader != null) {
- lastLoader = entry.loader;
- }
- }
- }
- extStackTrace[i] = new ExtendedStackTraceElement(stackTraceElement, extClassInfo);
- }
- return extStackTrace;
- }
-
@Override
public String toString() {
final String msg = this.message;
return msg != null ? this.name + ": " + msg : this.name;
}
-
- private ThrowableProxy[] toSuppressedProxies(final Throwable thrown, Set<Throwable> suppressedVisited) {
- try {
- final Throwable[] suppressed = thrown.getSuppressed();
- if (suppressed == null || suppressed.length == 0) {
- return EMPTY_THROWABLE_PROXY_ARRAY;
- }
- final List<ThrowableProxy> proxies = new ArrayList<>(suppressed.length);
- if (suppressedVisited == null) {
- suppressedVisited = new HashSet<>(suppressed.length);
- }
- for (int i = 0; i < suppressed.length; i++) {
- final Throwable candidate = suppressed[i];
- if (suppressedVisited.add(candidate)) {
- proxies.add(new ThrowableProxy(candidate, suppressedVisited));
- }
- }
- return proxies.toArray(new ThrowableProxy[proxies.size()]);
- } catch (final Exception e) {
- StatusLogger.getLogger().error(e);
- }
- return null;
- }
}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/32394238/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyHelper.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyHelper.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyHelper.java
new file mode 100644
index 0000000..547ca09
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyHelper.java
@@ -0,0 +1,234 @@
+/*
+ * 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 org.apache.logging.log4j.core.util.Loader;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
+
+import java.net.URL;
+import java.security.CodeSource;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * {@link ThrowableProxyHelper} provides utilities required to initialize a new {@link ThrowableProxy}
+ * instance.
+ */
+class ThrowableProxyHelper {
+
+ static final ThrowableProxy[] EMPTY_THROWABLE_PROXY_ARRAY = new ThrowableProxy[0];
+
+ private ThrowableProxyHelper() {
+ // Utility Class
+ }
+
+ /**
+ * Cached StackTracePackageElement and ClassLoader.
+ * <p>
+ * Consider this class private.
+ * </p>
+ */
+ static final class CacheEntry {
+ private final ExtendedClassInfo element;
+ private final ClassLoader loader;
+
+ private CacheEntry(final ExtendedClassInfo element, final ClassLoader loader) {
+ this.element = element;
+ this.loader = loader;
+ }
+ }
+
+ /**
+ * Resolve all the stack entries in this stack trace that are not common with the parent.
+ *
+ * @param src Instance for which to build an extended stack trace.
+ * @param stack The callers Class stack.
+ * @param map The cache of CacheEntry objects.
+ * @param rootTrace The first stack trace resolve or null.
+ * @param stackTrace The stack trace being resolved.
+ * @return The StackTracePackageElement array.
+ */
+ static ExtendedStackTraceElement[] toExtendedStackTrace(
+ final ThrowableProxy src,
+ final Stack<Class<?>> stack, final Map<String, CacheEntry> map,
+ final StackTraceElement[] rootTrace,
+ final StackTraceElement[] stackTrace) {
+ int stackLength;
+ if (rootTrace != null) {
+ int rootIndex = rootTrace.length - 1;
+ int stackIndex = stackTrace.length - 1;
+ while (rootIndex >= 0 && stackIndex >= 0 && rootTrace[rootIndex].equals(stackTrace[stackIndex])) {
+ --rootIndex;
+ --stackIndex;
+ }
+ src.setCommonElementCount(stackTrace.length - 1 - stackIndex);
+ stackLength = stackIndex + 1;
+ } else {
+ src.setCommonElementCount(0);
+ stackLength = stackTrace.length;
+ }
+ final ExtendedStackTraceElement[] extStackTrace = new ExtendedStackTraceElement[stackLength];
+ Class<?> clazz = stack.isEmpty() ? null : stack.peek();
+ ClassLoader lastLoader = null;
+ for (int i = stackLength - 1; i >= 0; --i) {
+ final StackTraceElement stackTraceElement = stackTrace[i];
+ final String className = stackTraceElement.getClassName();
+ // The stack returned from getCurrentStack may be missing entries for java.lang.reflect.Method.invoke()
+ // and its implementation. The Throwable might also contain stack entries that are no longer
+ // present as those methods have returned.
+ ExtendedClassInfo extClassInfo;
+ if (clazz != null && className.equals(clazz.getName())) {
+ final CacheEntry entry = toCacheEntry(clazz, true);
+ extClassInfo = entry.element;
+ lastLoader = entry.loader;
+ stack.pop();
+ clazz = stack.isEmpty() ? null : stack.peek();
+ } else {
+ final CacheEntry cacheEntry = map.get(className);
+ if (cacheEntry != null) {
+ final CacheEntry entry = cacheEntry;
+ extClassInfo = entry.element;
+ if (entry.loader != null) {
+ lastLoader = entry.loader;
+ }
+ } else {
+ final CacheEntry entry = toCacheEntry(ThrowableProxyHelper.loadClass(lastLoader, className), false);
+ extClassInfo = entry.element;
+ map.put(className, entry);
+ if (entry.loader != null) {
+ lastLoader = entry.loader;
+ }
+ }
+ }
+ extStackTrace[i] = new ExtendedStackTraceElement(stackTraceElement, extClassInfo);
+ }
+ return extStackTrace;
+ }
+
+ static ThrowableProxy[] toSuppressedProxies(final Throwable thrown, Set<Throwable> suppressedVisited) {
+ try {
+ final Throwable[] suppressed = thrown.getSuppressed();
+ if (suppressed == null || suppressed.length == 0) {
+ return EMPTY_THROWABLE_PROXY_ARRAY;
+ }
+ final List<ThrowableProxy> proxies = new ArrayList<>(suppressed.length);
+ if (suppressedVisited == null) {
+ suppressedVisited = new HashSet<>(suppressed.length);
+ }
+ for (int i = 0; i < suppressed.length; i++) {
+ final Throwable candidate = suppressed[i];
+ if (suppressedVisited.add(candidate)) {
+ proxies.add(new ThrowableProxy(candidate, suppressedVisited));
+ }
+ }
+ return proxies.toArray(new ThrowableProxy[proxies.size()]);
+ } catch (final Exception e) {
+ StatusLogger.getLogger().error(e);
+ }
+ return null;
+ }
+
+ /**
+ * Construct the CacheEntry from the Class's information.
+ *
+ * @param callerClass The Class.
+ * @param exact True if the class was obtained via Reflection.getCallerClass.
+ * @return The CacheEntry.
+ */
+ private static CacheEntry toCacheEntry(final Class<?> callerClass, final boolean exact) {
+ String location = "?";
+ String version = "?";
+ ClassLoader lastLoader = null;
+ if (callerClass != null) {
+ try {
+ final CodeSource source = callerClass.getProtectionDomain().getCodeSource();
+ if (source != null) {
+ final URL locationURL = source.getLocation();
+ if (locationURL != null) {
+ final String str = locationURL.toString().replace('\\', '/');
+ int index = str.lastIndexOf("/");
+ if (index >= 0 && index == str.length() - 1) {
+ index = str.lastIndexOf("/", index - 1);
+ location = str.substring(index + 1);
+ } else {
+ location = str.substring(index + 1);
+ }
+ }
+ }
+ } catch (final Exception ex) {
+ // Ignore the exception.
+ }
+ final Package pkg = callerClass.getPackage();
+ if (pkg != null) {
+ final String ver = pkg.getImplementationVersion();
+ if (ver != null) {
+ version = ver;
+ }
+ }
+ try {
+ lastLoader = callerClass.getClassLoader();
+ } catch (final SecurityException e) {
+ lastLoader = null;
+ }
+ }
+ return new CacheEntry(new ExtendedClassInfo(exact, location, version), lastLoader);
+ }
+
+
+ /**
+ * Loads classes not located via Reflection.getCallerClass.
+ *
+ * @param lastLoader The ClassLoader that loaded the Class that called this Class.
+ * @param className The name of the Class.
+ * @return The Class object for the Class or null if it could not be located.
+ */
+ private static Class<?> loadClass(final ClassLoader lastLoader, final String className) {
+ // XXX: this is overly complicated
+ Class<?> clazz;
+ if (lastLoader != null) {
+ try {
+ clazz = lastLoader.loadClass(className);
+ if (clazz != null) {
+ return clazz;
+ }
+ } catch (final Throwable ignore) {
+ // Ignore exception.
+ }
+ }
+ try {
+ clazz = LoaderUtil.loadClass(className);
+ } catch (final ClassNotFoundException | NoClassDefFoundError e) {
+ return loadClass(className);
+ } catch (final SecurityException e) {
+ return null;
+ }
+ return clazz;
+ }
+
+ private static Class<?> loadClass(final String className) {
+ try {
+ return Loader.loadClass(className, ThrowableProxyHelper.class.getClassLoader());
+ } catch (final ClassNotFoundException | NoClassDefFoundError | SecurityException e) {
+ return null;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/32394238/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyRenderer.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyRenderer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyRenderer.java
new file mode 100644
index 0000000..d86d2bb
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyRenderer.java
@@ -0,0 +1,217 @@
+/*
+ * 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 org.apache.logging.log4j.core.pattern.TextRenderer;
+import org.apache.logging.log4j.util.Strings;
+
+import java.util.List;
+
+/**
+ * {@link ThrowableProxyRenderer} is an internal utility providing the code to render a {@link ThrowableProxy}
+ * to a {@link StringBuilder}.
+ */
+class ThrowableProxyRenderer {
+
+ private static final String TAB = "\t";
+ private static final String CAUSED_BY_LABEL = "Caused by: ";
+ private static final String SUPPRESSED_LABEL = "Suppressed: ";
+ private static final String WRAPPED_BY_LABEL = "Wrapped by: ";
+
+ private ThrowableProxyRenderer() {
+ // Utility Class
+ }
+
+ @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+ static void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages,
+ final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
+ final Throwable caused = cause.getCauseProxy() != null ? cause.getCauseProxy().getThrowable() : null;
+ if (caused != null) {
+ formatWrapper(sb, cause.getCauseProxy(), ignorePackages, textRenderer, suffix, lineSeparator);
+ sb.append(WRAPPED_BY_LABEL);
+ renderSuffix(suffix, sb, textRenderer);
+ }
+ renderOn(cause, sb, textRenderer);
+ renderSuffix(suffix, sb, textRenderer);
+ textRenderer.render(lineSeparator, sb, "Text");
+ formatElements(sb, Strings.EMPTY, cause.getCommonElementCount(),
+ cause.getThrowable().getStackTrace(), cause.getExtendedStackTrace(), ignorePackages, textRenderer, suffix, lineSeparator);
+ }
+
+ private static void formatCause(final StringBuilder sb, final String prefix, final ThrowableProxy cause,
+ final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
+ formatThrowableProxy(sb, prefix, CAUSED_BY_LABEL, cause, ignorePackages, textRenderer, suffix, lineSeparator);
+ }
+
+ private static void formatThrowableProxy(final StringBuilder sb, final String prefix, final String causeLabel,
+ final ThrowableProxy throwableProxy, final List<String> ignorePackages,
+ final TextRenderer textRenderer, final String suffix, String lineSeparator) {
+ if (throwableProxy == null) {
+ return;
+ }
+ textRenderer.render(prefix, sb, "Prefix");
+ textRenderer.render(causeLabel, sb, "CauseLabel");
+ renderOn(throwableProxy, sb, textRenderer);
+ renderSuffix(suffix, sb, textRenderer);
+ textRenderer.render(lineSeparator, sb, "Text");
+ formatElements(sb, prefix, throwableProxy.getCommonElementCount(),
+ throwableProxy.getStackTrace(), throwableProxy.getExtendedStackTrace(), ignorePackages, textRenderer, suffix, lineSeparator);
+ formatSuppressed(sb, prefix + TAB, throwableProxy.getSuppressedProxies(), ignorePackages, textRenderer, suffix, lineSeparator);
+ formatCause(sb, prefix, throwableProxy.getCauseProxy(), ignorePackages, textRenderer, suffix, lineSeparator);
+ }
+
+ private static void formatSuppressed(final StringBuilder sb, final String prefix, final ThrowableProxy[] suppressedProxies,
+ final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
+ if (suppressedProxies == null) {
+ return;
+ }
+ for (final ThrowableProxy suppressedProxy : suppressedProxies) {
+ formatThrowableProxy(sb, prefix, SUPPRESSED_LABEL, suppressedProxy, ignorePackages, textRenderer, suffix, lineSeparator);
+ }
+ }
+
+ private static void formatElements(final StringBuilder sb, final String prefix, final int commonCount,
+ final StackTraceElement[] causedTrace, final ExtendedStackTraceElement[] extStackTrace,
+ final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
+ if (ignorePackages == null || ignorePackages.isEmpty()) {
+ for (final ExtendedStackTraceElement element : extStackTrace) {
+ formatEntry(element, sb, prefix, textRenderer, suffix, lineSeparator);
+ }
+ } else {
+ int count = 0;
+ for (int i = 0; i < extStackTrace.length; ++i) {
+ if (!ignoreElement(causedTrace[i], ignorePackages)) {
+ if (count > 0) {
+ appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator);
+ count = 0;
+ }
+ formatEntry(extStackTrace[i], sb, prefix, textRenderer, suffix, lineSeparator);
+ } else {
+ ++count;
+ }
+ }
+ if (count > 0) {
+ appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator);
+ }
+ }
+ if (commonCount != 0) {
+ textRenderer.render(prefix, sb, "Prefix");
+ textRenderer.render("\t... ", sb, "More");
+ textRenderer.render(Integer.toString(commonCount), sb, "More");
+ textRenderer.render(" more", sb, "More");
+ renderSuffix(suffix, sb, textRenderer);
+ textRenderer.render(lineSeparator, sb, "Text");
+ }
+ }
+
+ private static void renderSuffix(final String suffix, final StringBuilder sb, final TextRenderer textRenderer) {
+ if (!suffix.isEmpty()) {
+ textRenderer.render(" ", sb, "Suffix");
+ textRenderer.render(suffix, sb, "Suffix");
+ }
+ }
+
+ private static void appendSuppressedCount(final StringBuilder sb, final String prefix, final int count,
+ final TextRenderer textRenderer, final String suffix, String lineSeparator) {
+ textRenderer.render(prefix, sb, "Prefix");
+ if (count == 1) {
+ textRenderer.render("\t... ", sb, "Suppressed");
+ } else {
+ textRenderer.render("\t... suppressed ", sb, "Suppressed");
+ textRenderer.render(Integer.toString(count), sb, "Suppressed");
+ textRenderer.render(" lines", sb, "Suppressed");
+ }
+ renderSuffix(suffix, sb, textRenderer);
+ textRenderer.render(lineSeparator, sb, "Text");
+ }
+
+ private static void formatEntry(final ExtendedStackTraceElement extStackTraceElement, final StringBuilder sb,
+ final String prefix, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
+ textRenderer.render(prefix, sb, "Prefix");
+ textRenderer.render("\tat ", sb, "At");
+ extStackTraceElement.renderOn(sb, textRenderer);
+ renderSuffix(suffix, sb, textRenderer);
+ textRenderer.render(lineSeparator, sb, "Text");
+ }
+
+ private static boolean ignoreElement(final StackTraceElement element, final List<String> ignorePackages) {
+ if (ignorePackages != null) {
+ final String className = element.getClassName();
+ for (final String pkg : ignorePackages) {
+ if (className.startsWith(pkg)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Formats the stack trace including packaging information.
+ *
+ * @param src ThrowableProxy instance to format
+ * @param sb Destination.
+ * @param ignorePackages List of packages to be ignored in the trace.
+ * @param textRenderer The message renderer.
+ * @param suffix Append this to the end of each stack frame.
+ * @param lineSeparator The end-of-line separator.
+ */
+ static void formatExtendedStackTraceTo(ThrowableProxy src, final StringBuilder sb, final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
+ textRenderer.render(src.getName(), sb, "Name");
+ textRenderer.render(": ", sb, "NameMessageSeparator");
+ textRenderer.render(src.getMessage(), sb, "Message");
+ renderSuffix(suffix, sb, textRenderer);
+ textRenderer.render(lineSeparator, sb, "Text");
+ final StackTraceElement[] causedTrace = src.getThrowable() != null ? src.getThrowable().getStackTrace() : null;
+ formatElements(sb, Strings.EMPTY, 0, causedTrace, src.getExtendedStackTrace(), ignorePackages, textRenderer, suffix, lineSeparator);
+ formatSuppressed(sb, TAB, src.getSuppressedProxies(), ignorePackages, textRenderer, suffix, lineSeparator);
+ formatCause(sb, Strings.EMPTY, src.getCauseProxy(), ignorePackages, textRenderer, suffix, lineSeparator);
+ }
+
+ /**
+ * Formats the Throwable that is the cause of the <pre>src</pre> Throwable.
+ *
+ * @param src Throwable whose cause to render
+ * @param sb Destination to render the formatted Throwable that caused this Throwable onto.
+ * @param ignorePackages The List of packages to be suppressed from the stack trace.
+ * @param textRenderer The text renderer.
+ * @param suffix Append this to the end of each stack frame.
+ * @param lineSeparator The end-of-line separator.
+ */
+ static void formatCauseStackTrace(final ThrowableProxy src, final StringBuilder sb, final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
+ ThrowableProxy causeProxy = src.getCauseProxy();
+ if (causeProxy != null) {
+ formatWrapper(sb, causeProxy, ignorePackages, textRenderer, suffix, lineSeparator);
+ sb.append(WRAPPED_BY_LABEL);
+ ThrowableProxyRenderer.renderSuffix(suffix, sb, textRenderer);
+ }
+ renderOn(src, sb, textRenderer);
+ ThrowableProxyRenderer.renderSuffix(suffix, sb, textRenderer);
+ textRenderer.render(lineSeparator, sb, "Text");
+ ThrowableProxyRenderer.formatElements(sb, Strings.EMPTY, 0, src.getStackTrace(), src.getExtendedStackTrace(),
+ ignorePackages, textRenderer, suffix, lineSeparator);
+ }
+
+ private static void renderOn(final ThrowableProxy src, final StringBuilder output, final TextRenderer textRenderer) {
+ final String msg = src.getMessage();
+ textRenderer.render(src.getName(), output, "Name");
+ if (msg != null) {
+ textRenderer.render(": ", output, "NameMessageSeparator");
+ textRenderer.render(msg, output, "Message");
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/32394238/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyTest.java
index 813a5b7..f6268d4 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyTest.java
@@ -372,11 +372,11 @@ public class ThrowableProxyTest {
@Test
public void testStack() {
- final Map<String, ThrowableProxy.CacheEntry> map = new HashMap<>();
+ final Map<String, ThrowableProxyHelper.CacheEntry> map = new HashMap<>();
final Stack<Class<?>> stack = new Stack<>();
final Throwable throwable = new IllegalStateException("This is a test");
final ThrowableProxy proxy = new ThrowableProxy(throwable);
- final ExtendedStackTraceElement[] callerPackageData = proxy.toExtendedStackTrace(stack, map, null,
+ final ExtendedStackTraceElement[] callerPackageData = ThrowableProxyHelper.toExtendedStackTrace(proxy, stack, map, null,
throwable.getStackTrace());
assertNotNull("No package data returned", callerPackageData);
}
@@ -389,7 +389,7 @@ public class ThrowableProxyTest {
@Test
public void testStackWithUnloadableClass() throws Exception {
final Stack<Class<?>> stack = new Stack<>();
- final Map<String, ThrowableProxy.CacheEntry> map = new HashMap<>();
+ final Map<String, ThrowableProxyHelper.CacheEntry> map = new HashMap<>();
final String runtimeExceptionThrownAtUnloadableClass_base64 = "rO0ABXNyABpqYXZhLmxhbmcuUnVudGltZUV4Y2VwdGlvbp5fBkcKNIPlAgAAeHIAE2phdmEubGFuZy5FeGNlcHRpb27Q/R8+GjscxAIAAHhyABNqYXZhLmxhbmcuVGhyb3dhYmxl1cY1Jzl3uMsDAANMAAVjYXVzZXQAFUxqYXZhL2xhbmcvVGhyb3dhYmxlO0wADWRldGFpbE1lc3NhZ2V0ABJMamF2YS9sYW5nL1N0cmluZztbAApzdGFja1RyYWNldAAeW0xqYXZhL2xhbmcvU3RhY2tUcmFjZUVsZW1lbnQ7eHBxAH4ABnB1cgAeW0xqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnQ7AkYqPDz9IjkCAAB4cAAAAAFzcgAbamF2YS5sYW5nLlN0YWNrVHJhY2VFbGVtZW50YQnFmiY23YUCAARJAApsaW5lTnVtYmVyTAAOZGVjbGFyaW5nQ2xhc3NxAH4ABEwACGZpbGVOYW1lcQB+AARMAAptZXRob2ROYW1lcQB+AAR4cAAAAAZ0ADxvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLkZvcmNlTm9EZWZDbGFzc0ZvdW5kRXJyb3J0AB5Gb3JjZU5vRGVmQ2xhc3NGb3VuZEVycm9yLmphdmF0AARtYWlueA==";
final byte[] binaryDecoded = Base64Converter
@@ -399,7 +399,7 @@ public class ThrowableProxyTest {
final Throwable throwable = (Throwable) in.readObject();
final ThrowableProxy subject = new ThrowableProxy(throwable);
- subject.toExtendedStackTrace(stack, map, null, throwable.getStackTrace());
+ ThrowableProxyHelper.toExtendedStackTrace(subject, stack, map, null, throwable.getStackTrace());
}
/**