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> 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 Ralph 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.getLevel());
200         }
201     }
202 
203     /**
204      * 
205      */
206     @Override
207     public void start() {
208         if (this.key == null) {
209             addError("No key name was specified");
210         }
211         super.start();
212     }
213 
214     /**
215      * This method first finds the MDC value for 'key'. It then finds the level
216      * threshold associated with this MDC value from the list of MDCValueLevelPair
217      * passed to this filter. This value is stored in a variable called
218      * 'levelAssociatedWithMDCValue'. If it null, then it is set to the
219      * 
220      * @{link #defaultThreshold} value.
221      * 
222      * If no such value exists, then
223      * 
224      * 
225      * @param marker
226      * @param logger
227      * @param level
228      * @param s
229      * @param objects
230      * @param throwable
231      * 
232      * @return FilterReply - this filter's decision
233      */
234     @Override
235     public FilterReply decide(Marker marker, Logger logger, Level level, String s, Object[] objects, Throwable throwable) {
236 
237         String mdcValue = MDC.get(this.key);
238         if (!isStarted()) {
239             return FilterReply.NEUTRAL;
240         }
241 
242         Level levelAssociatedWithMDCValue = null;
243         if (mdcValue != null) {
244             levelAssociatedWithMDCValue = valueLevelMap.get(mdcValue);
245         }
246         if (levelAssociatedWithMDCValue == null) {
247             levelAssociatedWithMDCValue = defaultThreshold;
248         }
249         if (level.isGreaterOrEqual(levelAssociatedWithMDCValue)) {
250             return onHigherOrEqual;
251         } else {
252             return onLower;
253         }
254     }
255 }