001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, 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 v2.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        ThrowableProxyUtil.subjoinExceptionMessage(buf, tp);
181    }
182
183
184    protected void subjoinSTEPArray(StringBuilder buf, int indent, IThrowableProxy tp) {
185        StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();
186        int commonFrames = tp.getCommonFrames();
187
188        boolean unrestrictedPrinting = lengthOption > stepArray.length;
189
190        int maxIndex = (unrestrictedPrinting) ? stepArray.length : lengthOption;
191        if (commonFrames > 0 && unrestrictedPrinting) {
192            maxIndex -= commonFrames;
193        }
194
195        int ignoredCount = 0;
196        for (int i = 0; i < maxIndex; i++) {
197            StackTraceElementProxy element = stepArray[i];
198            if (!isIgnoredStackTraceLine(element.toString())) {
199                ThrowableProxyUtil.indent(buf, indent);
200                printStackLine(buf, ignoredCount, element);
201                ignoredCount = 0;
202                buf.append(CoreConstants.LINE_SEPARATOR);
203            } else {
204                ++ignoredCount;
205                if (maxIndex < stepArray.length) {
206                    ++maxIndex;
207                }
208            }
209        }
210        if (ignoredCount > 0) {
211            printIgnoredCount(buf, ignoredCount);
212            buf.append(CoreConstants.LINE_SEPARATOR);
213        }
214
215        if (commonFrames > 0 && unrestrictedPrinting) {
216            ThrowableProxyUtil.indent(buf, indent);
217            buf.append("... ").append(tp.getCommonFrames()).append(" common frames omitted")
218                    .append(CoreConstants.LINE_SEPARATOR);
219        }
220    }
221
222    private void printStackLine(StringBuilder buf, int ignoredCount, StackTraceElementProxy element) {
223        buf.append(element);
224        extraData(buf, element); // allow other data to be added
225        if (ignoredCount > 0) {
226            printIgnoredCount(buf, ignoredCount);
227        }
228    }
229
230    private void printIgnoredCount(StringBuilder buf, int ignoredCount) {
231        buf.append(" [").append(ignoredCount).append(" skipped]");
232    }
233
234    private boolean isIgnoredStackTraceLine(String line) {
235        if (ignoredStackTraceLines != null) {
236            for (String ignoredStackTraceLine : ignoredStackTraceLines) {
237                if (line.contains(ignoredStackTraceLine)) {
238                    return true;
239                }
240            }
241        }
242        return false;
243    }
244
245}