001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic.turbo;
015
016import ch.qos.logback.classic.Logger;
017import ch.qos.logback.classic.Level;
018import ch.qos.logback.core.spi.FilterReply;
019import org.slf4j.Marker;
020import org.slf4j.MDC;
021
022import java.util.Map;
023import java.util.HashMap;
024
025/**
026 * This filter allows for efficient course grained filtering based on criteria
027 * such as product name or company name that would be associated with requests
028 * as they are processed.
029 * 
030 * <p> This filter will allow you to associate threshold levels to a key put in
031 * the MDC. This key can be any value specified by the user. Furthermore, you
032 * can pass MDC value and level threshold associations, which are then looked up
033 * to find the level threshold to apply to the current logging request. If no
034 * level threshold could be found, then a 'default' value specified by the user
035 * is applied. We call this value 'levelAssociatedWithMDCValue'.
036 * 
037 * <p> If 'levelAssociatedWithMDCValue' is higher or equal to the level of the
038 * current logger request, the
039 * {@link #decide(Marker, Logger, Level, String, Object[], Throwable) decide()}
040 * method returns the value of {@link #getOnHigherOrEqual() onHigherOrEqual},
041 * if it is lower then the value of {@link #getOnLower() onLower} is returned.
042 * Both 'onHigherOrEqual' and 'onLower' can be set by the user. By default,
043 * 'onHigherOrEqual' is set to NEUTRAL and 'onLower' is set to DENY. Thus, if
044 * the current logger request's level is lower than
045 * 'levelAssociatedWithMDCValue', then the request is denied, and if it is
046 * higher or equal, then this filter decides NEUTRAL letting subsequent filters
047 * to make the decision on the fate of the logging request.
048 * 
049 * <p> The example below illustrates how logging could be enabled for only
050 * individual users. In this example all events for logger names matching
051 * "com.mycompany" will be logged if they are for 'user1' and at a level higher
052 * than equals to DEBUG, and for 'user2' if they are at a level higher than or
053 * equal to TRACE, and for other users only if they are at level ERROR or
054 * higher. Events issued by loggers other than "com.mycompany" will only be
055 * logged if they are at level ERROR or higher since that is all the root logger
056 * allows.
057 * 
058 * <pre>
059 * &lt;configuration&gt;
060 *   &lt;appender name="STDOUT"
061 *             class="ch.qos.logback.core.ConsoleAppender"&gt;
062 *     &lt;layout class="ch.qos.logback.classic.PatternLayout"&gt;
063 *       &lt;Pattern>TEST %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n&lt;/Pattern>
064 *     &lt;/layout&gt;
065 *   &lt;/appender&gt;
066 *   
067 *   &lt;turboFilter class=&quot;ch.qos.logback.classic.turbo.DynamicThresholdFilter&quot;&gt;
068 *     &lt;Key&gt;userId&lt;/Key&gt;
069 *     &lt;DefaultThreshold&gt;ERROR&lt;/DefaultThreshold&gt;
070 *     &lt;MDCValueLevelPair&gt;
071 *       &lt;value&gt;user1&lt;/value&gt;
072 *       &lt;level&gt;DEBUG&lt;/level&gt;
073 *     &lt;/MDCValueLevelPair&gt;
074 *     &lt;MDCValueLevelPair&gt;
075 *       &lt;value&gt;user2&lt;/value&gt;
076 *       &lt;level&gt;TRACE&lt;/level&gt;
077 *     &lt;/MDCValueLevelPair&gt;
078 *   &lt;/turboFilter&gt;
079 *   
080 *   &lt;logger name="com.mycompany" level="TRACE"/&gt;
081 *   
082 *   &lt;root level="ERROR" &gt;
083 *     &lt;appender-ref ref="STDOUT" /&gt;
084 *   &lt;/root&gt;
085 * &lt;/configuration&gt;
086 * </pre>
087 * 
088 * In the next configuration events from user1 and user2 will be logged
089 * regardless of the logger levels. Events for other users and records without a
090 * userid in the MDC will be logged if they are ERROR level messages. With this
091 * configuration, the root level is never checked since DynamicThresholdFilter
092 * will either accept or deny all records.
093 * 
094 * <pre>
095 * &lt;configuration&gt;
096 *   &lt;appender name="STDOUT"
097 *             class="ch.qos.logback.core.ConsoleAppender"&gt;
098 *     &lt;layout class="ch.qos.logback.classic.PatternLayout"&gt;
099 *        &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 */
127public 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}