View Javadoc
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
76                          .getObject(CoreConstants.EVALUATOR_MAP);
77                  EventEvaluator<ILoggingEvent> ee = (EventEvaluator<ILoggingEvent>) evaluatorMap
78                          .get(evaluatorOrIgnoredStackTraceLine);
79                  if (ee != null) {
80                      addEvaluator(ee);
81                  } else {
82                      addIgnoreStackTraceLine(evaluatorOrIgnoredStackTraceLine);
83                  }
84              }
85          }
86          super.start();
87      }
88  
89      private void addEvaluator(EventEvaluator<ILoggingEvent> ee) {
90          if (evaluatorList == null) {
91              evaluatorList = new ArrayList<EventEvaluator<ILoggingEvent>>();
92          }
93          evaluatorList.add(ee);
94      }
95  
96      private void addIgnoreStackTraceLine(String ignoredStackTraceLine) {
97          if (ignoredStackTraceLines == null) {
98              ignoredStackTraceLines = new ArrayList<String>();
99          }
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 }