1 /**
2  * Logback: the reliable, generic, fast and flexible logging framework.
3  * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4  *
5  * This program and the accompanying materials are dual-licensed under
6  * either the terms of the Eclipse Public License v1.0 as published by
7  * the Eclipse Foundation
8  *
9  *   or (per the licensee's choosing)
10  *
11  * under the terms of the GNU Lesser General Public License version 2.1
12  * as published by the Free Software Foundation.
13  */

14 package ch.qos.logback.classic.pattern;
15
16 import java.util.ArrayList;
17 import java.util.List;
18 import java.util.Map;
19
20 import ch.qos.logback.classic.spi.ILoggingEvent;
21 import ch.qos.logback.classic.spi.IThrowableProxy;
22 import ch.qos.logback.classic.spi.StackTraceElementProxy;
23 import ch.qos.logback.classic.spi.ThrowableProxyUtil;
24 import ch.qos.logback.core.Context;
25 import ch.qos.logback.core.CoreConstants;
26 import ch.qos.logback.core.boolex.EvaluationException;
27 import ch.qos.logback.core.boolex.EventEvaluator;
28 import ch.qos.logback.core.status.ErrorStatus;
29
30 /**
31  * Add a stack trace in case the event contains a Throwable.
32  *
33  * @author Ceki Gülcü
34  */

35 public class ThrowableProxyConverter extends ThrowableHandlingConverter {
36
37     protected static final int BUILDER_CAPACITY = 2048;
38
39     int lengthOption;
40     List<EventEvaluator<ILoggingEvent>> evaluatorList = null;
41     List<String> ignoredStackTraceLines = null;
42
43     int errorCount = 0;
44
45     @SuppressWarnings("unchecked")
46     public void start() {
47
48         String lengthStr = getFirstOption();
49
50         if (lengthStr == null) {
51             lengthOption = Integer.MAX_VALUE;
52         } else {
53             lengthStr = lengthStr.toLowerCase();
54             if ("full".equals(lengthStr)) {
55                 lengthOption = Integer.MAX_VALUE;
56             } else if ("short".equals(lengthStr)) {
57                 lengthOption = 1;
58             } else {
59                 try {
60                     lengthOption = Integer.parseInt(lengthStr);
61                 } catch (NumberFormatException nfe) {
62                     addError("Could not parse [" + lengthStr + "] as an integer");
63                     lengthOption = Integer.MAX_VALUE;
64                 }
65             }
66         }
67
68         final List<String> optionList = getOptionList();
69
70         if (optionList != null && optionList.size() > 1) {
71             final int optionListSize = optionList.size();
72             for (int i = 1; i < optionListSize; i++) {
73                 String evaluatorOrIgnoredStackTraceLine = (String) optionList.get(i);
74                 Context context = getContext();
75                 Map<String, EventEvaluator<?>> evaluatorMap = (Map<String, EventEvaluator<?>>) context.getObject(CoreConstants.EVALUATOR_MAP);
76                 EventEvaluator<ILoggingEvent> ee = (EventEvaluator<ILoggingEvent>) evaluatorMap.get(evaluatorOrIgnoredStackTraceLine);
77                 if (ee != null) {
78                     addEvaluator(ee);
79                 } else {
80                     addIgnoreStackTraceLine(evaluatorOrIgnoredStackTraceLine);
81                 }
82             }
83         }
84         super.start();
85     }
86
87     private void addEvaluator(EventEvaluator<ILoggingEvent> ee) {
88         if (evaluatorList == null) {
89             evaluatorList = new ArrayList<EventEvaluator<ILoggingEvent>>();
90         }
91         evaluatorList.add(ee);
92     }
93
94     private void addIgnoreStackTraceLine(String ignoredStackTraceLine) {
95         if (ignoredStackTraceLines == null) {
96             ignoredStackTraceLines = new ArrayList<String>();
97         }
98         ignoredStackTraceLines.add(ignoredStackTraceLine);
99     }
100
101     public void stop() {
102         evaluatorList = null;
103         super.stop();
104     }
105
106     protected void extraData(StringBuilder builder, StackTraceElementProxy step) {
107         // nop
108     }
109
110     public String convert(ILoggingEvent event) {
111
112         IThrowableProxy tp = event.getThrowableProxy();
113         if (tp == null) {
114             return CoreConstants.EMPTY_STRING;
115         }
116
117         // an evaluator match will cause stack printing to be skipped
118         if (evaluatorList != null) {
119             boolean printStack = true;
120             for (int i = 0; i < evaluatorList.size(); i++) {
121                 EventEvaluator<ILoggingEvent> ee = evaluatorList.get(i);
122                 try {
123                     if (ee.evaluate(event)) {
124                         printStack = false;
125                         break;
126                     }
127                 } catch (EvaluationException eex) {
128                     errorCount++;
129                     if (errorCount < CoreConstants.MAX_ERROR_COUNT) {
130                         addError("Exception thrown for evaluator named [" + ee.getName() + "]", eex);
131                     } else if (errorCount == CoreConstants.MAX_ERROR_COUNT) {
132                         ErrorStatus errorStatus = new ErrorStatus("Exception thrown for evaluator named [" + ee.getName() + "]."this, eex);
133                         errorStatus.add(new ErrorStatus("This was the last warning about this evaluator's errors."
134                                         + "We don't want the StatusManager to get flooded."this));
135                         addStatus(errorStatus);
136                     }
137                 }
138             }
139
140             if (!printStack) {
141                 return CoreConstants.EMPTY_STRING;
142             }
143         }
144
145         return throwableProxyToString(tp);
146     }
147
148     protected String throwableProxyToString(IThrowableProxy tp) {
149         StringBuilder sb = new StringBuilder(BUILDER_CAPACITY);
150
151         recursiveAppend(sb, null, ThrowableProxyUtil.REGULAR_EXCEPTION_INDENT, tp);
152
153         return sb.toString();
154     }
155
156     private void recursiveAppend(StringBuilder sb, String prefix, int indent, IThrowableProxy tp) {
157         if (tp == null)
158             return;
159         subjoinFirstLine(sb, prefix, indent, tp);
160         sb.append(CoreConstants.LINE_SEPARATOR);
161         subjoinSTEPArray(sb, indent, tp);
162         IThrowableProxy[] suppressed = tp.getSuppressed();
163         if (suppressed != null) {
164             for (IThrowableProxy current : suppressed) {
165                 recursiveAppend(sb, CoreConstants.SUPPRESSED, indent + ThrowableProxyUtil.SUPPRESSED_EXCEPTION_INDENT, current);
166             }
167         }
168         recursiveAppend(sb, CoreConstants.CAUSED_BY, indent, tp.getCause());
169     }
170
171     private void subjoinFirstLine(StringBuilder buf, String prefix, int indent, IThrowableProxy tp) {
172         ThrowableProxyUtil.indent(buf, indent - 1);
173         if (prefix != null) {
174             buf.append(prefix);
175         }
176         subjoinExceptionMessage(buf, tp);
177     }
178
179     private void subjoinExceptionMessage(StringBuilder buf, IThrowableProxy tp) {
180         buf.append(tp.getClassName()).append(": ").append(tp.getMessage());
181     }
182
183     protected void subjoinSTEPArray(StringBuilder buf, int indent, IThrowableProxy tp) {
184         StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();
185         int commonFrames = tp.getCommonFrames();
186
187         boolean unrestrictedPrinting = lengthOption > stepArray.length;
188
189         int maxIndex = (unrestrictedPrinting) ? stepArray.length : lengthOption;
190         if (commonFrames > 0 && unrestrictedPrinting) {
191             maxIndex -= commonFrames;
192         }
193
194         int ignoredCount = 0;
195         for (int i = 0; i < maxIndex; i++) {
196             StackTraceElementProxy element = stepArray[i];
197             if (!isIgnoredStackTraceLine(element.toString())) {
198                 ThrowableProxyUtil.indent(buf, indent);
199                 printStackLine(buf, ignoredCount, element);
200                 ignoredCount = 0;
201                 buf.append(CoreConstants.LINE_SEPARATOR);
202             } else {
203                 ++ignoredCount;
204                 if (maxIndex < stepArray.length) {
205                     ++maxIndex;
206                 }
207             }
208         }
209         if (ignoredCount > 0) {
210             printIgnoredCount(buf, ignoredCount);
211             buf.append(CoreConstants.LINE_SEPARATOR);
212         }
213
214         if (commonFrames > 0 && unrestrictedPrinting) {
215             ThrowableProxyUtil.indent(buf, indent);
216             buf.append("... ").append(tp.getCommonFrames()).append(" common frames omitted").append(CoreConstants.LINE_SEPARATOR);
217         }
218     }
219
220     private void printStackLine(StringBuilder buf, int ignoredCount, StackTraceElementProxy element) {
221         buf.append(element);
222         extraData(buf, element); // allow other data to be added
223         if (ignoredCount > 0) {
224             printIgnoredCount(buf, ignoredCount);
225         }
226     }
227
228     private void printIgnoredCount(StringBuilder buf, int ignoredCount) {
229         buf.append(" [").append(ignoredCount).append(" skipped]");
230     }
231
232     private boolean isIgnoredStackTraceLine(String line) {
233         if (ignoredStackTraceLines != null) {
234             for (String ignoredStackTraceLine : ignoredStackTraceLines) {
235                 if (line.contains(ignoredStackTraceLine)) {
236                     return true;
237                 }
238             }
239         }
240         return false;
241     }
242
243 }
244