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.pattern;
015
016import java.util.ArrayList;
017import java.util.List;
018import java.util.Map;
019
020import static java.util.regex.Pattern.quote;
021import ch.qos.logback.classic.spi.CallerData;
022import ch.qos.logback.classic.spi.ILoggingEvent;
023import ch.qos.logback.core.Context;
024import ch.qos.logback.core.CoreConstants;
025import ch.qos.logback.core.boolex.EvaluationException;
026import ch.qos.logback.core.boolex.EventEvaluator;
027import ch.qos.logback.core.status.ErrorStatus;
028
029/**
030 * This converter outputs caller data depending on depth or depth range and
031 * marker data.
032 * 
033 * @author Ceki Gulcu
034 */
035public class CallerDataConverter extends ClassicConverter {
036
037    public static final String DEFAULT_CALLER_LINE_PREFIX = "Caller+";
038
039    public static final String DEFAULT_RANGE_DELIMITER = "..";
040
041    private int depthStart = 0;
042    private int depthEnd = 5;
043    List<EventEvaluator<ILoggingEvent>> evaluatorList = null;
044
045    final int MAX_ERROR_COUNT = 4;
046    int errorCount = 0;
047
048    @SuppressWarnings("unchecked")
049    public void start() {
050        String depthStr = getFirstOption();
051        if (depthStr == null) {
052            return;
053        }
054
055        try {
056            if (isRange(depthStr)) {
057                String[] numbers = splitRange(depthStr);
058                if (numbers.length == 2) {
059                    depthStart = Integer.parseInt(numbers[0]);
060                    depthEnd = Integer.parseInt(numbers[1]);
061                    checkRange();
062                } else {
063                    addError("Failed to parse depth option as range [" + depthStr + "]");
064                }
065            } else {
066                depthEnd = Integer.parseInt(depthStr);
067            }
068        } catch (NumberFormatException nfe) {
069            addError("Failed to parse depth option [" + depthStr + "]", nfe);
070        }
071
072        final List<String> optionList = getOptionList();
073
074        if (optionList != null && optionList.size() > 1) {
075            final int optionListSize = optionList.size();
076            for (int i = 1; i < optionListSize; i++) {
077                String evaluatorStr = optionList.get(i);
078                Context context = getContext();
079                if (context != null) {
080                    Map<String, EventEvaluator<?>> evaluatorMap = (Map<String, EventEvaluator<?>>) context
081                            .getObject(CoreConstants.EVALUATOR_MAP);
082                    EventEvaluator<ILoggingEvent> ee = (EventEvaluator<ILoggingEvent>) evaluatorMap.get(evaluatorStr);
083                    if (ee != null) {
084                        addEvaluator(ee);
085                    }
086                }
087            }
088        }
089    }
090
091    private boolean isRange(String depthStr) {
092        return depthStr.contains(getDefaultRangeDelimiter());
093    }
094
095    private String[] splitRange(String depthStr) {
096        return depthStr.split(quote(getDefaultRangeDelimiter()), 2);
097    }
098
099    private void checkRange() {
100        if (depthStart < 0 || depthEnd < 0) {
101            addError("Invalid depthStart/depthEnd range [" + depthStart + ", " + depthEnd
102                    + "] (negative values are not allowed)");
103        } else if (depthStart >= depthEnd) {
104            addError("Invalid depthEnd range [" + depthStart + ", " + depthEnd + "] (start greater or equal to end)");
105        }
106    }
107
108    private void addEvaluator(EventEvaluator<ILoggingEvent> ee) {
109        if (evaluatorList == null) {
110            evaluatorList = new ArrayList<EventEvaluator<ILoggingEvent>>();
111        }
112        evaluatorList.add(ee);
113    }
114
115    public String convert(ILoggingEvent le) {
116        StringBuilder buf = new StringBuilder();
117
118        if (evaluatorList != null) {
119            boolean printCallerData = false;
120            for (int i = 0; i < evaluatorList.size(); i++) {
121                EventEvaluator<ILoggingEvent> ee = evaluatorList.get(i);
122                try {
123                    if (ee.evaluate(le)) {
124                        printCallerData = true;
125                        break;
126                    }
127                } catch (EvaluationException eex) {
128                    errorCount++;
129                    if (errorCount < MAX_ERROR_COUNT) {
130                        addError("Exception thrown for evaluator named [" + ee.getName() + "]", eex);
131                    } else if (errorCount == MAX_ERROR_COUNT) {
132                        ErrorStatus errorStatus = new ErrorStatus(
133                                "Exception thrown for evaluator named [" + ee.getName() + "].", this, eex);
134                        errorStatus.add(new ErrorStatus("This was the last warning about this evaluator's errors."
135                                + "We don't want the StatusManager to get flooded.", this));
136                        addStatus(errorStatus);
137                    }
138
139                }
140            }
141
142            if (!printCallerData) {
143                return CoreConstants.EMPTY_STRING;
144            }
145        }
146
147        StackTraceElement[] cda = le.getCallerData();
148        if (cda != null && cda.length > depthStart) {
149            int limit = depthEnd < cda.length ? depthEnd : cda.length;
150
151            for (int i = depthStart; i < limit; i++) {
152                buf.append(getCallerLinePrefix());
153                buf.append(i);
154                buf.append("\t at ");
155                buf.append(cda[i]);
156                buf.append(CoreConstants.LINE_SEPARATOR);
157            }
158            return buf.toString();
159        } else {
160            return CallerData.CALLER_DATA_NA;
161        }
162    }
163
164    protected String getCallerLinePrefix() {
165        return DEFAULT_CALLER_LINE_PREFIX;
166    }
167
168    protected String getDefaultRangeDelimiter() {
169        return DEFAULT_RANGE_DELIMITER;
170    }
171}