All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.logging.log4j.core.impl.ThrowableProxy Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta2
Show newest version
/*
 * 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 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;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.ReflectionUtil;
import org.apache.logging.log4j.util.Strings;

/**
 * Wraps a Throwable to add packaging information about each stack trace element.
 * 
 * 

* A proxy is used to represent a throwable that may not exist in a different class loader or JVM. When an application * deserializes a ThrowableProxy, the throwable may not be set, but the throwable's information is preserved in other * fields of the proxy like the message and stack trace. *

* *

* TODO: Move this class to org.apache.logging.log4j.core because it is used from LogEvent. *

*

* TODO: Deserialize: Try to rebuild Throwable if the target exception is in this class loader? *

*/ public class ThrowableProxy implements Serializable { 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. *

* Consider this class private. *

*/ 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 long serialVersionUID = -2752771578252251910L; private final ThrowableProxy causeProxy; private int commonElementCount; private final ExtendedStackTraceElement[] extendedStackTrace; private final String localizedMessage; private final String message; private final String name; private final ThrowableProxy[] suppressedProxies; private final transient Throwable throwable; /** * For JSON and XML IO via Jackson. */ @SuppressWarnings("unused") private ThrowableProxy() { this.throwable = null; this.name = null; this.extendedStackTrace = null; this.causeProxy = null; this.message = null; this.localizedMessage = null; this.suppressedProxies = EMPTY_THROWABLE_PROXY_ARRAY; } /** * Constructs the wrapper for the Throwable that includes packaging data. * * @param throwable * The Throwable to wrap, must not be null. */ public ThrowableProxy(final Throwable throwable) { this(throwable, null); } /** * Constructs the wrapper for the Throwable that includes packaging data. * * @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 visited) { this.throwable = throwable; this.name = throwable.getClass().getName(); this.message = throwable.getMessage(); this.localizedMessage = throwable.getLocalizedMessage(); final Map map = new HashMap<>(); final Stack> stack = ReflectionUtil.getCurrentStackTrace(); this.extendedStackTrace = this.toExtendedStackTrace(stack, map, null, throwable.getStackTrace()); final Throwable throwableCause = throwable.getCause(); final Set causeVisited = new HashSet<>(1); this.causeProxy = throwableCause == null ? null : new ThrowableProxy(throwable, stack, map, throwableCause, visited, causeVisited); this.suppressedProxies = this.toSuppressedProxies(throwable, visited); } /** * Constructs the wrapper for a Throwable that is referenced as the cause by another Throwable. * * @param parent * The Throwable referencing this Throwable. * @param stack * The Class stack. * @param map * The cache containing the packaging data. * @param cause * The Throwable to wrap. * @param suppressedVisited TODO * @param causeVisited TODO */ private ThrowableProxy(final Throwable parent, final Stack> stack, final Map map, final Throwable cause, final Set suppressedVisited, final Set causeVisited) { causeVisited.add(cause); this.throwable = cause; 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()); 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); } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (this.getClass() != obj.getClass()) { return false; } final ThrowableProxy other = (ThrowableProxy) obj; if (this.causeProxy == null) { if (other.causeProxy != null) { return false; } } else if (!this.causeProxy.equals(other.causeProxy)) { return false; } if (this.commonElementCount != other.commonElementCount) { return false; } if (this.name == null) { if (other.name != null) { return false; } } else if (!this.name.equals(other.name)) { return false; } if (!Arrays.equals(this.extendedStackTrace, other.extendedStackTrace)) { return false; } if (!Arrays.equals(this.suppressedProxies, other.suppressedProxies)) { return false; } return true; } private void formatCause(final StringBuilder sb, final String prefix, final ThrowableProxy cause, final List ignorePackages) { formatThrowableProxy(sb, prefix, CAUSED_BY_LABEL, cause, ignorePackages); } private void formatThrowableProxy(final StringBuilder sb, final String prefix, final String causeLabel, final ThrowableProxy throwableProxy, final List ignorePackages) { if (throwableProxy == null) { return; } sb.append(prefix).append(causeLabel).append(throwableProxy).append(EOL); this.formatElements(sb, prefix, throwableProxy.commonElementCount, throwableProxy.getStackTrace(), throwableProxy.extendedStackTrace, ignorePackages); this.formatSuppressed(sb, prefix + "\t", throwableProxy.suppressedProxies, ignorePackages); this.formatCause(sb, prefix, throwableProxy.causeProxy, ignorePackages); } private void formatSuppressed(final StringBuilder sb, final String prefix, final ThrowableProxy[] suppressedProxies, final List ignorePackages) { if (suppressedProxies == null) { return; } for (final ThrowableProxy suppressedProxy : suppressedProxies) { final ThrowableProxy cause = suppressedProxy; formatThrowableProxy(sb, prefix, SUPPRESSED_LABEL, cause, ignorePackages); } } private void formatElements(final StringBuilder sb, final String prefix, final int commonCount, final StackTraceElement[] causedTrace, final ExtendedStackTraceElement[] extStackTrace, final List ignorePackages) { if (ignorePackages == null || ignorePackages.isEmpty()) { for (final ExtendedStackTraceElement element : extStackTrace) { this.formatEntry(element, sb, prefix); } } 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); count = 0; } this.formatEntry(extStackTrace[i], sb, prefix); } else { ++count; } } if (count > 0) { appendSuppressedCount(sb, prefix, count); } } if (commonCount != 0) { sb.append(prefix).append("\t... ").append(commonCount).append(" more").append(EOL); } } private void appendSuppressedCount(final StringBuilder sb, final String prefix, final int count) { sb.append(prefix); if (count == 1) { sb.append("\t....").append(EOL); } else { sb.append("\t... suppressed ").append(count).append(" lines").append(EOL); } } private void formatEntry(final ExtendedStackTraceElement extStackTraceElement, final StringBuilder sb, final String prefix) { sb.append(prefix); sb.append("\tat "); sb.append(extStackTraceElement); sb.append(EOL); } /** * Formats the specified Throwable. * * @param sb * StringBuilder to contain the formatted Throwable. * @param cause * The Throwable to format. */ public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause) { this.formatWrapper(sb, cause, null); } /** * Formats the specified Throwable. * * @param sb * StringBuilder to contain the formatted Throwable. * @param cause * The Throwable to format. * @param packages * The List of packages to be suppressed from the trace. */ @SuppressWarnings("ThrowableResultOfMethodCallIgnored") public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List packages) { final Throwable caused = cause.getCauseProxy() != null ? cause.getCauseProxy().getThrowable() : null; if (caused != null) { this.formatWrapper(sb, cause.causeProxy); sb.append(WRAPPED_BY_LABEL); } sb.append(cause).append(EOL); this.formatElements(sb, "", cause.commonElementCount, cause.getThrowable().getStackTrace(), cause.extendedStackTrace, packages); } public ThrowableProxy getCauseProxy() { return this.causeProxy; } /** * Format the Throwable that is the cause of this Throwable. * * @return The formatted Throwable that caused this Throwable. */ public String getCauseStackTraceAsString() { return this.getCauseStackTraceAsString(null); } /** * Format the Throwable that is the cause of this Throwable. * * @param packages * The List of packages to be suppressed from the trace. * @return The formatted Throwable that caused this Throwable. */ public String getCauseStackTraceAsString(final List packages) { final StringBuilder sb = new StringBuilder(); if (this.causeProxy != null) { this.formatWrapper(sb, this.causeProxy); sb.append(WRAPPED_BY_LABEL); } sb.append(this.toString()); sb.append(EOL); this.formatElements(sb, "", 0, this.throwable.getStackTrace(), this.extendedStackTrace, packages); return sb.toString(); } /** * Return the number of elements that are being omitted because they are common with the parent Throwable's stack * trace. * * @return The number of elements omitted from the stack trace. */ public int getCommonElementCount() { return this.commonElementCount; } /** * Gets the stack trace including packaging information. * * @return The stack trace including packaging information. */ public ExtendedStackTraceElement[] getExtendedStackTrace() { return this.extendedStackTrace; } /** * Format the stack trace including packaging information. * * @return The formatted stack trace including packaging information. */ public String getExtendedStackTraceAsString() { return this.getExtendedStackTraceAsString(null); } /** * Format the stack trace including packaging information. * * @param ignorePackages * List of packages to be ignored in the trace. * @return The formatted stack trace including packaging information. */ public String getExtendedStackTraceAsString(final List ignorePackages) { final StringBuilder sb = new StringBuilder(this.name); final String msg = this.message; if (msg != null) { sb.append(": ").append(msg); } sb.append(EOL); final StackTraceElement[] causedTrace = this.throwable != null ? this.throwable.getStackTrace() : null; this.formatElements(sb, "", 0, causedTrace, this.extendedStackTrace, ignorePackages); this.formatSuppressed(sb, "\t", this.suppressedProxies, ignorePackages); this.formatCause(sb, "", this.causeProxy, ignorePackages); return sb.toString(); } public String getLocalizedMessage() { return this.localizedMessage; } public String getMessage() { return this.message; } /** * Return the FQCN of the Throwable. * * @return The FQCN of the Throwable. */ public String getName() { return this.name; } public StackTraceElement[] getStackTrace() { return this.throwable == null ? null : this.throwable.getStackTrace(); } /** * Gets proxies for suppressed exceptions. * * @return proxies for suppressed exceptions. */ public ThrowableProxy[] getSuppressedProxies() { return this.suppressedProxies; } /** * Format the suppressed Throwables. * * @return The formatted suppressed Throwables. */ public String getSuppressedStackTrace() { final ThrowableProxy[] suppressed = this.getSuppressedProxies(); if (suppressed == null || suppressed.length == 0) { return Strings.EMPTY; } final StringBuilder sb = new StringBuilder("Suppressed Stack Trace Elements:").append(EOL); for (final ThrowableProxy proxy : suppressed) { sb.append(proxy.getExtendedStackTraceAsString()); } return sb.toString(); } /** * The throwable or null if this object is deserialized from XML or JSON. * * @return The throwable or null if this object is deserialized from XML or JSON. */ public Throwable getThrowable() { return this.throwable; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (this.causeProxy == null ? 0 : this.causeProxy.hashCode()); result = prime * result + this.commonElementCount; result = prime * result + (this.extendedStackTrace == null ? 0 : Arrays.hashCode(this.extendedStackTrace)); result = prime * result + (this.suppressedProxies == null ? 0 : Arrays.hashCode(this.suppressedProxies)); result = prime * result + (this.name == null ? 0 : this.name.hashCode()); return result; } private boolean ignoreElement(final StackTraceElement element, final List ignorePackages) { 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 = Loader.initializeClass(className, lastLoader); if (clazz != null) { return clazz; } } catch (final Throwable ignore) { // Ignore exception. } } try { clazz = Loader.loadClass(className); } catch (final ClassNotFoundException ignored) { return initializeClass(className); } catch (final NoClassDefFoundError ignored) { return initializeClass(className); } return clazz; } private Class initializeClass(final String className) { try { return Loader.initializeClass(className, this.getClass().getClassLoader()); } catch (final ClassNotFoundException ignore) { return null; } catch (final NoClassDefFoundError ignore) { 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 StackTraceElement stackTraceElement, 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; } } lastLoader = callerClass.getClassLoader(); } 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> stack, final Map 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(stackTraceElement, 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(stackTraceElement, this.loadClass(lastLoader, className), false); extClassInfo = entry.element; map.put(stackTraceElement.toString(), 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 suppressedVisited) { try { final Throwable[] suppressed = thrown.getSuppressed(); if (suppressed == null) { return EMPTY_THROWABLE_PROXY_ARRAY; } final List proxies = new ArrayList<>(suppressed.length); if (suppressedVisited == null) { suppressedVisited = new HashSet<>(proxies.size()); } for (int i = 0; i < suppressed.length; i++) { final Throwable candidate = suppressed[i]; if (!suppressedVisited.contains(candidate)) { 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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy