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