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    }