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 static java.util.regex.Pattern.quote;
21  import ch.qos.logback.classic.spi.CallerData;
22  import ch.qos.logback.classic.spi.ILoggingEvent;
23  import ch.qos.logback.core.Context;
24  import ch.qos.logback.core.CoreConstants;
25  import ch.qos.logback.core.boolex.EvaluationException;
26  import ch.qos.logback.core.boolex.EventEvaluator;
27  import ch.qos.logback.core.status.ErrorStatus;
28  
29  /**
30   * This converter outputs caller data depending on depth or depth range and
31   * marker data.
32   * 
33   * @author Ceki Gulcu
34   */
35  public class CallerDataConverter extends ClassicConverter {
36  
37      public static final String DEFAULT_CALLER_LINE_PREFIX = "Caller+";
38  
39      public static final String DEFAULT_RANGE_DELIMITER = "..";
40  
41      private int depthStart = 0;
42      private int depthEnd = 5;
43      List<EventEvaluator<ILoggingEvent>> evaluatorList = null;
44  
45      final int MAX_ERROR_COUNT = 4;
46      int errorCount = 0;
47  
48      @SuppressWarnings("unchecked")
49      public void start() {
50          String depthStr = getFirstOption();
51          if (depthStr == null) {
52              return;
53          }
54  
55          try {
56              if (isRange(depthStr)) {
57                  String[] numbers = splitRange(depthStr);
58                  if (numbers.length == 2) {
59                      depthStart = Integer.parseInt(numbers[0]);
60                      depthEnd = Integer.parseInt(numbers[1]);
61                      checkRange();
62                  } else {
63                      addError("Failed to parse depth option as range [" + depthStr + "]");
64                  }
65              } else {
66                  depthEnd = Integer.parseInt(depthStr);
67              }
68          } catch (NumberFormatException nfe) {
69              addError("Failed to parse depth option [" + depthStr + "]", nfe);
70          }
71  
72          final List<String> optionList = getOptionList();
73  
74          if (optionList != null && optionList.size() > 1) {
75              final int optionListSize = optionList.size();
76              for (int i = 1; i < optionListSize; i++) {
77                  String evaluatorStr = optionList.get(i);
78                  Context context = getContext();
79                  if (context != null) {
80                      Map<String, EventEvaluator<?>> evaluatorMap = (Map<String, EventEvaluator<?>>) context
81                              .getObject(CoreConstants.EVALUATOR_MAP);
82                      EventEvaluator<ILoggingEvent> ee = (EventEvaluator<ILoggingEvent>) evaluatorMap.get(evaluatorStr);
83                      if (ee != null) {
84                          addEvaluator(ee);
85                      }
86                  }
87              }
88          }
89      }
90  
91      private boolean isRange(String depthStr) {
92          return depthStr.contains(getDefaultRangeDelimiter());
93      }
94  
95      private String[] splitRange(String depthStr) {
96          return depthStr.split(quote(getDefaultRangeDelimiter()), 2);
97      }
98  
99      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 }