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 ch.qos.logback.classic.spi.ILoggingEvent;
021import ch.qos.logback.classic.spi.IThrowableProxy;
022import ch.qos.logback.classic.spi.StackTraceElementProxy;
023import ch.qos.logback.classic.spi.ThrowableProxyUtil;
024import ch.qos.logback.core.Context;
025import ch.qos.logback.core.CoreConstants;
026import ch.qos.logback.core.boolex.EvaluationException;
027import ch.qos.logback.core.boolex.EventEvaluator;
028import ch.qos.logback.core.status.ErrorStatus;
029
030/**
031 * Add a stack trace in case the event contains a Throwable.
032 *
033 * @author Ceki Gülcü
034 */
035public class ThrowableProxyConverter extends ThrowableHandlingConverter {
036
037    protected static final int BUILDER_CAPACITY = 2048;
038
039    int lengthOption;
040    List<EventEvaluator<ILoggingEvent>> evaluatorList = null;
041    List<String> ignoredStackTraceLines = null;
042
043    int errorCount = 0;
044
045    @SuppressWarnings("unchecked")
046    public void start() {
047
048        String lengthStr = getFirstOption();
049
050        if (lengthStr == null) {
051            lengthOption = Integer.MAX_VALUE;
052        } else {
053            lengthStr = lengthStr.toLowerCase();
054            if ("full".equals(lengthStr)) {
055                lengthOption = Integer.MAX_VALUE;
056            } else if ("short".equals(lengthStr)) {
057                lengthOption = 1;
058            } else {
059                try {
060                    lengthOption = Integer.parseInt(lengthStr);
061                } catch (NumberFormatException nfe) {
062                    addError("Could not parse [" + lengthStr + "] as an integer");
063                    lengthOption = Integer.MAX_VALUE;
064                }
065            }
066        }
067
068        final List<String> optionList = getOptionList();
069
070        if (optionList != null && optionList.size() > 1) {
071            final int optionListSize = optionList.size();
072            for (int i = 1; i < optionListSize; i++) {
073                String evaluatorOrIgnoredStackTraceLine = (String) optionList.get(i);
074                Context context = getContext();
075                Map<String, EventEvaluator<?>> evaluatorMap = (Map<String, EventEvaluator<?>>) context
076                        .getObject(CoreConstants.EVALUATOR_MAP);
077                EventEvaluator<ILoggingEvent> ee = (EventEvaluator<ILoggingEvent>) evaluatorMap
078                        .get(evaluatorOrIgnoredStackTraceLine);
079                if (ee != null) {
080                    addEvaluator(ee);
081                } else {
082                    addIgnoreStackTraceLine(evaluatorOrIgnoredStackTraceLine);
083                }
084            }
085        }
086        super.start();
087    }
088
089    private void addEvaluator(EventEvaluator<ILoggingEvent> ee) {
090        if (evaluatorList == null) {
091            evaluatorList = new ArrayList<EventEvaluator<ILoggingEvent>>();
092        }
093        evaluatorList.add(ee);
094    }
095
096    private void addIgnoreStackTraceLine(String ignoredStackTraceLine) {
097        if (ignoredStackTraceLines == null) {
098            ignoredStackTraceLines = new ArrayList<String>();
099        }
100        ignoredStackTraceLines.add(ignoredStackTraceLine);
101    }
102
103    public void stop() {
104        evaluatorList = null;
105        super.stop();
106    }
107
108    protected void extraData(StringBuilder builder, StackTraceElementProxy step) {
109        // nop
110    }
111
112    public String convert(ILoggingEvent event) {
113
114        IThrowableProxy tp = event.getThrowableProxy();
115        if (tp == null) {
116            return CoreConstants.EMPTY_STRING;
117        }
118
119        // an evaluator match will cause stack printing to be skipped
120        if (evaluatorList != null) {
121            boolean printStack = true;
122            for (int i = 0; i < evaluatorList.size(); i++) {
123                EventEvaluator<ILoggingEvent> ee = evaluatorList.get(i);
124                try {
125                    if (ee.evaluate(event)) {
126                        printStack = false;
127                        break;
128                    }
129                } catch (EvaluationException eex) {
130                    errorCount++;
131                    if (errorCount < CoreConstants.MAX_ERROR_COUNT) {
132                        addError("Exception thrown for evaluator named [" + ee.getName() + "]", eex);
133                    } else if (errorCount == CoreConstants.MAX_ERROR_COUNT) {
134                        ErrorStatus errorStatus = new ErrorStatus(
135                                "Exception thrown for evaluator named [" + ee.getName() + "].", this, eex);
136                        errorStatus.add(new ErrorStatus("This was the last warning about this evaluator's errors."
137                                + "We don't want the StatusManager to get flooded.", this));
138                        addStatus(errorStatus);
139                    }
140                }
141            }
142
143            if (!printStack) {
144                return CoreConstants.EMPTY_STRING;
145            }
146        }
147
148        return throwableProxyToString(tp);
149    }
150
151    protected String throwableProxyToString(IThrowableProxy tp) {
152        StringBuilder sb = new StringBuilder(BUILDER_CAPACITY);
153
154        recursiveAppend(sb, null, ThrowableProxyUtil.REGULAR_EXCEPTION_INDENT, tp);
155
156        return sb.toString();
157    }
158
159    private void recursiveAppend(StringBuilder sb, String prefix, int indent, IThrowableProxy tp) {
160        if (tp == null)
161            return;
162        subjoinFirstLine(sb, prefix, indent, tp);
163        sb.append(CoreConstants.LINE_SEPARATOR);
164        subjoinSTEPArray(sb, indent, tp);
165        IThrowableProxy[] suppressed = tp.getSuppressed();
166        if (suppressed != null) {
167            for (IThrowableProxy current : suppressed) {
168                recursiveAppend(sb, CoreConstants.SUPPRESSED, indent + ThrowableProxyUtil.SUPPRESSED_EXCEPTION_INDENT,
169                        current);
170            }
171        }
172        recursiveAppend(sb, CoreConstants.CAUSED_BY, indent, tp.getCause());
173    }
174
175    private void subjoinFirstLine(StringBuilder buf, String prefix, int indent, IThrowableProxy tp) {
176        ThrowableProxyUtil.indent(buf, indent - 1);
177        if (prefix != null) {
178            buf.append(prefix);
179        }
180        subjoinExceptionMessage(buf, tp);
181    }
182
183    private void subjoinExceptionMessage(StringBuilder buf, IThrowableProxy tp) {
184        if (tp.isCyclic()) {
185            buf.append("[CIRCULAR REFERENCE: ").append(tp.getClassName()).append(": ").append(tp.getMessage())
186                    .append(']');
187        } else {
188            buf.append(tp.getClassName()).append(": ").append(tp.getMessage());
189        }
190    }
191
192    protected void subjoinSTEPArray(StringBuilder buf, int indent, IThrowableProxy tp) {
193        StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();
194        int commonFrames = tp.getCommonFrames();
195
196        boolean unrestrictedPrinting = lengthOption > stepArray.length;
197
198        int maxIndex = (unrestrictedPrinting) ? stepArray.length : lengthOption;
199        if (commonFrames > 0 && unrestrictedPrinting) {
200            maxIndex -= commonFrames;
201        }
202
203        int ignoredCount = 0;
204        for (int i = 0; i < maxIndex; i++) {
205            StackTraceElementProxy element = stepArray[i];
206            if (!isIgnoredStackTraceLine(element.toString())) {
207                ThrowableProxyUtil.indent(buf, indent);
208                printStackLine(buf, ignoredCount, element);
209                ignoredCount = 0;
210                buf.append(CoreConstants.LINE_SEPARATOR);
211            } else {
212                ++ignoredCount;
213                if (maxIndex < stepArray.length) {
214                    ++maxIndex;
215                }
216            }
217        }
218        if (ignoredCount > 0) {
219            printIgnoredCount(buf, ignoredCount);
220            buf.append(CoreConstants.LINE_SEPARATOR);
221        }
222
223        if (commonFrames > 0 && unrestrictedPrinting) {
224            ThrowableProxyUtil.indent(buf, indent);
225            buf.append("... ").append(tp.getCommonFrames()).append(" common frames omitted")
226                    .append(CoreConstants.LINE_SEPARATOR);
227        }
228    }
229
230    private void printStackLine(StringBuilder buf, int ignoredCount, StackTraceElementProxy element) {
231        buf.append(element);
232        extraData(buf, element); // allow other data to be added
233        if (ignoredCount > 0) {
234            printIgnoredCount(buf, ignoredCount);
235        }
236    }
237
238    private void printIgnoredCount(StringBuilder buf, int ignoredCount) {
239        buf.append(" [").append(ignoredCount).append(" skipped]");
240    }
241
242    private boolean isIgnoredStackTraceLine(String line) {
243        if (ignoredStackTraceLines != null) {
244            for (String ignoredStackTraceLine : ignoredStackTraceLines) {
245                if (line.contains(ignoredStackTraceLine)) {
246                    return true;
247                }
248            }
249        }
250        return false;
251    }
252
253}