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