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 java.io.Serializable;
20 import java.net.URL;
21 import java.security.CodeSource;
22 import java.util.Arrays;
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.core.util.Loader;
30 import org.apache.logging.log4j.core.util.Throwables;
31 import org.apache.logging.log4j.status.StatusLogger;
32 import org.apache.logging.log4j.util.Strings;
33
34
35
36
37
38
39
40
41
42
43
44
45
46 public class ThrowableProxy implements Serializable {
47
48
49
50
51
52
53
54 static class CacheEntry {
55 private final ExtendedClassInfo element;
56 private final ClassLoader loader;
57
58 public CacheEntry(final ExtendedClassInfo element, final ClassLoader loader) {
59 this.element = element;
60 this.loader = loader;
61 }
62 }
63
64
65
66
67 private static class PrivateSecurityManager extends SecurityManager {
68 public Class<?>[] getClasses() {
69 return this.getClassContext();
70 }
71 }
72
73 private static final ThrowableProxy[] EMPTY_THROWABLE_PROXY_ARRAY = new ThrowableProxy[0];
74
75 private static final char EOL = '\n';
76
77 private static final Logger LOGGER = StatusLogger.getLogger();
78
79 private static final PrivateSecurityManager SECURITY_MANAGER;
80
81 private static final long serialVersionUID = -2752771578252251910L;
82
83 static {
84 if (ReflectiveCallerClassUtility.isSupported()) {
85 SECURITY_MANAGER = null;
86 } else {
87 PrivateSecurityManager securityManager;
88 try {
89 securityManager = new PrivateSecurityManager();
90 if (securityManager.getClasses() == null) {
91
92 securityManager = null;
93 LOGGER.error("Unable to obtain call stack from security manager.");
94 }
95 } catch (final Exception e) {
96 securityManager = null;
97 LOGGER.debug("Unable to install security manager.", e);
98 }
99 SECURITY_MANAGER = securityManager;
100 }
101 }
102
103 private final ThrowableProxy causeProxy;
104
105 private int commonElementCount;
106
107 private final ExtendedStackTraceElement[] extendedStackTrace;
108
109 private final String localizedMessage;
110
111 private final String message;
112
113 private final String name;
114
115 private final ThrowableProxy[] suppressedProxies;
116
117 private final transient Throwable throwable;
118
119
120
121
122 @SuppressWarnings("unused")
123 private ThrowableProxy() {
124 this.throwable = null;
125 this.name = null;
126 this.extendedStackTrace = null;
127 this.causeProxy = null;
128 this.message = null;
129 this.localizedMessage = null;
130 this.suppressedProxies = EMPTY_THROWABLE_PROXY_ARRAY;
131 }
132
133
134
135
136
137
138
139 public ThrowableProxy(final Throwable throwable) {
140 this.throwable = throwable;
141 this.name = throwable.getClass().getName();
142 this.message = throwable.getMessage();
143 this.localizedMessage = throwable.getLocalizedMessage();
144 final Map<String, CacheEntry> map = new HashMap<String, CacheEntry>();
145 final Stack<Class<?>> stack = this.getCurrentStack();
146 this.extendedStackTrace = this.toExtendedStackTrace(stack, map, null, throwable.getStackTrace());
147 final Throwable throwableCause = throwable.getCause();
148 this.causeProxy = throwableCause == null ? null : new ThrowableProxy(throwable, stack, map, throwableCause);
149 this.suppressedProxies = this.toSuppressedProxies(throwable);
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163
164 private ThrowableProxy(final Throwable parent, final Stack<Class<?>> stack, final Map<String, CacheEntry> map,
165 final Throwable cause) {
166 this.throwable = cause;
167 this.name = cause.getClass().getName();
168 this.message = this.throwable.getMessage();
169 this.localizedMessage = this.throwable.getLocalizedMessage();
170 this.extendedStackTrace = this.toExtendedStackTrace(stack, map, parent.getStackTrace(), cause.getStackTrace());
171 this.causeProxy = cause.getCause() == null ? null : new ThrowableProxy(parent, stack, map, cause.getCause());
172 this.suppressedProxies = this.toSuppressedProxies(cause);
173 }
174
175 @Override
176 public boolean equals(final Object obj) {
177 if (this == obj) {
178 return true;
179 }
180 if (obj == null) {
181 return false;
182 }
183 if (this.getClass() != obj.getClass()) {
184 return false;
185 }
186 final ThrowableProxy other = (ThrowableProxy) obj;
187 if (this.causeProxy == null) {
188 if (other.causeProxy != null) {
189 return false;
190 }
191 } else if (!this.causeProxy.equals(other.causeProxy)) {
192 return false;
193 }
194 if (this.commonElementCount != other.commonElementCount) {
195 return false;
196 }
197 if (this.name == null) {
198 if (other.name != null) {
199 return false;
200 }
201 } else if (!this.name.equals(other.name)) {
202 return false;
203 }
204 if (!Arrays.equals(this.extendedStackTrace, other.extendedStackTrace)) {
205 return false;
206 }
207 if (!Arrays.equals(this.suppressedProxies, other.suppressedProxies)) {
208 return false;
209 }
210 return true;
211 }
212
213 @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
214 private void formatCause(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages) {
215 sb.append("Caused by: ").append(cause).append(EOL);
216 this.formatElements(sb, cause.commonElementCount, cause.getThrowable().getStackTrace(),
217 cause.extendedStackTrace, ignorePackages);
218 if (cause.getCauseProxy() != null) {
219 this.formatCause(sb, cause.causeProxy, ignorePackages);
220 }
221 }
222
223 private void formatElements(final StringBuilder sb, final int commonCount, final StackTraceElement[] causedTrace,
224 final ExtendedStackTraceElement[] extStackTrace, final List<String> ignorePackages) {
225 if (ignorePackages == null || ignorePackages.isEmpty()) {
226 for (int i = 0; i < extStackTrace.length; ++i) {
227 this.formatEntry(causedTrace[i], extStackTrace[i], sb);
228 }
229 } else {
230 int count = 0;
231 for (int i = 0; i < extStackTrace.length; ++i) {
232 if (!this.ignoreElement(causedTrace[i], ignorePackages)) {
233 if (count > 0) {
234 if (count == 1) {
235 sb.append("\t....\n");
236 } else {
237 sb.append("\t... suppressed ").append(count).append(" lines\n");
238 }
239 count = 0;
240 }
241 this.formatEntry(causedTrace[i], extStackTrace[i], sb);
242 } else {
243 ++count;
244 }
245 }
246 if (count > 0) {
247 if (count == 1) {
248 sb.append("\t...\n");
249 } else {
250 sb.append("\t... suppressed ").append(count).append(" lines\n");
251 }
252 }
253 }
254 if (commonCount != 0) {
255 sb.append("\t... ").append(commonCount).append(" more").append('\n');
256 }
257 }
258
259 private void formatEntry(final StackTraceElement element, final ExtendedStackTraceElement extStackTraceElement,
260 final StringBuilder sb) {
261 sb.append("\tat ");
262 sb.append(extStackTraceElement);
263 sb.append('\n');
264 }
265
266
267
268
269
270
271
272
273
274 public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause) {
275 this.formatWrapper(sb, cause, null);
276 }
277
278
279
280
281
282
283
284
285
286
287
288 @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
289 public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> packages) {
290 final Throwable caused = cause.getCauseProxy() != null ? cause.getCauseProxy().getThrowable() : null;
291 if (caused != null) {
292 this.formatWrapper(sb, cause.causeProxy);
293 sb.append("Wrapped by: ");
294 }
295 sb.append(cause).append('\n');
296 this.formatElements(sb, cause.commonElementCount, cause.getThrowable().getStackTrace(),
297 cause.extendedStackTrace, packages);
298 }
299
300 public ThrowableProxy getCauseProxy() {
301 return this.causeProxy;
302 }
303
304
305
306
307
308
309 public String getCauseStackTraceAsString() {
310 return this.getCauseStackTraceAsString(null);
311 }
312
313
314
315
316
317
318
319
320 public String getCauseStackTraceAsString(final List<String> packages) {
321 final StringBuilder sb = new StringBuilder();
322 if (this.causeProxy != null) {
323 this.formatWrapper(sb, this.causeProxy);
324 sb.append("Wrapped by: ");
325 }
326 sb.append(this.toString());
327 sb.append('\n');
328 this.formatElements(sb, 0, this.throwable.getStackTrace(), this.extendedStackTrace, packages);
329 return sb.toString();
330 }
331
332
333
334
335
336
337
338 public int getCommonElementCount() {
339 return this.commonElementCount;
340 }
341
342
343
344
345
346
347
348 private Stack<Class<?>> getCurrentStack() {
349 if (ReflectiveCallerClassUtility.isSupported()) {
350 final Stack<Class<?>> classes = new Stack<Class<?>>();
351 int index = 1;
352 Class<?> clazz = ReflectiveCallerClassUtility.getCaller(index);
353 while (clazz != null) {
354 classes.push(clazz);
355 clazz = ReflectiveCallerClassUtility.getCaller(++index);
356 }
357 return classes;
358 } else if (SECURITY_MANAGER != null) {
359 final Class<?>[] array = SECURITY_MANAGER.getClasses();
360 final Stack<Class<?>> classes = new Stack<Class<?>>();
361 for (final Class<?> clazz : array) {
362 classes.push(clazz);
363 }
364 return classes;
365 }
366 return new Stack<Class<?>>();
367 }
368
369
370
371
372
373
374 public ExtendedStackTraceElement[] getExtendedStackTrace() {
375 return this.extendedStackTrace;
376 }
377
378
379
380
381
382
383 public String getExtendedStackTraceAsString() {
384 return this.getExtendedStackTraceAsString(null);
385 }
386
387
388
389
390
391
392
393
394 public String getExtendedStackTraceAsString(final List<String> ignorePackages) {
395 final StringBuilder sb = new StringBuilder(this.name);
396 final String msg = this.message;
397 if (msg != null) {
398 sb.append(": ").append(msg);
399 }
400 sb.append('\n');
401 this.formatElements(sb, 0, this.throwable.getStackTrace(), this.extendedStackTrace, ignorePackages);
402 if (this.causeProxy != null) {
403 this.formatCause(sb, this.causeProxy, ignorePackages);
404 }
405 return sb.toString();
406 }
407
408 public String getLocalizedMessage() {
409 return this.localizedMessage;
410 }
411
412 public String getMessage() {
413 return this.message;
414 }
415
416
417
418
419
420
421 public String getName() {
422 return this.name;
423 }
424
425 public StackTraceElement[] getStackTrace() {
426 return this.throwable == null ? null : this.throwable.getStackTrace();
427 }
428
429
430
431
432
433
434 public ThrowableProxy[] getSuppressedProxies() {
435 return this.suppressedProxies;
436 }
437
438
439
440
441
442
443 public String getSuppressedStackTrace() {
444 final ThrowableProxy[] suppressed = this.getSuppressedProxies();
445 if (suppressed == null || suppressed.length == 0) {
446 return Strings.EMPTY;
447 }
448 final StringBuilder sb = new StringBuilder("Suppressed Stack Trace Elements:\n");
449 for (final ThrowableProxy proxy : suppressed) {
450 sb.append(proxy.getExtendedStackTraceAsString());
451 }
452 return sb.toString();
453 }
454
455
456
457
458
459
460 public Throwable getThrowable() {
461 return this.throwable;
462 }
463
464 @Override
465 public int hashCode() {
466 final int prime = 31;
467 int result = 1;
468 result = prime * result + (this.causeProxy == null ? 0 : this.causeProxy.hashCode());
469 result = prime * result + this.commonElementCount;
470 result = prime * result + (this.extendedStackTrace == null ? 0 : Arrays.hashCode(this.extendedStackTrace));
471 result = prime * result + (this.suppressedProxies == null ? 0 : Arrays.hashCode(this.suppressedProxies));
472 result = prime * result + (this.name == null ? 0 : this.name.hashCode());
473 return result;
474 }
475
476 private boolean ignoreElement(final StackTraceElement element, final List<String> ignorePackages) {
477 final String className = element.getClassName();
478 for (final String pkg : ignorePackages) {
479 if (className.startsWith(pkg)) {
480 return true;
481 }
482 }
483 return false;
484 }
485
486
487
488
489
490
491
492
493
494
495 private Class<?> loadClass(final ClassLoader lastLoader, final String className) {
496
497 Class<?> clazz;
498 if (lastLoader != null) {
499 try {
500 clazz = Loader.initializeClass(className, lastLoader);
501 if (clazz != null) {
502 return clazz;
503 }
504 } catch (final Exception ignore) {
505
506 }
507 }
508 try {
509 clazz = Loader.loadClass(className);
510 } catch (final ClassNotFoundException ignored) {
511 try {
512 clazz = Loader.initializeClass(className, this.getClass().getClassLoader());
513 } catch (final ClassNotFoundException ignore) {
514 return null;
515 }
516 }
517 return clazz;
518 }
519
520
521
522
523
524
525
526
527
528
529
530
531
532 private CacheEntry toCacheEntry(final StackTraceElement stackTraceElement, final Class<?> callerClass,
533 final boolean exact) {
534 String location = "?";
535 String version = "?";
536 ClassLoader lastLoader = null;
537 if (callerClass != null) {
538 try {
539 final CodeSource source = callerClass.getProtectionDomain().getCodeSource();
540 if (source != null) {
541 final URL locationURL = source.getLocation();
542 if (locationURL != null) {
543 final String str = locationURL.toString().replace('\\', '/');
544 int index = str.lastIndexOf("/");
545 if (index >= 0 && index == str.length() - 1) {
546 index = str.lastIndexOf("/", index - 1);
547 location = str.substring(index + 1);
548 } else {
549 location = str.substring(index + 1);
550 }
551 }
552 }
553 } catch (final Exception ex) {
554
555 }
556 final Package pkg = callerClass.getPackage();
557 if (pkg != null) {
558 final String ver = pkg.getImplementationVersion();
559 if (ver != null) {
560 version = ver;
561 }
562 }
563 lastLoader = callerClass.getClassLoader();
564 }
565 return new CacheEntry(new ExtendedClassInfo(exact, location, version), lastLoader);
566 }
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581 ExtendedStackTraceElement[] toExtendedStackTrace(final Stack<Class<?>> stack, final Map<String, CacheEntry> map,
582 final StackTraceElement[] rootTrace, final StackTraceElement[] stackTrace) {
583 int stackLength;
584 if (rootTrace != null) {
585 int rootIndex = rootTrace.length - 1;
586 int stackIndex = stackTrace.length - 1;
587 while (rootIndex >= 0 && stackIndex >= 0 && rootTrace[rootIndex].equals(stackTrace[stackIndex])) {
588 --rootIndex;
589 --stackIndex;
590 }
591 this.commonElementCount = stackTrace.length - 1 - stackIndex;
592 stackLength = stackIndex + 1;
593 } else {
594 this.commonElementCount = 0;
595 stackLength = stackTrace.length;
596 }
597 final ExtendedStackTraceElement[] extStackTrace = new ExtendedStackTraceElement[stackLength];
598 Class<?> clazz = stack.isEmpty() ? null : stack.peek();
599 ClassLoader lastLoader = null;
600 for (int i = stackLength - 1; i >= 0; --i) {
601 final StackTraceElement stackTraceElement = stackTrace[i];
602 final String className = stackTraceElement.getClassName();
603
604
605
606 ExtendedClassInfo extClassInfo;
607 if (clazz != null && className.equals(clazz.getName())) {
608 final CacheEntry entry = this.toCacheEntry(stackTraceElement, clazz, true);
609 extClassInfo = entry.element;
610 lastLoader = entry.loader;
611 stack.pop();
612 clazz = stack.isEmpty() ? null : stack.peek();
613 } else {
614 if (map.containsKey(className)) {
615 final CacheEntry entry = map.get(className);
616 extClassInfo = entry.element;
617 if (entry.loader != null) {
618 lastLoader = entry.loader;
619 }
620 } else {
621 final CacheEntry entry = this.toCacheEntry(stackTraceElement,
622 this.loadClass(lastLoader, className), false);
623 extClassInfo = entry.element;
624 map.put(stackTraceElement.toString(), entry);
625 if (entry.loader != null) {
626 lastLoader = entry.loader;
627 }
628 }
629 }
630 extStackTrace[i] = new ExtendedStackTraceElement(stackTraceElement, extClassInfo);
631 }
632 return extStackTrace;
633 }
634
635 @Override
636 public String toString() {
637 final String msg = this.message;
638 return msg != null ? this.name + ": " + msg : this.name;
639 }
640
641 private ThrowableProxy[] toSuppressedProxies(final Throwable thrown) {
642 try {
643 final Throwable[] suppressed = Throwables.getSuppressed(thrown);
644 if (suppressed == null) {
645 return EMPTY_THROWABLE_PROXY_ARRAY;
646 }
647 final ThrowableProxy[] proxies = new ThrowableProxy[suppressed.length];
648 for (int i = 0; i < suppressed.length; i++) {
649 proxies[i] = new ThrowableProxy(suppressed[i]);
650 }
651 return proxies;
652 } catch (final Exception e) {
653 StatusLogger.getLogger().error(e);
654 }
655 return null;
656 }
657 }