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 org.apache.logging.log4j.Logger; 020 import org.apache.logging.log4j.core.helpers.Loader; 021 import org.apache.logging.log4j.status.StatusLogger; 022 023 import java.io.PrintStream; 024 import java.io.PrintWriter; 025 import java.lang.reflect.Method; 026 import java.lang.reflect.Modifier; 027 import java.net.URL; 028 import java.security.CodeSource; 029 import java.util.HashMap; 030 import java.util.List; 031 import java.util.Map; 032 import java.util.Stack; 033 034 /** 035 * Wraps a Throwable to add packaging information about each stack trace element. 036 */ 037 public class ThrowableProxy extends Throwable { 038 039 private static final long serialVersionUID = -2752771578252251910L; 040 041 private static Method getCallerClass; 042 043 private static PrivateSecurityManager securityManager; 044 045 private static final Logger LOGGER = StatusLogger.getLogger(); 046 047 private static Method getSuppressed; 048 private static Method addSuppressed; 049 050 private final Throwable throwable; 051 private final ThrowableProxy cause; 052 private int commonElementCount; 053 054 private final StackTracePackageElement[] callerPackageData; 055 056 057 static { 058 setupCallerCheck(); 059 versionCheck(); 060 } 061 062 /** 063 * Construct the wrapper for the Throwable that includes packaging data. 064 * @param throwable The Throwable to wrap. 065 */ 066 public ThrowableProxy(Throwable throwable) { 067 this.throwable = throwable; 068 Map<String, CacheEntry> map = new HashMap<String, CacheEntry>(); 069 Stack<Class<?>> stack = getCurrentStack(); 070 callerPackageData = resolvePackageData(stack, map, null, throwable.getStackTrace()); 071 this.cause = (throwable.getCause() == null) ? null : 072 new ThrowableProxy(throwable, stack, map, throwable.getCause()); 073 setSuppressed(throwable); 074 } 075 076 /** 077 * Constructs the wrapper for a Throwable that is referenced as the cause by another 078 * Throwable. 079 * @param parent The Throwable referencing this Throwable. 080 * @param stack The Class stack. 081 * @param map The cache containing the packaging data. 082 * @param cause The Throwable to wrap. 083 */ 084 private ThrowableProxy(Throwable parent, Stack<Class<?>> stack, Map<String, CacheEntry> map, Throwable cause) { 085 this.throwable = cause; 086 callerPackageData = resolvePackageData(stack, map, parent.getStackTrace(), cause.getStackTrace()); 087 this.cause = (throwable.getCause() == null) ? null : 088 new ThrowableProxy(parent, stack, map, throwable.getCause()); 089 setSuppressed(throwable); 090 } 091 092 093 @Override 094 public void setStackTrace(StackTraceElement[] stackTraceElements) { 095 throw new UnsupportedOperationException("Cannot set the stack trace on a ThrowableProxy"); 096 } 097 098 @Override 099 public String getMessage() { 100 return throwable.getMessage(); 101 } 102 103 @Override 104 public String getLocalizedMessage() { 105 return throwable.getLocalizedMessage(); 106 } 107 108 @Override 109 public Throwable getCause() { 110 return cause; 111 } 112 113 @Override 114 public Throwable initCause(Throwable throwable) { 115 throw new IllegalStateException("Cannot set the cause on a ThrowableProxy"); 116 } 117 118 @Override 119 public String toString() { 120 return throwable.toString(); 121 } 122 123 @Override 124 public void printStackTrace() { 125 throwable.printStackTrace(); 126 } 127 128 @Override 129 public void printStackTrace(PrintStream printStream) { 130 throwable.printStackTrace(printStream); 131 } 132 133 @Override 134 public void printStackTrace(PrintWriter printWriter) { 135 throwable.printStackTrace(printWriter); 136 } 137 138 @Override 139 public Throwable fillInStackTrace() { 140 return this; 141 } 142 143 @Override 144 public StackTraceElement[] getStackTrace() { 145 return throwable.getStackTrace(); 146 } 147 148 /** 149 * Format the Throwable that is the cause of this Throwable. 150 * @return The formatted Throwable that caused this Throwable. 151 */ 152 public String getRootCauseStackTrace() { 153 return getRootCauseStackTrace(null); 154 } 155 156 /** 157 * Format the Throwable that is the cause of this Throwable. 158 * @param packages The List of packages to be suppressed from the trace. 159 * @return The formatted Throwable that caused this Throwable. 160 */ 161 public String getRootCauseStackTrace(List<String> packages) { 162 StringBuilder sb = new StringBuilder(); 163 if (cause != null) { 164 formatWrapper(sb, cause); 165 sb.append("Wrapped by: "); 166 } 167 sb.append(throwable.toString()); 168 sb.append("\n"); 169 formatElements(sb, 0, throwable.getStackTrace(), callerPackageData, packages); 170 return sb.toString(); 171 } 172 173 /** 174 * Formats the specified Throwable. 175 * @param sb StringBuilder to contain the formatted Throwable. 176 * @param cause The Throwable to format. 177 */ 178 public void formatWrapper(StringBuilder sb, ThrowableProxy cause) { 179 formatWrapper(sb, cause, null); 180 } 181 182 /** 183 * Formats the specified Throwable. 184 * @param sb StringBuilder to contain the formatted Throwable. 185 * @param cause The Throwable to format. 186 * @param packages The List of packages to be suppressed from the trace. 187 */ 188 public void formatWrapper(StringBuilder sb, ThrowableProxy cause, List<String> packages) { 189 Throwable caused = cause.getCause(); 190 if (caused != null) { 191 formatWrapper(sb, cause.cause); 192 sb.append("Wrapped by: "); 193 } 194 sb.append(cause).append("\n"); 195 formatElements(sb, cause.commonElementCount, cause.getStackTrace(), cause.callerPackageData, packages); 196 } 197 198 /** 199 * Format the stack trace including packaging information. 200 * @return The formatted stack trace including packaging information. 201 */ 202 public String getExtendedStackTrace() { 203 return getExtendedStackTrace(null); 204 } 205 206 /** 207 * Format the stack trace including packaging information. 208 * @param packages List of packages to be suppressed from the trace. 209 * @return The formatted stack trace including packaging information. 210 */ 211 public String getExtendedStackTrace(List<String> packages) { 212 StringBuilder sb = new StringBuilder(throwable.toString()); 213 sb.append("\n"); 214 formatElements(sb, 0, throwable.getStackTrace(), callerPackageData, packages); 215 if (cause != null) { 216 formatCause(sb, cause, packages); 217 } 218 return sb.toString(); 219 } 220 221 /** 222 * Format the suppressed Throwables. 223 * @return The formatted suppressed Throwables. 224 */ 225 public String getSuppressedStackTrace() { 226 ThrowableProxy[] suppressed = getSuppressedProxies(); 227 if (suppressed == null || suppressed.length == 0) { 228 return ""; 229 } 230 StringBuilder sb = new StringBuilder("Suppressed Stack Trace Elements:\n"); 231 for (ThrowableProxy proxy : suppressed) { 232 sb.append(proxy.getExtendedStackTrace()); 233 } 234 return sb.toString(); 235 } 236 237 private void formatCause(StringBuilder sb, ThrowableProxy cause, List<String> packages) { 238 sb.append("Caused by: ").append(cause).append("\n"); 239 formatElements(sb, cause.commonElementCount, cause.getStackTrace(), cause.callerPackageData, packages); 240 if (cause.getCause() != null) { 241 formatCause(sb, cause.cause, packages); 242 } 243 } 244 245 private void formatElements(StringBuilder sb, int commonCount, StackTraceElement[] causedTrace, 246 StackTracePackageElement[] packageData, List<String> packages) { 247 if (packages == null || packages.size() == 0) { 248 for (int i = 0; i < packageData.length; ++i) { 249 formatEntry(causedTrace[i], packageData[i], sb); 250 } 251 } else { 252 int count = 0; 253 for (int i = 0; i < packageData.length; ++i) { 254 if (!isSuppressed(causedTrace[i], packages)) { 255 if (count > 0) { 256 if (count == 1) { 257 sb.append("\t....\n"); 258 } else { 259 sb.append("\t... suppressed ").append(count).append(" lines\n"); 260 } 261 count = 0; 262 } 263 formatEntry(causedTrace[i], packageData[i], sb); 264 } else { 265 ++count; 266 } 267 } 268 if (count > 0) { 269 if (count == 1) { 270 sb.append("\t...\n"); 271 } else { 272 sb.append("\t... suppressed ").append(count).append(" lines\n"); 273 } 274 } 275 } 276 if (commonCount != 0) { 277 sb.append("\t... ").append(commonCount).append(" more").append("\n"); 278 } 279 } 280 281 private void formatEntry(StackTraceElement element, StackTracePackageElement packageData, StringBuilder sb) { 282 sb.append("\tat "); 283 sb.append(element); 284 sb.append(" "); 285 sb.append(packageData); 286 sb.append("\n"); 287 } 288 289 private boolean isSuppressed(StackTraceElement element, List<String> packages) { 290 String className = element.getClassName(); 291 for (String pkg : packages) { 292 if (className.startsWith(pkg)) { 293 return true; 294 } 295 } 296 return false; 297 } 298 299 /** 300 * Initialize the cache by resolving everything in the current stack trace via Reflection.getCallerClass 301 * or via the SecurityManager if either are available. These are the only Classes that can be trusted 302 * to be accurate. 303 * @return A Deque containing the current stack of Class objects. 304 */ 305 private Stack<Class<?>> getCurrentStack() { 306 if (getCallerClass != null) { 307 Stack<Class<?>> classes = new Stack<Class<?>>(); 308 int index = 2; 309 Class<?> clazz = getCallerClass(index); 310 while (clazz != null) { 311 classes.push(clazz); 312 clazz = getCallerClass(++index); 313 } 314 return classes; 315 } else if (securityManager != null) { 316 Class<?>[] array = securityManager.getClasses(); 317 Stack<Class<?>> classes = new Stack<Class<?>>(); 318 for (Class<?> clazz : array) { 319 classes.push(clazz); 320 } 321 return classes; 322 } 323 return new Stack<Class<?>>(); 324 } 325 326 /** 327 * Resolve all the stack entries in this stack trace that are not common with the parent. 328 * @param stack The callers Class stack. 329 * @param map The cache of CacheEntry objects. 330 * @param rootTrace The first stack trace resolve or null. 331 * @param stackTrace The stack trace being resolved. 332 * @return The StackTracePackageElement array. 333 */ 334 private StackTracePackageElement[] resolvePackageData(Stack<Class<?>> stack, Map<String, CacheEntry> map, 335 StackTraceElement[] rootTrace, 336 StackTraceElement[] stackTrace) { 337 int stackLength; 338 if (rootTrace != null) { 339 int rootIndex = rootTrace.length - 1; 340 int stackIndex = stackTrace.length - 1; 341 while (rootIndex >= 0 && stackIndex >= 0 && rootTrace[rootIndex].equals(stackTrace[stackIndex])) { 342 --rootIndex; 343 --stackIndex; 344 } 345 commonElementCount = stackTrace.length - 1 - stackIndex; 346 stackLength = stackIndex + 1; 347 } else { 348 commonElementCount = 0; 349 stackLength = stackTrace.length; 350 } 351 StackTracePackageElement[] packageArray = new StackTracePackageElement[stackLength]; 352 Class<?> clazz = stack.peek(); 353 ClassLoader lastLoader = null; 354 for (int i = stackLength - 1; i >= 0; --i) { 355 String className = stackTrace[i].getClassName(); 356 // The stack returned from getCurrentStack will be missing entries for java.lang.reflect.Method.invoke() 357 // and its implementation. The Throwable might also contain stack entries that are no longer 358 // present as those methods have returned. 359 if (className.equals(clazz.getName())) { 360 CacheEntry entry = resolvePackageElement(clazz, true); 361 packageArray[i] = entry.element; 362 lastLoader = entry.loader; 363 stack.pop(); 364 clazz = stack.peek(); 365 } else { 366 if (map.containsKey(className)) { 367 CacheEntry entry = map.get(className); 368 packageArray[i] = entry.element; 369 if (entry.loader != null) { 370 lastLoader = entry.loader; 371 } 372 } else { 373 CacheEntry entry = resolvePackageElement(loadClass(lastLoader, className), false); 374 packageArray[i] = entry.element; 375 map.put(className, entry); 376 if (entry.loader != null) { 377 lastLoader = entry.loader; 378 } 379 } 380 } 381 } 382 return packageArray; 383 } 384 385 386 /** 387 * Construct the CacheEntry from the Class's information. 388 * @param callerClass The Class. 389 * @param exact True if the class was obtained via Reflection.getCallerClass. 390 * @return The CacheEntry. 391 */ 392 private CacheEntry resolvePackageElement(Class<?> callerClass, boolean exact) { 393 String location = "?"; 394 String version = "?"; 395 ClassLoader lastLoader = null; 396 if (callerClass != null) { 397 try { 398 CodeSource source = callerClass.getProtectionDomain().getCodeSource(); 399 if (source != null) { 400 URL locationURL = source.getLocation(); 401 if (locationURL != null) { 402 String str = locationURL.toString().replace('\\', '/'); 403 int index = str.lastIndexOf("/"); 404 if (index >= 0 && index == str.length() - 1) { 405 index = str.lastIndexOf("/", index - 1); 406 location = str.substring(index + 1); 407 } else { 408 location = str.substring(index + 1); 409 } 410 } 411 } 412 } catch (Exception ex) { 413 // Ignore the exception. 414 } 415 Package pkg = callerClass.getPackage(); 416 if (pkg != null) { 417 String ver = pkg.getImplementationVersion(); 418 if (ver != null) { 419 version = ver; 420 } 421 } 422 lastLoader = callerClass.getClassLoader(); 423 } 424 return new CacheEntry(new StackTracePackageElement(location, version, exact), lastLoader); 425 } 426 427 /** 428 * Invoke Reflection.getCallerClass via reflection. This is slightly slower than calling the method 429 * directly but removes any dependency on Sun's JDK being present at compile time. The difference 430 * can be measured by running the ReflectionComparison test. 431 * @param index The index into the stack trace. 432 * @return The Class at the specified stack entry. 433 */ 434 private Class<?> getCallerClass(int index) { 435 if (getCallerClass != null) { 436 try { 437 Object[] params = new Object[]{index}; 438 return (Class<?>) getCallerClass.invoke(null, params); 439 } catch (Exception ex) { 440 // logger.debug("Unable to determine caller class via Sun Reflection", ex); 441 } 442 } 443 return null; 444 } 445 446 /** 447 * Loads classes not located via Reflection.getCallerClass. 448 * @param lastLoader The ClassLoader that loaded the Class that called this Class. 449 * @param className The name of the Class. 450 * @return The Class object for the Class or null if it could not be located. 451 */ 452 private Class<?> loadClass(ClassLoader lastLoader, String className) { 453 Class<?> clazz; 454 if (lastLoader != null) { 455 try { 456 clazz = lastLoader.loadClass(className); 457 if (clazz != null) { 458 return clazz; 459 } 460 } catch (Exception ex) { 461 // Ignore exception. 462 } 463 } 464 try { 465 clazz = Thread.currentThread().getContextClassLoader().loadClass(className); 466 } catch (ClassNotFoundException e) { 467 try { 468 clazz = Class.forName(className); 469 } catch (ClassNotFoundException e1) { 470 try { 471 clazz = getClass().getClassLoader().loadClass(className); 472 } catch (ClassNotFoundException e2) { 473 return null; 474 } 475 } 476 } 477 return clazz; 478 } 479 480 private static void versionCheck() { 481 Method[] methods = Throwable.class.getMethods(); 482 for (Method method : methods) { 483 if (method.getName().equals("getSuppressed")) { 484 getSuppressed = method; 485 } else if (method.getName().equals("addSuppressed")) { 486 addSuppressed = method; 487 } 488 } 489 } 490 491 /** 492 * Determine if Reflection.getCallerClass is available. 493 */ 494 private static void setupCallerCheck() { 495 try { 496 ClassLoader loader = Loader.getClassLoader(); 497 // Use wildcard to avoid compile-time reference. 498 Class<?> clazz = loader.loadClass("sun.reflect.Reflection"); 499 Method[] methods = clazz.getMethods(); 500 for (Method method : methods) { 501 int modifier = method.getModifiers(); 502 if (method.getName().equals("getCallerClass") && Modifier.isStatic(modifier)) { 503 getCallerClass = method; 504 return; 505 } 506 } 507 } catch (ClassNotFoundException cnfe) { 508 LOGGER.debug("sun.reflect.Reflection is not installed"); 509 } 510 511 try { 512 PrivateSecurityManager mgr = new PrivateSecurityManager(); 513 if (mgr.getClasses() != null) { 514 securityManager = mgr; 515 } else { 516 // This shouldn't happen. 517 LOGGER.error("Unable to obtain call stack from security manager"); 518 } 519 } catch (Exception ex) { 520 LOGGER.debug("Unable to install security manager", ex); 521 } 522 } 523 524 private ThrowableProxy[] getSuppressedProxies() { 525 if (getSuppressed != null) { 526 try { 527 return (ThrowableProxy[]) getSuppressed.invoke(this, null); 528 } catch (Exception ex) { 529 return null; 530 } 531 } 532 return null; 533 } 534 535 private void setSuppressed(Throwable throwable) { 536 if (getSuppressed != null && addSuppressed != null) { 537 try { 538 Throwable[] array = (Throwable[]) getSuppressed.invoke(throwable, null); 539 for (Throwable t : array) { 540 addSuppressed.invoke(this, new ThrowableProxy(t)); 541 } 542 } catch (Exception ex) { 543 // 544 } 545 } 546 } 547 548 /** 549 * Cached StackTracePackageElement and the ClassLoader. 550 */ 551 private class CacheEntry { 552 private final StackTracePackageElement element; 553 private final ClassLoader loader; 554 555 public CacheEntry(StackTracePackageElement element, ClassLoader loader) { 556 this.element = element; 557 this.loader = loader; 558 } 559 } 560 561 /** 562 * Security Manager for accessing the call stack. 563 */ 564 private static class PrivateSecurityManager extends SecurityManager { 565 public Class<?>[] getClasses() { 566 return getClassContext(); 567 } 568 } 569 }