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.pattern;
018    
019    import org.apache.logging.log4j.core.LogEvent;
020    import org.apache.logging.log4j.core.config.plugins.Plugin;
021    import org.apache.logging.log4j.core.impl.ThrowableProxy;
022    
023    import java.util.ArrayList;
024    import java.util.List;
025    import java.util.Scanner;
026    
027    /**
028     * Outputs the Throwable portion of the LoggingEvent as a full stacktrace
029     * unless this converter's option is 'short', where it just outputs the first line of the trace, or if
030     * the number of lines to print is explicitly specified.
031     * <p>
032     * The extended stack trace will also include the location of where the class was loaded from and the
033     * version of the jar if available.
034     */
035    @Plugin(name = "ExtendedThrowablePatternConverter", type = "Converter")
036    @ConverterKeys({"xEx", "xThrowable", "xException" })
037    public final class ExtendedThrowablePatternConverter extends ThrowablePatternConverter {
038    
039        private static final String FILTERS = "filters(";
040    
041        private final List<String> packages;
042    
043        /**
044         * Private constructor.
045         *
046         * @param options options, may be null.
047         */
048        private ExtendedThrowablePatternConverter(final String[] options) {
049            super("ExtendedThrowable", "throwable", options);
050            List<String> tempPackages = null;
051            if (options != null && options.length > 1) {
052                if (options[1].startsWith(FILTERS) && options[1].endsWith(")")) {
053                    final String filterStr = options[1].substring(FILTERS.length(), options[1].length() - 1);
054                    final String[] array = filterStr.split(",");
055                    if (array.length > 0) {
056                        tempPackages = new ArrayList<String>(array.length);
057                        for (final String token : array) {
058                            tempPackages.add(token.trim());
059                        }
060                    }
061                }
062            }
063            packages = tempPackages;
064        }
065    
066        /**
067         * Gets an instance of the class.
068         *
069         * @param options pattern options, may be null.  If first element is "short",
070         *                only the first line of the throwable will be formatted.
071         * @return instance of class.
072         */
073        public static ExtendedThrowablePatternConverter newInstance(final String[] options) {
074            String type = null;
075            String[] array = options;
076            if (options != null && options.length == 1 && options[0].length() > 0) {
077                final String[] opts = options[0].split(",", 2);
078                final String first = opts[0].trim();
079                String filter;
080                final Scanner scanner = new Scanner(first);
081                if (first.equalsIgnoreCase(FULL) || first.equalsIgnoreCase(SHORT) || scanner.hasNextInt()) {
082                    type = first;
083                    filter = opts[1].trim();
084                } else {
085                    filter = options[0].trim();
086                }
087                array = new String[] {type, filter};
088            }
089    
090            return new ExtendedThrowablePatternConverter(array);
091        }
092    
093        /**
094         * {@inheritDoc}
095         */
096        @Override
097        public void format(final LogEvent event, final StringBuilder toAppendTo) {
098            final Throwable throwable = event.getThrown();
099            if (throwable != null && lines > 0) {
100                if (!(throwable instanceof ThrowableProxy)) {
101                    super.format(event, toAppendTo);
102                    return;
103                }
104                final ThrowableProxy t = (ThrowableProxy) throwable;
105                final String trace = t.getExtendedStackTrace(packages);
106                final int len = toAppendTo.length();
107                if (len > 0 && !Character.isWhitespace(toAppendTo.charAt(len - 1))) {
108                    toAppendTo.append(" ");
109                }
110                if (lines != Integer.MAX_VALUE) {
111                    final StringBuilder sb = new StringBuilder();
112                    final String[] array = trace.split("\n");
113                    final int limit = lines > array.length ? array.length : lines;
114                    for (int i = 0; i < limit; ++i) {
115                        sb.append(array[i]).append("\n");
116                    }
117                    toAppendTo.append(sb.toString());
118    
119                } else {
120                    toAppendTo.append(trace);
121                }
122            }
123        }
124    }