1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
63
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
79
80
81
82
83
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
125
126
127
128
129
130
131
132
133 public String getRootCauseStackTrace() {
134 return getRootCauseStackTrace(null);
135 }
136
137
138
139
140
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
156
157
158
159 public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause) {
160 formatWrapper(sb, cause, null);
161 }
162
163
164
165
166
167
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
181
182
183 public String getExtendedStackTrace() {
184 return getExtendedStackTrace(null);
185 }
186
187
188
189
190
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
208
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
287
288
289
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
314
315
316
317
318
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
344
345
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
375
376
377
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
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
416
417
418
419
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
428 }
429 }
430 return null;
431 }
432
433
434
435
436
437
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
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
480
481 private static void setupCallerCheck() {
482 try {
483 final ClassLoader loader = Loader.getClassLoader();
484
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
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
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
550
551 private static class PrivateSecurityManager extends SecurityManager {
552 public Class<?>[] getClasses() {
553 return getClassContext();
554 }
555 }
556 }