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