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