View Javadoc

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