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