001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017 package org.apache.logging.log4j.core.impl; 018 019 import java.io.Serializable; 020 import java.net.URL; 021 import java.security.CodeSource; 022 import java.util.Arrays; 023 import java.util.HashMap; 024 import java.util.List; 025 import java.util.Map; 026 import java.util.Stack; 027 028 import org.apache.logging.log4j.Logger; 029 import org.apache.logging.log4j.core.util.Loader; 030 import org.apache.logging.log4j.core.util.Throwables; 031 import org.apache.logging.log4j.status.StatusLogger; 032 import org.apache.logging.log4j.util.Strings; 033 034 /** 035 * Wraps a Throwable to add packaging information about each stack trace element. 036 * 037 * <p> 038 * A proxy is used to represent a throwable that may not exist in a different class loader or JVM. When an application 039 * deserializes a ThrowableProxy, the throwable may not be set, but the throwable's information is preserved in other 040 * fields of the proxy like the message and stack trace. 041 * </p> 042 * 043 * TODO: Move this class to org.apache.logging.log4j.core because it is used from LogEvent. TODO: Deserialize: Try to 044 * rebuild Throwable if the target exception is in this class loader? 045 */ 046 public class ThrowableProxy implements Serializable { 047 048 /** 049 * Cached StackTracePackageElement and ClassLoader. 050 * <p> 051 * Consider this class private. 052 * </p> 053 */ 054 static class CacheEntry { 055 private final ExtendedClassInfo element; 056 private final ClassLoader loader; 057 058 public CacheEntry(final ExtendedClassInfo element, final ClassLoader loader) { 059 this.element = element; 060 this.loader = loader; 061 } 062 } 063 064 /** 065 * Security Manager for accessing the call stack. 066 */ 067 private static class PrivateSecurityManager extends SecurityManager { 068 public Class<?>[] getClasses() { 069 return this.getClassContext(); 070 } 071 } 072 073 private static final ThrowableProxy[] EMPTY_THROWABLE_PROXY_ARRAY = new ThrowableProxy[0]; 074 075 private static final char EOL = '\n'; 076 077 private static final Logger LOGGER = StatusLogger.getLogger(); 078 079 private static final PrivateSecurityManager SECURITY_MANAGER; 080 081 private static final long serialVersionUID = -2752771578252251910L; 082 083 static { 084 if (ReflectiveCallerClassUtility.isSupported()) { 085 SECURITY_MANAGER = null; 086 } else { 087 PrivateSecurityManager securityManager; 088 try { 089 securityManager = new PrivateSecurityManager(); 090 if (securityManager.getClasses() == null) { 091 // This shouldn't happen. 092 securityManager = null; 093 LOGGER.error("Unable to obtain call stack from security manager."); 094 } 095 } catch (final Exception e) { 096 securityManager = null; 097 LOGGER.debug("Unable to install security manager.", e); 098 } 099 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 * For JSON and XML IO via Jackson. 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 * Constructs the wrapper for the Throwable that includes packaging data. 135 * 136 * @param throwable 137 * The Throwable to wrap, must not be null. 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 * Constructs the wrapper for a Throwable that is referenced as the cause by another Throwable. 154 * 155 * @param parent 156 * The Throwable referencing this Throwable. 157 * @param stack 158 * The Class stack. 159 * @param map 160 * The cache containing the packaging data. 161 * @param cause 162 * The Throwable to wrap. 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 * Formats the specified Throwable. 268 * 269 * @param sb 270 * StringBuilder to contain the formatted Throwable. 271 * @param cause 272 * The Throwable to format. 273 */ 274 public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause) { 275 this.formatWrapper(sb, cause, null); 276 } 277 278 /** 279 * Formats the specified Throwable. 280 * 281 * @param sb 282 * StringBuilder to contain the formatted Throwable. 283 * @param cause 284 * The Throwable to format. 285 * @param packages 286 * The List of packages to be suppressed from the trace. 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 * Format the Throwable that is the cause of this Throwable. 306 * 307 * @return The formatted Throwable that caused this Throwable. 308 */ 309 public String getCauseStackTraceAsString() { 310 return this.getCauseStackTraceAsString(null); 311 } 312 313 /** 314 * Format the Throwable that is the cause of this Throwable. 315 * 316 * @param packages 317 * The List of packages to be suppressed from the trace. 318 * @return The formatted Throwable that caused this Throwable. 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 * Return the number of elements that are being omitted because they are common with the parent Throwable's stack 334 * trace. 335 * 336 * @return The number of elements omitted from the stack trace. 337 */ 338 public int getCommonElementCount() { 339 return this.commonElementCount; 340 } 341 342 /** 343 * Initialize the cache by resolving everything in the current stack trace via Reflection.getCallerClass or via the 344 * SecurityManager if either are available. These are the only Classes that can be trusted to be accurate. 345 * 346 * @return A Stack containing the current stack of Class objects. 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 * Gets the stack trace including packaging information. 371 * 372 * @return The stack trace including packaging information. 373 */ 374 public ExtendedStackTraceElement[] getExtendedStackTrace() { 375 return this.extendedStackTrace; 376 } 377 378 /** 379 * Format the stack trace including packaging information. 380 * 381 * @return The formatted stack trace including packaging information. 382 */ 383 public String getExtendedStackTraceAsString() { 384 return this.getExtendedStackTraceAsString(null); 385 } 386 387 /** 388 * Format the stack trace including packaging information. 389 * 390 * @param ignorePackages 391 * List of packages to be ignored in the trace. 392 * @return The formatted stack trace including packaging information. 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 * Return the FQCN of the Throwable. 418 * 419 * @return The FQCN of the Throwable. 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 * Gets proxies for suppressed exceptions. 431 * 432 * @return proxies for suppressed exceptions. 433 */ 434 public ThrowableProxy[] getSuppressedProxies() { 435 return this.suppressedProxies; 436 } 437 438 /** 439 * Format the suppressed Throwables. 440 * 441 * @return The formatted suppressed Throwables. 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 * The throwable or null if this object is deserialized from XML or JSON. 457 * 458 * @return The throwable or null if this object is deserialized from XML or JSON. 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 * Loads classes not located via Reflection.getCallerClass. 488 * 489 * @param lastLoader 490 * The ClassLoader that loaded the Class that called this Class. 491 * @param className 492 * The name of the Class. 493 * @return The Class object for the Class or null if it could not be located. 494 */ 495 private Class<?> loadClass(final ClassLoader lastLoader, final String className) { 496 // XXX: this is overly complicated 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 // Ignore exception. 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 * Construct the CacheEntry from the Class's information. 522 * 523 * @param stackTraceElement 524 * The stack trace element 525 * @param callerClass 526 * The Class. 527 * @param exact 528 * True if the class was obtained via Reflection.getCallerClass. 529 * 530 * @return The CacheEntry. 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 // Ignore the exception. 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 * Resolve all the stack entries in this stack trace that are not common with the parent. 570 * 571 * @param stack 572 * The callers Class stack. 573 * @param map 574 * The cache of CacheEntry objects. 575 * @param rootTrace 576 * The first stack trace resolve or null. 577 * @param stackTrace 578 * The stack trace being resolved. 579 * @return The StackTracePackageElement array. 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 // The stack returned from getCurrentStack may be missing entries for java.lang.reflect.Method.invoke() 604 // and its implementation. The Throwable might also contain stack entries that are no longer 605 // present as those methods have returned. 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 }