001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic.spi;
015
016import ch.qos.logback.core.CoreConstants;
017
018import java.util.List;
019
020/**
021 * This class computes caller data returning the result in the form of a
022 * StackTraceElement array.
023 *
024 * @author Ceki Gülcü
025 */
026public class CallerData {
027
028    /**
029     * When caller information is not available this constant is used for file name,
030     * method name, etc.
031     */
032    public static final String NA = "?";
033
034    // All logger call's in log4j-over-slf4j use the Category class
035    private static final String LOG4J_CATEGORY = "org.apache.log4j.Category";
036    private static final String SLF4J_BOUNDARY = "org.slf4j.Logger";
037
038    /**
039     * When caller information is not available this constant is used for the line
040     * number.
041     */
042    public static final int LINE_NA = -1;
043
044    public static final String CALLER_DATA_NA = "?#?:?" + CoreConstants.LINE_SEPARATOR;
045
046    /**
047     * This value is returned in case no caller data could be extracted.
048     */
049    public static final StackTraceElement[] EMPTY_CALLER_DATA_ARRAY = new StackTraceElement[0];
050
051    /**
052     * Extract caller data information as an array based on a Throwable passed as
053     * parameter
054     */
055    public static StackTraceElement[] extract(Throwable t, String fqnOfInvokingClass, final int maxDepth,
056            List<String> frameworkPackageList) {
057        if (t == null) {
058            return null;
059        }
060
061        StackTraceElement[] steArray = t.getStackTrace();
062        StackTraceElement[] callerDataArray;
063
064        int found = LINE_NA;
065        for (int i = 0; i < steArray.length; i++) {
066            if (isInFrameworkSpace(steArray[i].getClassName(), fqnOfInvokingClass, frameworkPackageList)) {
067                // the caller is assumed to be the next stack frame, hence the +1.
068                found = i + 1;
069            } else {
070                if (found != LINE_NA) {
071                    break;
072                }
073            }
074        }
075
076        // we failed to extract caller data
077        if (found == LINE_NA) {
078            return EMPTY_CALLER_DATA_ARRAY;
079        }
080
081        int availableDepth = steArray.length - found;
082        int desiredDepth = maxDepth < (availableDepth) ? maxDepth : availableDepth;
083
084        callerDataArray = new StackTraceElement[desiredDepth];
085        for (int i = 0; i < desiredDepth; i++) {
086            callerDataArray[i] = steArray[found + i];
087        }
088        return callerDataArray;
089    }
090
091    static boolean isInFrameworkSpace(String currentClass, String fqnOfInvokingClass,
092            List<String> frameworkPackageList) {
093        // the check for org.apache.log4j.Category class is intended to support
094        // log4j-over-slf4j. it solves http://bugzilla.slf4j.org/show_bug.cgi?id=66
095        if (currentClass.equals(fqnOfInvokingClass) || currentClass.equals(LOG4J_CATEGORY)
096                || currentClass.startsWith(SLF4J_BOUNDARY)
097                || isInFrameworkSpaceList(currentClass, frameworkPackageList)) {
098            return true;
099        } else {
100            return false;
101        }
102    }
103
104    /**
105     * Is currentClass present in the list of packages considered part of the
106     * logging framework?
107     */
108    private static boolean isInFrameworkSpaceList(String currentClass, List<String> frameworkPackageList) {
109        if (frameworkPackageList == null)
110            return false;
111
112        for (String s : frameworkPackageList) {
113            if (currentClass.startsWith(s))
114                return true;
115        }
116        return false;
117    }
118
119    /**
120     * Returns a StackTraceElement where all string fields are set to {@link #NA}
121     * and line number is set to {@link #LINE_NA}.
122     *
123     * @return StackTraceElement with values set to NA constants.
124     * @since 1.0.10
125     */
126    public static StackTraceElement naInstance() {
127        return new StackTraceElement(NA, NA, NA, LINE_NA);
128    }
129
130}