Repository: logging-log4j2 Updated Branches: refs/heads/master 7333573d9 -> de6593f5f
[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/de6593f5 Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/de6593f5 Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/de6593f5 Branch: refs/heads/master Commit: de6593f5f3b5acbb2cdb4ee91e4e1f472db5dcbb Parents: 7333573 Author: Carter Kozak <cko...@apache.org> Authored: Tue Aug 21 11:58:20 2018 -0400 Committer: Carter Kozak <cko...@apache.org> Committed: Mon Sep 3 19:32:26 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/de6593f5/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/de6593f5/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/de6593f5/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/de6593f5/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 ba09c99..8a9b83e 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 @@ -362,11 +362,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); } @@ -379,7 +379,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 @@ -389,7 +389,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()); } /**