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.util.Arrays; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026import java.util.Stack; 027 028import org.apache.logging.log4j.core.pattern.PlainTextRenderer; 029import org.apache.logging.log4j.core.pattern.TextRenderer; 030import org.apache.logging.log4j.util.StackLocatorUtil; 031import org.apache.logging.log4j.util.Strings; 032 033/** 034 * Wraps a Throwable to add packaging information about each stack trace element. 035 * 036 * <p> 037 * A proxy is used to represent a throwable that may not exist in a different class loader or JVM. When an application 038 * deserializes a ThrowableProxy, the throwable may not be set, but the throwable's information is preserved in other 039 * fields of the proxy like the message and stack trace. 040 * </p> 041 * 042 * <p> 043 * TODO: Move this class to org.apache.logging.log4j.core because it is used from LogEvent. 044 * </p> 045 * <p> 046 * TODO: Deserialize: Try to rebuild Throwable if the target exception is in this class loader? 047 * </p> 048 */ 049public class ThrowableProxy implements Serializable { 050 051 private static final char EOL = '\n'; 052 053 private static final String EOL_STR = String.valueOf(EOL); 054 055 private static final long serialVersionUID = -2752771578252251910L; 056 057 private final ThrowableProxy causeProxy; 058 059 private int commonElementCount; 060 061 private final ExtendedStackTraceElement[] extendedStackTrace; 062 063 private final String localizedMessage; 064 065 private final String message; 066 067 private final String name; 068 069 private final ThrowableProxy[] suppressedProxies; 070 071 private final transient Throwable throwable; 072 073 /** 074 * For JSON and XML IO via Jackson. 075 */ 076 @SuppressWarnings("unused") 077 private ThrowableProxy() { 078 this.throwable = null; 079 this.name = null; 080 this.extendedStackTrace = null; 081 this.causeProxy = null; 082 this.message = null; 083 this.localizedMessage = null; 084 this.suppressedProxies = ThrowableProxyHelper.EMPTY_THROWABLE_PROXY_ARRAY; 085 } 086 087 /** 088 * Constructs the wrapper for the Throwable that includes packaging data. 089 * 090 * @param throwable The Throwable to wrap, must not be null. 091 */ 092 public ThrowableProxy(final Throwable throwable) { 093 this(throwable, null); 094 } 095 096 /** 097 * Constructs the wrapper for the Throwable that includes packaging data. 098 * 099 * @param throwable The Throwable to wrap, must not be null. 100 * @param visited The set of visited suppressed exceptions. 101 */ 102 ThrowableProxy(final Throwable throwable, final Set<Throwable> visited) { 103 this.throwable = throwable; 104 this.name = throwable.getClass().getName(); 105 this.message = throwable.getMessage(); 106 this.localizedMessage = throwable.getLocalizedMessage(); 107 final Map<String, ThrowableProxyHelper.CacheEntry> map = new HashMap<>(); 108 final Stack<Class<?>> stack = StackLocatorUtil.getCurrentStackTrace(); 109 this.extendedStackTrace = ThrowableProxyHelper.toExtendedStackTrace(this, stack, map, null, throwable.getStackTrace()); 110 final Throwable throwableCause = throwable.getCause(); 111 final Set<Throwable> causeVisited = new HashSet<>(1); 112 this.causeProxy = throwableCause == null ? null : new ThrowableProxy(throwable, stack, map, throwableCause, 113 visited, causeVisited); 114 this.suppressedProxies = ThrowableProxyHelper.toSuppressedProxies(throwable, visited); 115 } 116 117 /** 118 * Constructs the wrapper for a Throwable that is referenced as the cause by another Throwable. 119 * 120 * @param parent The Throwable referencing this Throwable. 121 * @param stack The Class stack. 122 * @param map The cache containing the packaging data. 123 * @param cause The Throwable to wrap. 124 * @param suppressedVisited TODO 125 * @param causeVisited TODO 126 */ 127 private ThrowableProxy(final Throwable parent, final Stack<Class<?>> stack, 128 final Map<String, ThrowableProxyHelper.CacheEntry> map, 129 final Throwable cause, final Set<Throwable> suppressedVisited, 130 final Set<Throwable> causeVisited) { 131 causeVisited.add(cause); 132 this.throwable = cause; 133 this.name = cause.getClass().getName(); 134 this.message = this.throwable.getMessage(); 135 this.localizedMessage = this.throwable.getLocalizedMessage(); 136 this.extendedStackTrace = ThrowableProxyHelper.toExtendedStackTrace(this, stack, map, parent.getStackTrace(), cause.getStackTrace()); 137 final Throwable causeCause = cause.getCause(); 138 this.causeProxy = causeCause == null || causeVisited.contains(causeCause) ? null : new ThrowableProxy(parent, 139 stack, map, causeCause, suppressedVisited, causeVisited); 140 this.suppressedProxies = ThrowableProxyHelper.toSuppressedProxies(cause, suppressedVisited); 141 } 142 143 @Override 144 public boolean equals(final Object obj) { 145 if (this == obj) { 146 return true; 147 } 148 if (obj == null) { 149 return false; 150 } 151 if (this.getClass() != obj.getClass()) { 152 return false; 153 } 154 final ThrowableProxy other = (ThrowableProxy) obj; 155 if (this.causeProxy == null) { 156 if (other.causeProxy != null) { 157 return false; 158 } 159 } else if (!this.causeProxy.equals(other.causeProxy)) { 160 return false; 161 } 162 if (this.commonElementCount != other.commonElementCount) { 163 return false; 164 } 165 if (this.name == null) { 166 if (other.name != null) { 167 return false; 168 } 169 } else if (!this.name.equals(other.name)) { 170 return false; 171 } 172 if (!Arrays.equals(this.extendedStackTrace, other.extendedStackTrace)) { 173 return false; 174 } 175 if (!Arrays.equals(this.suppressedProxies, other.suppressedProxies)) { 176 return false; 177 } 178 return true; 179 } 180 181 /** 182 * Formats the specified Throwable. 183 * @param sb StringBuilder to contain the formatted Throwable. 184 * @param cause The Throwable to format. 185 * @param suffix 186 */ 187 public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final String suffix) { 188 this.formatWrapper(sb, cause, null, PlainTextRenderer.getInstance(), suffix); 189 } 190 191 /** 192 * Formats the specified Throwable. 193 * @param sb StringBuilder to contain the formatted Throwable. 194 * @param cause The Throwable to format. 195 * @param ignorePackages The List of packages to be suppressed from the trace. 196 * @param suffix 197 */ 198 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") 199 public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages, final String suffix) { 200 this.formatWrapper(sb, cause, ignorePackages, PlainTextRenderer.getInstance(), suffix); 201 } 202 203 /** 204 * Formats the specified Throwable. 205 * @param sb StringBuilder to contain the formatted Throwable. 206 * @param cause The Throwable to format. 207 * @param ignorePackages The List of packages to be suppressed from the stack trace. 208 * @param textRenderer The text renderer. 209 * @param suffix Append this to the end of each stack frame. 210 */ 211 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") 212 public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages, 213 final TextRenderer textRenderer, final String suffix) { 214 formatWrapper(sb, cause, ignorePackages, textRenderer, suffix, EOL_STR); 215 } 216 217 /** 218 * Formats the specified Throwable. 219 * @param sb StringBuilder to contain the formatted Throwable. 220 * @param cause The Throwable to format. 221 * @param ignorePackages The List of packages to be suppressed from the stack trace. 222 * @param textRenderer The text renderer. 223 * @param suffix Append this to the end of each stack frame. 224 * @param lineSeparator The end-of-line separator. 225 */ 226 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") 227 public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages, 228 final TextRenderer textRenderer, final String suffix, final String lineSeparator) { 229 ThrowableProxyRenderer.formatWrapper(sb, cause, ignorePackages, textRenderer, suffix, lineSeparator); 230 } 231 232 public ThrowableProxy getCauseProxy() { 233 return this.causeProxy; 234 } 235 236 /** 237 * Formats the Throwable that is the cause of this Throwable. 238 * 239 * @return The formatted Throwable that caused this Throwable. 240 * @param suffix 241 */ 242 public String getCauseStackTraceAsString(final String suffix) { 243 return this.getCauseStackTraceAsString(null, PlainTextRenderer.getInstance(), suffix, EOL_STR); 244 } 245 246 /** 247 * Formats the Throwable that is the cause of this Throwable. 248 * 249 * @param packages The List of packages to be suppressed from the trace. 250 * @param suffix Append this to the end of each stack frame. 251 * @return The formatted Throwable that caused this Throwable. 252 */ 253 public String getCauseStackTraceAsString(final List<String> packages, final String suffix) { 254 return getCauseStackTraceAsString(packages, PlainTextRenderer.getInstance(), suffix, EOL_STR); 255 } 256 257 /** 258 * Formats the Throwable that is the cause of this Throwable. 259 * 260 * @param ignorePackages The List of packages to be suppressed from the trace. 261 * @param textRenderer The text renderer. 262 * @param suffix Append this to the end of each stack frame. 263 * @return The formatted Throwable that caused this Throwable. 264 */ 265 public String getCauseStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix) { 266 return getCauseStackTraceAsString(ignorePackages, textRenderer, suffix, EOL_STR); 267 } 268 269 /** 270 * Formats the Throwable that is the cause of this Throwable. 271 * 272 * @param ignorePackages The List of packages to be suppressed from the stack trace. 273 * @param textRenderer The text renderer. 274 * @param suffix Append this to the end of each stack frame. 275 * @param lineSeparator The end-of-line separator. 276 * @return The formatted Throwable that caused this Throwable. 277 */ 278 public String getCauseStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { 279 final StringBuilder sb = new StringBuilder(); 280 ThrowableProxyRenderer.formatCauseStackTrace(this, sb, ignorePackages, textRenderer, suffix, lineSeparator); 281 return sb.toString(); 282 } 283 284 /** 285 * Returns the number of elements that are being omitted because they are common with the parent Throwable's stack 286 * trace. 287 * 288 * @return The number of elements omitted from the stack trace. 289 */ 290 public int getCommonElementCount() { 291 return this.commonElementCount; 292 } 293 294 /** 295 * Set the value of {@link ThrowableProxy#commonElementCount}. 296 * 297 * Method is package-private, to be used internally for initialization. 298 * 299 * @param value New value of commonElementCount. 300 */ 301 void setCommonElementCount(final int value) { 302 this.commonElementCount = value; 303 } 304 305 /** 306 * Gets the stack trace including packaging information. 307 * 308 * @return The stack trace including packaging information. 309 */ 310 public ExtendedStackTraceElement[] getExtendedStackTrace() { 311 return this.extendedStackTrace; 312 } 313 314 /** 315 * Formats the stack trace including packaging information. 316 * 317 * @return The formatted stack trace including packaging information. 318 */ 319 public String getExtendedStackTraceAsString() { 320 return this.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance(), Strings.EMPTY, EOL_STR); 321 } 322 323 /** 324 * Formats the stack trace including packaging information. 325 * 326 * @return The formatted stack trace including packaging information. 327 * @param suffix Append this to the end of each stack frame. 328 */ 329 public String getExtendedStackTraceAsString(final String suffix) { 330 return this.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance(), suffix, EOL_STR); 331 } 332 333 /** 334 * Formats the stack trace including packaging information. 335 * 336 * @param ignorePackages List of packages to be ignored in the trace. 337 * @param suffix Append this to the end of each stack frame. 338 * @return The formatted stack trace including packaging information. 339 */ 340 public String getExtendedStackTraceAsString(final List<String> ignorePackages, final String suffix) { 341 return getExtendedStackTraceAsString(ignorePackages, PlainTextRenderer.getInstance(), suffix, EOL_STR); 342 } 343 344 /** 345 * Formats the stack trace including packaging information. 346 * 347 * @param ignorePackages List of packages to be ignored in the trace. 348 * @param textRenderer The message renderer. 349 * @param suffix Append this to the end of each stack frame. 350 * @return The formatted stack trace including packaging information. 351 */ 352 public String getExtendedStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix) { 353 return getExtendedStackTraceAsString(ignorePackages, textRenderer, suffix, EOL_STR); 354 } 355 356 /** 357 * Formats the stack trace including packaging information. 358 * 359 * @param ignorePackages List of packages to be ignored in the trace. 360 * @param textRenderer The message renderer. 361 * @param suffix Append this to the end of each stack frame. 362 * @param lineSeparator The end-of-line separator. 363 * @return The formatted stack trace including packaging information. 364 */ 365 public String getExtendedStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { 366 final StringBuilder sb = new StringBuilder(1024); 367 formatExtendedStackTraceTo(sb, ignorePackages, textRenderer, suffix, lineSeparator); 368 return sb.toString(); 369 } 370 371 /** 372 * Formats the stack trace including packaging information. 373 * 374 * @param sb Destination. 375 * @param ignorePackages List of packages to be ignored in the trace. 376 * @param textRenderer The message renderer. 377 * @param suffix Append this to the end of each stack frame. 378 * @param lineSeparator The end-of-line separator. 379 */ 380 public void formatExtendedStackTraceTo(final StringBuilder sb, final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { 381 ThrowableProxyRenderer.formatExtendedStackTraceTo(this, sb, ignorePackages, textRenderer, suffix, lineSeparator); 382 } 383 384 public String getLocalizedMessage() { 385 return this.localizedMessage; 386 } 387 388 public String getMessage() { 389 return this.message; 390 } 391 392 /** 393 * Return the FQCN of the Throwable. 394 * 395 * @return The FQCN of the Throwable. 396 */ 397 public String getName() { 398 return this.name; 399 } 400 401 public StackTraceElement[] getStackTrace() { 402 return this.throwable == null ? null : this.throwable.getStackTrace(); 403 } 404 405 /** 406 * Gets proxies for suppressed exceptions. 407 * 408 * @return proxies for suppressed exceptions. 409 */ 410 public ThrowableProxy[] getSuppressedProxies() { 411 return this.suppressedProxies; 412 } 413 414 /** 415 * Formats the suppressed Throwables. 416 * 417 * @return The formatted suppressed Throwables. 418 * @param suffix 419 */ 420 public String getSuppressedStackTrace(final String suffix) { 421 final ThrowableProxy[] suppressed = this.getSuppressedProxies(); 422 if (suppressed == null || suppressed.length == 0) { 423 return Strings.EMPTY; 424 } 425 final StringBuilder sb = new StringBuilder("Suppressed Stack Trace Elements:").append(EOL); 426 for (final ThrowableProxy proxy : suppressed) { 427 sb.append(proxy.getExtendedStackTraceAsString(suffix)); 428 } 429 return sb.toString(); 430 } 431 432 /** 433 * The throwable or null if this object is deserialized from XML or JSON. 434 * 435 * @return The throwable or null if this object is deserialized from XML or JSON. 436 */ 437 public Throwable getThrowable() { 438 return this.throwable; 439 } 440 441 @Override 442 public int hashCode() { 443 final int prime = 31; 444 int result = 1; 445 result = prime * result + (this.causeProxy == null ? 0 : this.causeProxy.hashCode()); 446 result = prime * result + this.commonElementCount; 447 result = prime * result + (this.extendedStackTrace == null ? 0 : Arrays.hashCode(this.extendedStackTrace)); 448 result = prime * result + (this.suppressedProxies == null ? 0 : Arrays.hashCode(this.suppressedProxies)); 449 result = prime * result + (this.name == null ? 0 : this.name.hashCode()); 450 return result; 451 } 452 453 @Override 454 public String toString() { 455 final String msg = this.message; 456 return msg != null ? this.name + ": " + msg : this.name; 457 } 458}