001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.impl;
018
019import java.io.Serializable;
020import java.util.Arrays;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.Stack;
027
028import org.apache.logging.log4j.core.pattern.PlainTextRenderer;
029import org.apache.logging.log4j.core.pattern.TextRenderer;
030import org.apache.logging.log4j.util.StackLocatorUtil;
031import org.apache.logging.log4j.util.Strings;
032
033/**
034 * Wraps a Throwable to add packaging information about each stack trace element.
035 *
036 * <p>
037 * A proxy is used to represent a throwable that may not exist in a different class loader or JVM. When an application
038 * deserializes a ThrowableProxy, the throwable may not be set, but the throwable's information is preserved in other
039 * fields of the proxy like the message and stack trace.
040 * </p>
041 *
042 * <p>
043 * TODO: Move this class to org.apache.logging.log4j.core because it is used from LogEvent.
044 * </p>
045 * <p>
046 * TODO: Deserialize: Try to rebuild Throwable if the target exception is in this class loader?
047 * </p>
048 */
049public class ThrowableProxy implements Serializable {
050
051    private static final char EOL = '\n';
052
053    private static final String EOL_STR = String.valueOf(EOL);
054
055    private static final long serialVersionUID = -2752771578252251910L;
056
057    private final ThrowableProxy causeProxy;
058
059    private int commonElementCount;
060
061    private final ExtendedStackTraceElement[] extendedStackTrace;
062
063    private final String localizedMessage;
064
065    private final String message;
066
067    private final String name;
068
069    private final ThrowableProxy[] suppressedProxies;
070
071    private final transient Throwable throwable;
072
073    /**
074     * For JSON and XML IO via Jackson.
075     */
076    @SuppressWarnings("unused")
077    private ThrowableProxy() {
078        this.throwable = null;
079        this.name = null;
080        this.extendedStackTrace = null;
081        this.causeProxy = null;
082        this.message = null;
083        this.localizedMessage = null;
084        this.suppressedProxies = ThrowableProxyHelper.EMPTY_THROWABLE_PROXY_ARRAY;
085    }
086
087    /**
088     * Constructs the wrapper for the Throwable that includes packaging data.
089     *
090     * @param throwable The Throwable to wrap, must not be null.
091     */
092    public ThrowableProxy(final Throwable throwable) {
093        this(throwable, null);
094    }
095
096    /**
097     * Constructs the wrapper for the Throwable that includes packaging data.
098     *
099     * @param throwable The Throwable to wrap, must not be null.
100     * @param visited   The set of visited suppressed exceptions.
101     */
102    ThrowableProxy(final Throwable throwable, final Set<Throwable> visited) {
103        this.throwable = throwable;
104        this.name = throwable.getClass().getName();
105        this.message = throwable.getMessage();
106        this.localizedMessage = throwable.getLocalizedMessage();
107        final Map<String, ThrowableProxyHelper.CacheEntry> map = new HashMap<>();
108        final Stack<Class<?>> stack = StackLocatorUtil.getCurrentStackTrace();
109        this.extendedStackTrace = ThrowableProxyHelper.toExtendedStackTrace(this, stack, map, null, throwable.getStackTrace());
110        final Throwable throwableCause = throwable.getCause();
111        final Set<Throwable> causeVisited = new HashSet<>(1);
112        this.causeProxy = throwableCause == null ? null : new ThrowableProxy(throwable, stack, map, throwableCause,
113            visited, causeVisited);
114        this.suppressedProxies = ThrowableProxyHelper.toSuppressedProxies(throwable, visited);
115    }
116
117    /**
118     * Constructs the wrapper for a Throwable that is referenced as the cause by another Throwable.
119     *
120     * @param parent            The Throwable referencing this Throwable.
121     * @param stack             The Class stack.
122     * @param map               The cache containing the packaging data.
123     * @param cause             The Throwable to wrap.
124     * @param suppressedVisited TODO
125     * @param causeVisited      TODO
126     */
127    private ThrowableProxy(final Throwable parent, final Stack<Class<?>> stack,
128                           final Map<String, ThrowableProxyHelper.CacheEntry> map,
129                           final Throwable cause, final Set<Throwable> suppressedVisited,
130                           final Set<Throwable> causeVisited) {
131        causeVisited.add(cause);
132        this.throwable = cause;
133        this.name = cause.getClass().getName();
134        this.message = this.throwable.getMessage();
135        this.localizedMessage = this.throwable.getLocalizedMessage();
136        this.extendedStackTrace = ThrowableProxyHelper.toExtendedStackTrace(this, stack, map, parent.getStackTrace(), cause.getStackTrace());
137        final Throwable causeCause = cause.getCause();
138        this.causeProxy = causeCause == null || causeVisited.contains(causeCause) ? null : new ThrowableProxy(parent,
139            stack, map, causeCause, suppressedVisited, causeVisited);
140        this.suppressedProxies = ThrowableProxyHelper.toSuppressedProxies(cause, suppressedVisited);
141    }
142
143    @Override
144    public boolean equals(final Object obj) {
145        if (this == obj) {
146            return true;
147        }
148        if (obj == null) {
149            return false;
150        }
151        if (this.getClass() != obj.getClass()) {
152            return false;
153        }
154        final ThrowableProxy other = (ThrowableProxy) obj;
155        if (this.causeProxy == null) {
156            if (other.causeProxy != null) {
157                return false;
158            }
159        } else if (!this.causeProxy.equals(other.causeProxy)) {
160            return false;
161        }
162        if (this.commonElementCount != other.commonElementCount) {
163            return false;
164        }
165        if (this.name == null) {
166            if (other.name != null) {
167                return false;
168            }
169        } else if (!this.name.equals(other.name)) {
170            return false;
171        }
172        if (!Arrays.equals(this.extendedStackTrace, other.extendedStackTrace)) {
173            return false;
174        }
175        if (!Arrays.equals(this.suppressedProxies, other.suppressedProxies)) {
176            return false;
177        }
178        return true;
179    }
180
181    /**
182     * Formats the specified Throwable.
183     *  @param sb    StringBuilder to contain the formatted Throwable.
184     * @param cause The Throwable to format.
185     * @param suffix
186     */
187    public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final String suffix) {
188        this.formatWrapper(sb, cause, null, PlainTextRenderer.getInstance(), suffix);
189    }
190
191    /**
192     * Formats the specified Throwable.
193     *  @param sb             StringBuilder to contain the formatted Throwable.
194     * @param cause          The Throwable to format.
195     * @param ignorePackages The List of packages to be suppressed from the trace.
196     * @param suffix
197     */
198    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
199    public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages, final String suffix) {
200        this.formatWrapper(sb, cause, ignorePackages, PlainTextRenderer.getInstance(), suffix);
201    }
202
203    /**
204     * Formats the specified Throwable.
205     * @param sb StringBuilder to contain the formatted Throwable.
206     * @param cause The Throwable to format.
207     * @param ignorePackages The List of packages to be suppressed from the stack trace.
208     * @param textRenderer The text renderer.
209     * @param suffix Append this to the end of each stack frame.
210     */
211    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
212    public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages,
213            final TextRenderer textRenderer, final String suffix) {
214        formatWrapper(sb, cause, ignorePackages, textRenderer, suffix, EOL_STR);
215    }
216
217    /**
218     * Formats the specified Throwable.
219     * @param sb StringBuilder to contain the formatted Throwable.
220     * @param cause The Throwable to format.
221     * @param ignorePackages The List of packages to be suppressed from the stack trace.
222     * @param textRenderer The text renderer.
223     * @param suffix Append this to the end of each stack frame.
224     * @param lineSeparator The end-of-line separator.
225     */
226    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
227    public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages,
228                              final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
229        ThrowableProxyRenderer.formatWrapper(sb,  cause, ignorePackages, textRenderer, suffix, lineSeparator);
230    }
231
232    public ThrowableProxy getCauseProxy() {
233        return this.causeProxy;
234    }
235
236    /**
237     * Formats the Throwable that is the cause of this Throwable.
238     *
239     * @return The formatted Throwable that caused this Throwable.
240     * @param suffix
241     */
242    public String getCauseStackTraceAsString(final String suffix) {
243        return this.getCauseStackTraceAsString(null, PlainTextRenderer.getInstance(), suffix, EOL_STR);
244    }
245
246    /**
247     * Formats the Throwable that is the cause of this Throwable.
248     *
249     * @param packages The List of packages to be suppressed from the trace.
250     * @param suffix Append this to the end of each stack frame.
251     * @return The formatted Throwable that caused this Throwable.
252     */
253    public String getCauseStackTraceAsString(final List<String> packages, final String suffix) {
254        return getCauseStackTraceAsString(packages, PlainTextRenderer.getInstance(), suffix, EOL_STR);
255    }
256
257    /**
258     * Formats the Throwable that is the cause of this Throwable.
259     *
260     * @param ignorePackages The List of packages to be suppressed from the trace.
261     * @param textRenderer The text renderer.
262     * @param suffix Append this to the end of each stack frame.
263     * @return The formatted Throwable that caused this Throwable.
264     */
265    public String getCauseStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix) {
266        return getCauseStackTraceAsString(ignorePackages, textRenderer, suffix, EOL_STR);
267    }
268
269    /**
270     * Formats the Throwable that is the cause of this Throwable.
271     *
272     * @param ignorePackages The List of packages to be suppressed from the stack trace.
273     * @param textRenderer The text renderer.
274     * @param suffix Append this to the end of each stack frame.
275     * @param lineSeparator The end-of-line separator.
276     * @return The formatted Throwable that caused this Throwable.
277     */
278    public String getCauseStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
279        final StringBuilder sb = new StringBuilder();
280        ThrowableProxyRenderer.formatCauseStackTrace(this, sb, ignorePackages, textRenderer, suffix, lineSeparator);
281        return sb.toString();
282    }
283
284    /**
285     * Returns the number of elements that are being omitted because they are common with the parent Throwable's stack
286     * trace.
287     *
288     * @return The number of elements omitted from the stack trace.
289     */
290    public int getCommonElementCount() {
291        return this.commonElementCount;
292    }
293
294    /**
295     * Set the value of {@link ThrowableProxy#commonElementCount}.
296     *
297     * Method is package-private, to be used internally for initialization.
298     *
299     * @param value New value of commonElementCount.
300     */
301    void setCommonElementCount(final int value) {
302        this.commonElementCount = value;
303    }
304
305    /**
306     * Gets the stack trace including packaging information.
307     *
308     * @return The stack trace including packaging information.
309     */
310    public ExtendedStackTraceElement[] getExtendedStackTrace() {
311        return this.extendedStackTrace;
312    }
313
314    /**
315     * Formats the stack trace including packaging information.
316     *
317     * @return The formatted stack trace including packaging information.
318     */
319    public String getExtendedStackTraceAsString() {
320        return this.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance(), Strings.EMPTY, EOL_STR);
321    }
322
323    /**
324     * Formats the stack trace including packaging information.
325     *
326     * @return The formatted stack trace including packaging information.
327     * @param suffix Append this to the end of each stack frame.
328     */
329    public String getExtendedStackTraceAsString(final String suffix) {
330        return this.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance(), suffix, EOL_STR);
331    }
332
333    /**
334     * Formats the stack trace including packaging information.
335     *
336     * @param ignorePackages List of packages to be ignored in the trace.
337     * @param suffix Append this to the end of each stack frame.
338     * @return The formatted stack trace including packaging information.
339     */
340    public String getExtendedStackTraceAsString(final List<String> ignorePackages, final String suffix) {
341        return getExtendedStackTraceAsString(ignorePackages, PlainTextRenderer.getInstance(), suffix, EOL_STR);
342    }
343
344    /**
345     * Formats the stack trace including packaging information.
346     *
347     * @param ignorePackages List of packages to be ignored in the trace.
348     * @param textRenderer The message renderer.
349     * @param suffix Append this to the end of each stack frame.
350     * @return The formatted stack trace including packaging information.
351     */
352    public String getExtendedStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix) {
353        return getExtendedStackTraceAsString(ignorePackages, textRenderer, suffix, EOL_STR);
354    }
355
356    /**
357     * Formats the stack trace including packaging information.
358     *
359     * @param ignorePackages List of packages to be ignored in the trace.
360     * @param textRenderer The message renderer.
361     * @param suffix Append this to the end of each stack frame.
362     * @param lineSeparator The end-of-line separator.
363     * @return The formatted stack trace including packaging information.
364     */
365    public String getExtendedStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
366        final StringBuilder sb = new StringBuilder(1024);
367        formatExtendedStackTraceTo(sb, ignorePackages, textRenderer, suffix, lineSeparator);
368        return sb.toString();
369    }
370
371    /**
372     * Formats the stack trace including packaging information.
373     *
374     * @param sb Destination.
375     * @param ignorePackages List of packages to be ignored in the trace.
376     * @param textRenderer The message renderer.
377     * @param suffix Append this to the end of each stack frame.
378     * @param lineSeparator The end-of-line separator.
379     */
380    public void formatExtendedStackTraceTo(final StringBuilder sb, final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
381        ThrowableProxyRenderer.formatExtendedStackTraceTo(this, sb, ignorePackages, textRenderer, suffix, lineSeparator);
382    }
383
384    public String getLocalizedMessage() {
385        return this.localizedMessage;
386    }
387
388    public String getMessage() {
389        return this.message;
390    }
391
392    /**
393     * Return the FQCN of the Throwable.
394     *
395     * @return The FQCN of the Throwable.
396     */
397    public String getName() {
398        return this.name;
399    }
400
401    public StackTraceElement[] getStackTrace() {
402        return this.throwable == null ? null : this.throwable.getStackTrace();
403    }
404
405    /**
406     * Gets proxies for suppressed exceptions.
407     *
408     * @return proxies for suppressed exceptions.
409     */
410    public ThrowableProxy[] getSuppressedProxies() {
411        return this.suppressedProxies;
412    }
413
414    /**
415     * Formats the suppressed Throwables.
416     *
417     * @return The formatted suppressed Throwables.
418     * @param suffix
419     */
420    public String getSuppressedStackTrace(final String suffix) {
421        final ThrowableProxy[] suppressed = this.getSuppressedProxies();
422        if (suppressed == null || suppressed.length == 0) {
423            return Strings.EMPTY;
424        }
425        final StringBuilder sb = new StringBuilder("Suppressed Stack Trace Elements:").append(EOL);
426        for (final ThrowableProxy proxy : suppressed) {
427            sb.append(proxy.getExtendedStackTraceAsString(suffix));
428        }
429        return sb.toString();
430    }
431
432    /**
433     * The throwable or null if this object is deserialized from XML or JSON.
434     *
435     * @return The throwable or null if this object is deserialized from XML or JSON.
436     */
437    public Throwable getThrowable() {
438        return this.throwable;
439    }
440
441    @Override
442    public int hashCode() {
443        final int prime = 31;
444        int result = 1;
445        result = prime * result + (this.causeProxy == null ? 0 : this.causeProxy.hashCode());
446        result = prime * result + this.commonElementCount;
447        result = prime * result + (this.extendedStackTrace == null ? 0 : Arrays.hashCode(this.extendedStackTrace));
448        result = prime * result + (this.suppressedProxies == null ? 0 : Arrays.hashCode(this.suppressedProxies));
449        result = prime * result + (this.name == null ? 0 : this.name.hashCode());
450        return result;
451    }
452
453    @Override
454    public String toString() {
455        final String msg = this.message;
456        return msg != null ? this.name + ": " + msg : this.name;
457    }
458}