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