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 * <configuration>
60 * <appender name="STDOUT"
61 * class="ch.qos.logback.core.ConsoleAppender">
62 * <layout class="ch.qos.logback.classic.PatternLayout">
63 * <Pattern>TEST %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
64 * </layout>
65 * </appender>
66 *
67 * <turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter">
68 * <Key>userId</Key>
69 * <DefaultThreshold>ERROR</DefaultThreshold>
70 * <MDCValueLevelPair>
71 * <value>user1</value>
72 * <level>DEBUG</level>
73 * </MDCValueLevelPair>
74 * <MDCValueLevelPair>
75 * <value>user2</value>
76 * <level>TRACE</level>
77 * </MDCValueLevelPair>
78 * </turboFilter>
79 *
80 * <logger name="com.mycompany" level="TRACE"/>
81 *
82 * <root level="ERROR" >
83 * <appender-ref ref="STDOUT" />
84 * </root>
85 * </configuration>
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 * <configuration>
96 * <appender name="STDOUT"
97 * class="ch.qos.logback.core.ConsoleAppender">
98 * <layout class="ch.qos.logback.classic.PatternLayout">
99 * <Pattern>TEST %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
100 * </layout>
101 * </appender>
102 *
103 * <turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter">
104 * <Key>userId</Key>
105 * <DefaultThreshold>ERROR</DefaultThreshold>
106 * <OnHigherOrEqual>ACCEPT</OnHigherOrEqual>
107 * <OnLower>DENY</OnLower>
108 * <MDCValueLevelPair>
109 * <value>user1</value>
110 * <level>TRACE</level>
111 * </MDCValueLevelPair>
112 * <MDCValueLevelPair>
113 * <value>user2</value>
114 * <level>TRACE</level>
115 * </MDCValueLevelPair>
116 * </turboFilter>
117 *
118 * <root level="DEBUG" >
119 * <appender-ref ref="STDOUT" />
120 * </root>
121 * </configuration>
122 * </pre>
123 *
124 * @author Raplh Goers
125 * @author Ceki Gülcü
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 }