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