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