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