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.lang.reflect.Method;
21  import java.net.URL;
22  import java.security.CodeSource;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Stack;
27  
28  import org.apache.logging.log4j.Logger;
29  import org.apache.logging.log4j.status.StatusLogger;
30  
31  /**
32   * Wraps a Throwable to add packaging information about each stack trace element.
33   */
34  public class ThrowableProxy implements Serializable {
35  
36      private static final long serialVersionUID = -2752771578252251910L;
37  
38      private static final Logger LOGGER = StatusLogger.getLogger();
39  
40      private static final PrivateSecurityManager SECURITY_MANAGER;
41  
42      private static final Method GET_SUPPRESSED;
43  
44      private static final Method ADD_SUPPRESSED;
45  
46      private final ThrowableProxy proxyCause;
47  
48      private final Throwable throwable;
49  
50      private final String name;
51  
52      private final StackTracePackageElement[] callerPackageData;
53  
54      private int commonElementCount;
55  
56      static {
57          if (ReflectiveCallerClassUtility.isSupported()) {
58              SECURITY_MANAGER = null;
59          } else {
60              PrivateSecurityManager securityManager;
61              try {
62                  securityManager = new PrivateSecurityManager();
63                  if (securityManager.getClasses() == null) {
64                      // This shouldn't happen.
65                      securityManager = null;
66                      LOGGER.error("Unable to obtain call stack from security manager.");
67                  }
68              } catch (final Exception e) {
69                  securityManager = null;
70                  LOGGER.debug("Unable to install security manager.", e);
71              }
72              SECURITY_MANAGER = securityManager;
73          }
74  
75          Method getSuppressed = null, addSuppressed = null;
76          final Method[] methods = Throwable.class.getMethods();
77          for (final Method method : methods) {
78              if (method.getName().equals("getSuppressed")) {
79                  getSuppressed = method;
80              } else if (method.getName().equals("addSuppressed")) {
81                  addSuppressed = method;
82              }
83          }
84          GET_SUPPRESSED = getSuppressed;
85          ADD_SUPPRESSED = addSuppressed;
86      }
87  
88      /**
89       * Construct the wrapper for the Throwable that includes packaging data.
90       * @param throwable The Throwable to wrap.
91       */
92      public ThrowableProxy(final Throwable throwable) {
93          this.throwable = throwable;
94          this.name = throwable.getClass().getName();
95          final Map<String, CacheEntry> map = new HashMap<String, CacheEntry>();
96          final Stack<Class<?>> stack = getCurrentStack();
97          callerPackageData = resolvePackageData(stack, map, null, throwable.getStackTrace());
98          this.proxyCause = throwable.getCause() == null ? null :
99              new ThrowableProxy(throwable, stack, map, throwable.getCause());
100         setSuppressed(throwable);
101     }
102 
103     /**
104      * Constructs the wrapper for a Throwable that is referenced as the cause by another
105      * Throwable.
106      * @param parent The Throwable referencing this Throwable.
107      * @param stack The Class stack.
108      * @param map The cache containing the packaging data.
109      * @param cause The Throwable to wrap.
110      */
111     private ThrowableProxy(final Throwable parent, final Stack<Class<?>> stack, final Map<String, CacheEntry> map,
112                            final Throwable cause) {
113         this.throwable = cause;
114         this.name = cause.getClass().getName();
115         callerPackageData = resolvePackageData(stack, map, parent.getStackTrace(), cause.getStackTrace());
116         this.proxyCause = cause.getCause() == null ? null :
117             new ThrowableProxy(parent, stack, map, cause.getCause());
118         setSuppressed(cause);
119     }
120 
121     public Throwable getThrowable() {
122         return throwable;
123     }
124 
125     public ThrowableProxy getCause() {
126         return proxyCause;
127     }
128 
129     /**
130      * Return the FQCN of the Throwable.
131      * @return The FQCN of the Throwable.
132      */
133     public String getName() {
134         return name;
135     }
136 
137     /**
138      * Return the number of elements that are being ommitted because they are common with the parent Throwable's
139      * stack trace.
140      * @return The number of elements ommitted from the stack trace.
141      */
142     public int getCommonElementCount() {
143         return commonElementCount;
144     }
145 
146     /**
147      * Return the package data associated with the stack trace.
148      * @return The package data associated with the stack trace.
149      */
150     public StackTracePackageElement[] getPackageData() {
151         return callerPackageData;
152     }
153 
154     @Override
155     public String toString() {
156         final String msg = throwable.getMessage();
157         return msg != null ? name + ": " + msg : name;
158     }
159 
160     /**
161      * Format the Throwable that is the cause of this Throwable.
162      * @return The formatted Throwable that caused this Throwable.
163      */
164     public String getRootCauseStackTrace() {
165         return getRootCauseStackTrace(null);
166     }
167 
168     /**
169      * Format the Throwable that is the cause of this Throwable.
170      * @param packages The List of packages to be suppressed from the trace.
171      * @return The formatted Throwable that caused this Throwable.
172      */
173     public String getRootCauseStackTrace(final List<String> packages) {
174         final StringBuilder sb = new StringBuilder();
175         if (proxyCause != null) {
176             formatWrapper(sb, proxyCause);
177             sb.append("Wrapped by: ");
178         }
179         sb.append(toString());
180         sb.append("\n");
181         formatElements(sb, 0, throwable.getStackTrace(), callerPackageData, packages);
182         return sb.toString();
183     }
184 
185     /**
186      * Formats the specified Throwable.
187      * @param sb StringBuilder to contain the formatted Throwable.
188      * @param cause The Throwable to format.
189      */
190     public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause) {
191         formatWrapper(sb, cause, null);
192     }
193 
194     /**
195      * Formats the specified Throwable.
196      * @param sb StringBuilder to contain the formatted Throwable.
197      * @param cause The Throwable to format.
198      * @param packages The List of packages to be suppressed from the trace.
199      */
200     @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
201     public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> packages) {
202         final Throwable caused = cause.getCause() != null ? cause.getCause().getThrowable() : null;
203         if (caused != null) {
204             formatWrapper(sb, cause.proxyCause);
205             sb.append("Wrapped by: ");
206         }
207         sb.append(cause).append("\n");
208         formatElements(sb, cause.commonElementCount, cause.getThrowable().getStackTrace(), cause.callerPackageData,
209             packages);
210     }
211 
212     /**
213      * Format the stack trace including packaging information.
214      * @return The formatted stack trace including packaging information.
215      */
216     public String getExtendedStackTrace() {
217         return getExtendedStackTrace(null);
218     }
219 
220     /**
221      * Format the stack trace including packaging information.
222      * @param packages List of packages to be suppressed from the trace.
223      * @return The formatted stack trace including packaging information.
224      */
225     public String getExtendedStackTrace(final List<String> packages) {
226         final StringBuilder sb = new StringBuilder(name);
227         final String msg = throwable.getMessage();
228         if (msg != null) {
229             sb.append(": ").append(throwable.getMessage());
230         }
231         sb.append("\n");
232         formatElements(sb, 0, throwable.getStackTrace(), callerPackageData, packages);
233         if (proxyCause != null) {
234             formatCause(sb, proxyCause, packages);
235         }
236         return sb.toString();
237     }
238 
239     /**
240      * Format the suppressed Throwables.
241      * @return The formatted suppressed Throwables.
242      */
243     public String getSuppressedStackTrace() {
244         final ThrowableProxy[] suppressed = getSuppressed();
245         if (suppressed == null || suppressed.length == 0) {
246             return "";
247         }
248         final StringBuilder sb = new StringBuilder("Suppressed Stack Trace Elements:\n");
249         for (final ThrowableProxy proxy : suppressed) {
250             sb.append(proxy.getExtendedStackTrace());
251         }
252         return sb.toString();
253     }
254 
255     @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
256     private void formatCause(final StringBuilder sb, final ThrowableProxy cause, final List<String> packages) {
257         sb.append("Caused by: ").append(cause).append("\n");
258         formatElements(sb, cause.commonElementCount, cause.getThrowable().getStackTrace(), cause.callerPackageData,
259             packages);
260         if (cause.getCause() != null) {
261             formatCause(sb, cause.proxyCause, packages);
262         }
263     }
264 
265     private void formatElements(final StringBuilder sb, final int commonCount, final StackTraceElement[] causedTrace,
266                                 final StackTracePackageElement[] packageData, final List<String> packages) {
267         if (packages == null || packages.size() == 0) {
268             for (int i = 0; i < packageData.length; ++i) {
269                 formatEntry(causedTrace[i], packageData[i], sb);
270             }
271         } else {
272             int count = 0;
273             for (int i = 0; i < packageData.length; ++i) {
274                 if (!isSuppressed(causedTrace[i], packages)) {
275                     if (count > 0) {
276                         if (count == 1) {
277                             sb.append("\t....\n");
278                         } else {
279                             sb.append("\t... suppressed ").append(count).append(" lines\n");
280                         }
281                         count = 0;
282                     }
283                     formatEntry(causedTrace[i], packageData[i], sb);
284                 } else {
285                     ++count;
286                 }
287             }
288             if (count > 0) {
289                 if (count == 1) {
290                     sb.append("\t...\n");
291                 } else {
292                     sb.append("\t... suppressed ").append(count).append(" lines\n");
293                 }
294             }
295         }
296         if (commonCount != 0) {
297             sb.append("\t... ").append(commonCount).append(" more").append("\n");
298         }
299     }
300 
301     private void formatEntry(final StackTraceElement element, final StackTracePackageElement packageData,
302                              final StringBuilder sb) {
303         sb.append("\tat ");
304         sb.append(element);
305         sb.append(" ");
306         sb.append(packageData);
307         sb.append("\n");
308     }
309 
310     private boolean isSuppressed(final StackTraceElement element, final List<String> packages) {
311         final String className = element.getClassName();
312         for (final String pkg : packages) {
313             if (className.startsWith(pkg)) {
314                 return true;
315             }
316         }
317         return false;
318     }
319 
320     /**
321      * Initialize the cache by resolving everything in the current stack trace via Reflection.getCallerClass
322      * or via the SecurityManager if either are available. These are the only Classes that can be trusted
323      * to be accurate.
324      * @return A Deque containing the current stack of Class objects.
325      */
326     private Stack<Class<?>> getCurrentStack() {
327         if (ReflectiveCallerClassUtility.isSupported()) {
328             final Stack<Class<?>> classes = new Stack<Class<?>>();
329             int index = 1;
330             Class<?> clazz = ReflectiveCallerClassUtility.getCaller(index);
331             while (clazz != null) {
332                 classes.push(clazz);
333                 clazz = ReflectiveCallerClassUtility.getCaller(++index);
334             }
335             return classes;
336         } else if (SECURITY_MANAGER != null) {
337             final Class<?>[] array = SECURITY_MANAGER.getClasses();
338             final Stack<Class<?>> classes = new Stack<Class<?>>();
339             for (final Class<?> clazz : array) {
340                 classes.push(clazz);
341             }
342             return classes;
343         }
344         return new Stack<Class<?>>();
345     }
346 
347     /**
348      * Resolve all the stack entries in this stack trace that are not common with the parent.
349      * @param stack The callers Class stack.
350      * @param map The cache of CacheEntry objects.
351      * @param rootTrace The first stack trace resolve or null.
352      * @param stackTrace The stack trace being resolved.
353      * @return The StackTracePackageElement array.
354      */
355     StackTracePackageElement[] resolvePackageData(final Stack<Class<?>> stack, final Map<String,
356                                                           CacheEntry> map,
357                                                           final StackTraceElement[] rootTrace,
358                                                           final StackTraceElement[] stackTrace) {
359         int stackLength;
360         if (rootTrace != null) {
361             int rootIndex = rootTrace.length - 1;
362             int stackIndex = stackTrace.length - 1;
363             while (rootIndex >= 0 && stackIndex >= 0 && rootTrace[rootIndex].equals(stackTrace[stackIndex])) {
364                 --rootIndex;
365                 --stackIndex;
366             }
367             commonElementCount = stackTrace.length - 1 - stackIndex;
368             stackLength = stackIndex + 1;
369         } else {
370             commonElementCount = 0;
371             stackLength = stackTrace.length;
372         }
373         final StackTracePackageElement[] packageArray = new StackTracePackageElement[stackLength];
374         Class<?> clazz = stack.isEmpty() ? null : stack.peek();
375         ClassLoader lastLoader = null;
376         for (int i = stackLength - 1; i >= 0; --i) {
377             final String className = stackTrace[i].getClassName();
378             // The stack returned from getCurrentStack will be missing entries for  java.lang.reflect.Method.invoke()
379             // and its implementation. The Throwable might also contain stack entries that are no longer
380             // present as those methods have returned.
381             if (clazz != null && className.equals(clazz.getName())) {
382                 final CacheEntry entry = resolvePackageElement(clazz, true);
383                 packageArray[i] = entry.element;
384                 lastLoader = entry.loader;
385                 stack.pop();
386                 clazz = stack.isEmpty() ? null : stack.peek();
387             } else {
388                 if (map.containsKey(className)) {
389                     final CacheEntry entry = map.get(className);
390                     packageArray[i] = entry.element;
391                     if (entry.loader != null) {
392                         lastLoader = entry.loader;
393                     }
394                 } else {
395                     final CacheEntry entry = resolvePackageElement(loadClass(lastLoader, className), false);
396                     packageArray[i] = entry.element;
397                     map.put(className, entry);
398                     if (entry.loader != null) {
399                         lastLoader = entry.loader;
400                     }
401                 }
402             }
403         }
404         return packageArray;
405     }
406 
407 
408     /**
409      * Construct the CacheEntry from the Class's information.
410      * @param callerClass The Class.
411      * @param exact True if the class was obtained via Reflection.getCallerClass.
412      * @return The CacheEntry.
413      */
414     private CacheEntry resolvePackageElement(final Class<?> callerClass, final boolean exact) {
415         String location = "?";
416         String version = "?";
417         ClassLoader lastLoader = null;
418         if (callerClass != null) {
419             try {
420                 final CodeSource source = callerClass.getProtectionDomain().getCodeSource();
421                 if (source != null) {
422                     final URL locationURL = source.getLocation();
423                     if (locationURL != null) {
424                         final String str = locationURL.toString().replace('\\', '/');
425                         int index = str.lastIndexOf("/");
426                         if (index >= 0 && index == str.length() - 1) {
427                             index = str.lastIndexOf("/", index - 1);
428                             location = str.substring(index + 1);
429                         } else {
430                             location = str.substring(index + 1);
431                         }
432                     }
433                 }
434             } catch (final Exception ex) {
435                 // Ignore the exception.
436             }
437             final Package pkg = callerClass.getPackage();
438             if (pkg != null) {
439                 final String ver = pkg.getImplementationVersion();
440                 if (ver != null) {
441                     version = ver;
442                 }
443             }
444             lastLoader = callerClass.getClassLoader();
445         }
446         return new CacheEntry(new StackTracePackageElement(location, version, exact), lastLoader);
447     }
448 
449     /**
450      * Loads classes not located via Reflection.getCallerClass.
451      * @param lastLoader The ClassLoader that loaded the Class that called this Class.
452      * @param className The name of the Class.
453      * @return The Class object for the Class or null if it could not be located.
454      */
455     private Class<?> loadClass(final ClassLoader lastLoader, final String className) {
456         Class<?> clazz;
457         if (lastLoader != null) {
458             try {
459                 clazz = lastLoader.loadClass(className);
460                 if (clazz != null) {
461                     return clazz;
462                 }
463             } catch (final Exception ex) {
464                 // Ignore exception.
465             }
466         }
467         try {
468             clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
469         } catch (final ClassNotFoundException e) {
470             try {
471                 clazz = Class.forName(className);
472             } catch (final ClassNotFoundException e1) {
473                 try {
474                     clazz = getClass().getClassLoader().loadClass(className);
475                 } catch (final ClassNotFoundException e2) {
476                     return null;
477                 }
478             }
479         }
480         return clazz;
481     }
482 
483     public ThrowableProxy[] getSuppressed() {
484         if (GET_SUPPRESSED != null) {
485             try {
486                 return (ThrowableProxy[]) GET_SUPPRESSED.invoke(throwable);
487             } catch (final Exception ignore) {
488                 return null;
489             }
490         }
491         return null;
492     }
493 
494     private void setSuppressed(final Throwable throwable) {
495         if (GET_SUPPRESSED != null && ADD_SUPPRESSED != null) {
496             try {
497                 final Throwable[] array = (Throwable[]) GET_SUPPRESSED.invoke(throwable);
498                 for (final Throwable t : array) {
499                     ADD_SUPPRESSED.invoke(this, new ThrowableProxy(t));
500                 }
501             } catch (final Exception ignore) {
502                 //
503             }
504         }
505     }
506 
507     /**
508      * Cached StackTracePackageElement and the ClassLoader.
509      */
510     class CacheEntry {
511         private final StackTracePackageElement element;
512         private final ClassLoader loader;
513 
514         public CacheEntry(final StackTracePackageElement element, final ClassLoader loader) {
515             this.element = element;
516             this.loader = loader;
517         }
518     }
519 
520     /**
521      * Security Manager for accessing the call stack.
522      */
523     private static class PrivateSecurityManager extends SecurityManager {
524         public Class<?>[] getClasses() {
525             return getClassContext();
526         }
527     }
528 }