View Javadoc

1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2011, 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.turbo;
15  
16  import ch.qos.logback.classic.Logger;
17  import ch.qos.logback.classic.Level;
18  import ch.qos.logback.core.spi.FilterReply;
19  import org.slf4j.Marker;
20  import org.slf4j.MDC;
21  
22  import java.util.Map;
23  import java.util.HashMap;
24  
25  /**
26   * This filter allows for efficient course grained filtering based on criteria
27   * such as product name or company name that would be associated with requests
28   * as they are processed.
29   * 
30   * <p> This filter will allow you to associate threshold levels to a key put in
31   * the MDC. This key can be any value specified by the user. Furthermore, you
32   * can pass MDC value and level threshold associations, which are then looked up
33   * to find the level threshold to apply to the current logging request. If no
34   * level threshold could be found, then a 'default' value specified by the user
35   * is applied. We call this value 'levelAssociatedWithMDCValue'.
36   * 
37   * <p> If 'levelAssociatedWithMDCValue' is higher or equal to the level of the
38   * current logger request, the
39   * {@link #decide(Marker, Logger, Level, String, Object[], Throwable) decide()}
40   * method returns the value of {@link #getOnHigherOrEqual() onHigherOrEqual},
41   * if it is lower then the value of {@link #getOnLower() onLower} is returned.
42   * Both 'onHigherOrEqual' and 'onLower' can be set by the user. By default,
43   * 'onHigherOrEqual' is set to NEUTRAL and 'onLower' is set to DENY. Thus, if
44   * the current logger request's level is lower than
45   * 'levelAssociatedWithMDCValue', then the request is denied, and if it is
46   * higher or equal, then this filter decides NEUTRAL letting subsequent filters
47   * to make the decision on the fate of the logging request.
48   * 
49   * <p> The example below illustrates how logging could be enabled for only
50   * individual users. In this example all events for logger names matching
51   * "com.mycompany" will be logged if they are for 'user1' and at a level higher
52   * than equals to DEBUG, and for 'user2' if they are at a level higher than or
53   * equal to TRACE, and for other users only if they are at level ERROR or
54   * higher. Events issued by loggers other than "com.mycompany" will only be
55   * logged if they are at level ERROR or higher since that is all the root logger
56   * allows.
57   * 
58   * <pre>
59   * &lt;configuration&gt;
60   *   &lt;appender name="STDOUT"
61   *             class="ch.qos.logback.core.ConsoleAppender"&gt;
62   *     &lt;layout class="ch.qos.logback.classic.PatternLayout"&gt;
63   *       &lt;Pattern>TEST %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n&lt;/Pattern>
64   *     &lt;/layout&gt;
65   *   &lt;/appender&gt;
66   *   
67   *   &lt;turboFilter class=&quot;ch.qos.logback.classic.turbo.DynamicThresholdFilter&quot;&gt;
68   *     &lt;Key&gt;userId&lt;/Key&gt;
69   *     &lt;DefaultThreshold&gt;ERROR&lt;/DefaultThreshold&gt;
70   *     &lt;MDCValueLevelPair&gt;
71   *       &lt;value&gt;user1&lt;/value&gt;
72   *       &lt;level&gt;DEBUG&lt;/level&gt;
73   *     &lt;/MDCValueLevelPair&gt;
74   *     &lt;MDCValueLevelPair&gt;
75   *       &lt;value&gt;user2&lt;/value&gt;
76   *       &lt;level&gt;TRACE&lt;/level&gt;
77   *     &lt;/MDCValueLevelPair&gt;
78   *   &lt;/turboFilter&gt;
79   *   
80   *   &lt;logger name="com.mycompany" level="TRACE"/&gt;
81   *   
82   *   &lt;root level="ERROR" &gt;
83   *     &lt;appender-ref ref="STDOUT" /&gt;
84   *   &lt;/root&gt;
85   * &lt;/configuration&gt;
86   * </pre>
87   * 
88   * In the next configuration events from user1 and user2 will be logged
89   * regardless of the logger levels. Events for other users and records without a
90   * userid in the MDC will be logged if they are ERROR level messages. With this
91   * configuration, the root level is never checked since DynamicThresholdFilter
92   * will either accept or deny all records.
93   * 
94   * <pre>
95   * &lt;configuration&gt;
96   *   &lt;appender name="STDOUT"
97   *             class="ch.qos.logback.core.ConsoleAppender"&gt;
98   *     &lt;layout class="ch.qos.logback.classic.PatternLayout"&gt;
99   *        &lt;Pattern>TEST %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n&lt;/Pattern>
100  *     &lt;/layout&gt;
101  *   &lt;/appender&gt;
102  *   
103  *   &lt;turboFilter class=&quot;ch.qos.logback.classic.turbo.DynamicThresholdFilter&quot;&gt;
104  *     &lt;Key&gt;userId&lt;/Key&gt;
105  *     &lt;DefaultThreshold&gt;ERROR&lt;/DefaultThreshold&gt;
106  *     &lt;OnHigherOrEqual&gt;ACCEPT&lt;/OnHigherOrEqual&gt;
107  *     &lt;OnLower&gt;DENY&lt;/OnLower&gt;
108  *     &lt;MDCValueLevelPair&gt;
109  *       &lt;value&gt;user1&lt;/value&gt;
110  *       &lt;level&gt;TRACE&lt;/level&gt;
111  *     &lt;/MDCValueLevelPair&gt;
112  *     &lt;MDCValueLevelPair&gt;
113  *       &lt;value&gt;user2&lt;/value&gt;
114  *       &lt;level&gt;TRACE&lt;/level&gt;
115  *     &lt;/MDCValueLevelPair&gt;
116  *   &lt;/turboFilter&gt;
117  *   
118  *   &lt;root level="DEBUG" &gt;
119  *     &lt;appender-ref ref="STDOUT" /&gt;
120  *   &lt;/root&gt;
121  * &lt;/configuration&gt;
122  * </pre>
123  * 
124  * @author Raplh Goers
125  * @author Ceki G&uuml;lc&uuml;
126  */
127 public class DynamicThresholdFilter extends TurboFilter {
128   private Map<String, Level> valueLevelMap = new HashMap<String, Level>();
129   private Level defaultThreshold = Level.ERROR;
130   private String key;
131 
132   private FilterReply onHigherOrEqual = FilterReply.NEUTRAL;
133   private FilterReply onLower = FilterReply.DENY;
134 
135   /**
136    * Get the MDC key whose value will be used as a level threshold
137    * 
138    * @return the name of the MDC key.
139    */
140   public String getKey() {
141     return this.key;
142   }
143 
144   /**
145    * @see setKey
146    */
147   public void setKey(String key) {
148     this.key = key;
149   }
150 
151   /**
152    * Get the default threshold value when the MDC key is not set.
153    * 
154    * @return the default threshold value in the absence of a set MDC key
155    */
156   public Level getDefaultThreshold() {
157     return defaultThreshold;
158   }
159 
160   public void setDefaultThreshold(Level defaultThreshold) {
161     this.defaultThreshold = defaultThreshold;
162   }
163 
164   /**
165    * Get the FilterReply when the effective level is higher or equal to the
166    * level of current logging request
167    * 
168    * @return FilterReply
169    */
170   public FilterReply getOnHigherOrEqual() {
171     return onHigherOrEqual;
172   }
173 
174   public void setOnHigherOrEqual(FilterReply onHigherOrEqual) {
175     this.onHigherOrEqual = onHigherOrEqual;
176   }
177 
178   /**
179    * Get the FilterReply when the effective level is lower than the level of
180    * current logging request
181    * 
182    * @return FilterReply
183    */
184   public FilterReply getOnLower() {
185     return onLower;
186   }
187 
188   public void setOnLower(FilterReply onLower) {
189     this.onLower = onLower;
190   }
191 
192   /**
193    * Add a new MDCValuePair
194    */
195   public void addMDCValueLevelPair(MDCValueLevelPair mdcValueLevelPair) {
196     if (valueLevelMap.containsKey(mdcValueLevelPair.getValue())) {
197       addError(mdcValueLevelPair.getValue() + " has been already set");
198     } else {
199       valueLevelMap.put(mdcValueLevelPair.getValue(), mdcValueLevelPair
200           .getLevel());
201     }
202   }
203 
204   /**
205    * 
206    */
207   @Override
208   public void start() {
209     if (this.key == null) {
210       addError("No key name was specified");
211     }
212     super.start();
213   }
214 
215   /**
216    * This method first finds the MDC value for 'key'. It then finds the level
217    * threshold associated with this MDC value from the list of MDCValueLevelPair
218    * passed to this filter. This value is stored in a variable called
219    * 'levelAssociatedWithMDCValue'. If it null, then it is set to the
220    * 
221    * @{link #defaultThreshold} value.
222    * 
223    * If no such value exists, then
224    * 
225    * 
226    * @param marker
227    * @param logger
228    * @param level
229    * @param s
230    * @param objects
231    * @param throwable
232    * 
233    * @return FilterReply - this filter's decision
234    */
235   @Override
236   public FilterReply decide(Marker marker, Logger logger, Level level,
237       String s, Object[] objects, Throwable throwable) {
238 
239     String mdcValue = MDC.get(this.key);
240     if (!isStarted()) {
241       return FilterReply.NEUTRAL;
242     }
243 
244     Level levelAssociatedWithMDCValue = null;
245     if (mdcValue != null) {
246       levelAssociatedWithMDCValue = valueLevelMap.get(mdcValue);
247     }
248     if (levelAssociatedWithMDCValue == null) {
249       levelAssociatedWithMDCValue = defaultThreshold;
250     }
251     if (level.isGreaterOrEqual(levelAssociatedWithMDCValue)) {
252       return onHigherOrEqual;
253     } else {
254       return onLower;
255     }
256   }
257 }