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