| /* |
| * 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.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.pattern.PlainTextRenderer; |
| import org.apache.logging.log4j.core.pattern.TextRenderer; |
| import org.apache.logging.log4j.util.StackLocatorUtil; |
| import org.apache.logging.log4j.util.Strings; |
| |
| /** |
| * Wraps a Throwable to add packaging information about each stack trace element. |
| * |
| * <p> |
| * 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. |
| * </p> |
| * |
| * <p> |
| * TODO: Move this class to org.apache.logging.log4j.core because it is used from LogEvent. |
| * </p> |
| * <p> |
| * TODO: Deserialize: Try to rebuild Throwable if the target exception is in this class loader? |
| * </p> |
| */ |
| public class ThrowableProxy implements Serializable { |
| |
| static final ThrowableProxy[] EMPTY_ARRAY = {}; |
| |
| private static final char EOL = '\n'; |
| |
| private static final String EOL_STR = String.valueOf(EOL); |
| |
| 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") |
| ThrowableProxy() { |
| this.throwable = null; |
| this.name = null; |
| this.extendedStackTrace = ExtendedStackTraceElement.EMPTY_ARRAY; |
| this.causeProxy = null; |
| this.message = null; |
| this.localizedMessage = null; |
| this.suppressedProxies = ThrowableProxy.EMPTY_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. |
| */ |
| 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, ThrowableProxyHelper.CacheEntry> map = new HashMap<>(); |
| final Stack<Class<?>> stack = StackLocatorUtil.getCurrentStackTrace(); |
| 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 = ThrowableProxyHelper.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<Class<?>> stack, |
| final Map<String, ThrowableProxyHelper.CacheEntry> map, |
| final Throwable cause, final Set<Throwable> suppressedVisited, |
| final Set<Throwable> causeVisited) { |
| causeVisited.add(cause); |
| this.throwable = cause; |
| this.name = cause.getClass().getName(); |
| this.message = this.throwable.getMessage(); |
| this.localizedMessage = this.throwable.getLocalizedMessage(); |
| 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 = ThrowableProxyHelper.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; |
| } |
| |
| /** |
| * Formats the specified Throwable. |
| * @param sb StringBuilder to contain the formatted Throwable. |
| * @param cause The Throwable to format. |
| * @param suffix |
| */ |
| public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final String suffix) { |
| this.formatWrapper(sb, cause, null, PlainTextRenderer.getInstance(), suffix); |
| } |
| |
| /** |
| * Formats the specified Throwable. |
| * @param sb StringBuilder to contain the formatted Throwable. |
| * @param cause The Throwable to format. |
| * @param ignorePackages The List of packages to be suppressed from the trace. |
| * @param suffix |
| */ |
| @SuppressWarnings("ThrowableResultOfMethodCallIgnored") |
| public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages, final String suffix) { |
| this.formatWrapper(sb, cause, ignorePackages, PlainTextRenderer.getInstance(), suffix); |
| } |
| |
| /** |
| * Formats the specified Throwable. |
| * @param sb StringBuilder to contain the formatted Throwable. |
| * @param cause The Throwable to format. |
| * @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. |
| */ |
| @SuppressWarnings("ThrowableResultOfMethodCallIgnored") |
| public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages, |
| final TextRenderer textRenderer, final String suffix) { |
| formatWrapper(sb, cause, ignorePackages, textRenderer, suffix, EOL_STR); |
| } |
| |
| /** |
| * Formats the specified Throwable. |
| * @param sb StringBuilder to contain the formatted Throwable. |
| * @param cause The Throwable to format. |
| * @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. |
| */ |
| @SuppressWarnings("ThrowableResultOfMethodCallIgnored") |
| public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages, |
| final TextRenderer textRenderer, final String suffix, final String lineSeparator) { |
| ThrowableProxyRenderer.formatWrapper(sb, cause, ignorePackages, textRenderer, suffix, lineSeparator); |
| } |
| |
| public ThrowableProxy getCauseProxy() { |
| return this.causeProxy; |
| } |
| |
| /** |
| * Formats the Throwable that is the cause of this Throwable. |
| * |
| * @return The formatted Throwable that caused this Throwable. |
| * @param suffix |
| */ |
| public String getCauseStackTraceAsString(final String suffix) { |
| return this.getCauseStackTraceAsString(null, PlainTextRenderer.getInstance(), suffix, EOL_STR); |
| } |
| |
| /** |
| * Formats the Throwable that is the cause of this Throwable. |
| * |
| * @param packages The List of packages to be suppressed from the trace. |
| * @param suffix Append this to the end of each stack frame. |
| * @return The formatted Throwable that caused this Throwable. |
| */ |
| public String getCauseStackTraceAsString(final List<String> packages, final String suffix) { |
| return getCauseStackTraceAsString(packages, PlainTextRenderer.getInstance(), suffix, EOL_STR); |
| } |
| |
| /** |
| * Formats the Throwable that is the cause of this Throwable. |
| * |
| * @param ignorePackages The List of packages to be suppressed from the trace. |
| * @param textRenderer The text renderer. |
| * @param suffix Append this to the end of each stack frame. |
| * @return The formatted Throwable that caused this Throwable. |
| */ |
| public String getCauseStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix) { |
| return getCauseStackTraceAsString(ignorePackages, textRenderer, suffix, EOL_STR); |
| } |
| |
| /** |
| * Formats the Throwable that is the cause of this Throwable. |
| * |
| * @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. |
| * @return The formatted Throwable that caused this Throwable. |
| */ |
| public String getCauseStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { |
| final StringBuilder sb = new StringBuilder(); |
| ThrowableProxyRenderer.formatCauseStackTrace(this, sb, ignorePackages, textRenderer, suffix, lineSeparator); |
| return sb.toString(); |
| } |
| |
| /** |
| * Returns 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; |
| } |
| |
| /** |
| * 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(final int value) { |
| this.commonElementCount = value; |
| } |
| |
| /** |
| * Gets the stack trace including packaging information. |
| * |
| * @return The stack trace including packaging information. |
| */ |
| public ExtendedStackTraceElement[] getExtendedStackTrace() { |
| return this.extendedStackTrace; |
| } |
| |
| /** |
| * Formats the stack trace including packaging information. |
| * |
| * @return The formatted stack trace including packaging information. |
| */ |
| public String getExtendedStackTraceAsString() { |
| return this.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance(), Strings.EMPTY, EOL_STR); |
| } |
| |
| /** |
| * Formats the stack trace including packaging information. |
| * |
| * @return The formatted stack trace including packaging information. |
| * @param suffix Append this to the end of each stack frame. |
| */ |
| public String getExtendedStackTraceAsString(final String suffix) { |
| return this.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance(), suffix, EOL_STR); |
| } |
| |
| /** |
| * Formats the stack trace including packaging information. |
| * |
| * @param ignorePackages List of packages to be ignored in the trace. |
| * @param suffix Append this to the end of each stack frame. |
| * @return The formatted stack trace including packaging information. |
| */ |
| public String getExtendedStackTraceAsString(final List<String> ignorePackages, final String suffix) { |
| return getExtendedStackTraceAsString(ignorePackages, PlainTextRenderer.getInstance(), suffix, EOL_STR); |
| } |
| |
| /** |
| * Formats the stack trace including packaging information. |
| * |
| * @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. |
| * @return The formatted stack trace including packaging information. |
| */ |
| public String getExtendedStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix) { |
| return getExtendedStackTraceAsString(ignorePackages, textRenderer, suffix, EOL_STR); |
| } |
| |
| /** |
| * Formats the stack trace including packaging information. |
| * |
| * @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. |
| * @return The formatted stack trace including packaging information. |
| */ |
| public String getExtendedStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { |
| final StringBuilder sb = new StringBuilder(1024); |
| formatExtendedStackTraceTo(sb, ignorePackages, textRenderer, suffix, lineSeparator); |
| return sb.toString(); |
| } |
| |
| /** |
| * Formats the stack trace including packaging information. |
| * |
| * @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. |
| */ |
| public void formatExtendedStackTraceTo(final StringBuilder sb, final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { |
| ThrowableProxyRenderer.formatExtendedStackTraceTo(this, sb, ignorePackages, textRenderer, suffix, lineSeparator); |
| } |
| |
| 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; |
| } |
| |
| /** |
| * Formats the suppressed Throwables. |
| * |
| * @return The formatted suppressed Throwables. |
| * @param suffix |
| */ |
| public String getSuppressedStackTrace(final String suffix) { |
| 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(suffix)); |
| } |
| 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; |
| } |
| |
| @Override |
| public String toString() { |
| final String msg = this.message; |
| return msg != null ? this.name + ": " + msg : this.name; |
| } |
| } |