View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.impl;
18  
19  import java.io.Serializable;
20  import java.util.Arrays;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  import java.util.Stack;
27  
28  import org.apache.logging.log4j.core.pattern.PlainTextRenderer;
29  import org.apache.logging.log4j.core.pattern.TextRenderer;
30  import org.apache.logging.log4j.util.StackLocatorUtil;
31  import org.apache.logging.log4j.util.Strings;
32  
33  /**
34   * Wraps a Throwable to add packaging information about each stack trace element.
35   *
36   * <p>
37   * A proxy is used to represent a throwable that may not exist in a different class loader or JVM. When an application
38   * deserializes a ThrowableProxy, the throwable may not be set, but the throwable's information is preserved in other
39   * fields of the proxy like the message and stack trace.
40   * </p>
41   *
42   * <p>
43   * TODO: Move this class to org.apache.logging.log4j.core because it is used from LogEvent.
44   * </p>
45   * <p>
46   * TODO: Deserialize: Try to rebuild Throwable if the target exception is in this class loader?
47   * </p>
48   */
49  public class ThrowableProxy implements Serializable {
50  
51      private static final char EOL = '\n';
52  
53      private static final String EOL_STR = String.valueOf(EOL);
54  
55      private static final long serialVersionUID = -2752771578252251910L;
56  
57      private final ThrowableProxy causeProxy;
58  
59      private int commonElementCount;
60  
61      private final ExtendedStackTraceElement[] extendedStackTrace;
62  
63      private final String localizedMessage;
64  
65      private final String message;
66  
67      private final String name;
68  
69      private final ThrowableProxy[] suppressedProxies;
70  
71      private final transient Throwable throwable;
72  
73      /**
74       * For JSON and XML IO via Jackson.
75       */
76      @SuppressWarnings("unused")
77      private ThrowableProxy() {
78          this.throwable = null;
79          this.name = null;
80          this.extendedStackTrace = null;
81          this.causeProxy = null;
82          this.message = null;
83          this.localizedMessage = null;
84          this.suppressedProxies = ThrowableProxyHelper.EMPTY_THROWABLE_PROXY_ARRAY;
85      }
86  
87      /**
88       * Constructs the wrapper for the Throwable that includes packaging data.
89       *
90       * @param throwable The Throwable to wrap, must not be null.
91       */
92      public ThrowableProxy(final Throwable throwable) {
93          this(throwable, null);
94      }
95  
96      /**
97       * Constructs the wrapper for the Throwable that includes packaging data.
98       *
99       * @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 }