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