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>
031 * This filter will allow you to associate threshold levels to a key put in the
032 * MDC. This key can be any value specified by the user. Furthermore, you can
033 * pass MDC value and level threshold associations, which are then looked up to
034 * find the level threshold to apply to the current logging request. If no level
035 * threshold could be found, then a 'default' value specified by the user is
036 * applied. We call this value 'levelAssociatedWithMDCValue'.
037 * 
038 * <p>
039 * If 'levelAssociatedWithMDCValue' is higher or equal to the level of the
040 * current logger request, the
041 * {@link #decide(Marker, Logger, Level, String, Object[], Throwable) decide()}
042 * method returns the value of {@link #getOnHigherOrEqual() onHigherOrEqual}, if
043 * it is lower than the value of {@link #getOnLower() onLower} is returned. Both
044 * 'onHigherOrEqual' and 'onLower' can be set by the user. By default,
045 * 'onHigherOrEqual' is set to NEUTRAL and 'onLower' is set to DENY. Thus, if
046 * the current logger request's level is lower than
047 * 'levelAssociatedWithMDCValue', then the request is denied, and if it is
048 * higher or equal, then this filter decides NEUTRAL letting subsequent filters
049 * to make the decision on the fate of the logging request.
050 * 
051 * <p>
052 * The example below illustrates how logging could be enabled for only
053 * individual users. In this example all events for logger names matching
054 * "com.mycompany" will be logged if they are for 'user1' and at a level higher
055 * than equals to DEBUG, and for 'user2' if they are at a level higher than or
056 * equal to TRACE, and for other users only if they are at level ERROR or
057 * higher. Events issued by loggers other than "com.mycompany" will only be
058 * logged if they are at level ERROR or higher since that is all the root logger
059 * allows.
060 * 
061 * <pre>
062 * &lt;configuration&gt;
063 *   &lt;appender name="STDOUT"
064 *             class="ch.qos.logback.core.ConsoleAppender"&gt;
065 *     &lt;layout class="ch.qos.logback.classic.PatternLayout"&gt;
066 *       &lt;Pattern>TEST %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n&lt;/Pattern>
067 *     &lt;/layout&gt;
068 *   &lt;/appender&gt;
069 *   
070 *   &lt;turboFilter class=&quot;ch.qos.logback.classic.turbo.DynamicThresholdFilter&quot;&gt;
071 *     &lt;Key&gt;userId&lt;/Key&gt;
072 *     &lt;DefaultThreshold&gt;ERROR&lt;/DefaultThreshold&gt;
073 *     &lt;MDCValueLevelPair&gt;
074 *       &lt;value&gt;user1&lt;/value&gt;
075 *       &lt;level&gt;DEBUG&lt;/level&gt;
076 *     &lt;/MDCValueLevelPair&gt;
077 *     &lt;MDCValueLevelPair&gt;
078 *       &lt;value&gt;user2&lt;/value&gt;
079 *       &lt;level&gt;TRACE&lt;/level&gt;
080 *     &lt;/MDCValueLevelPair&gt;
081 *   &lt;/turboFilter&gt;
082 *   
083 *   &lt;logger name="com.mycompany" level="TRACE"/&gt;
084 *   
085 *   &lt;root level="ERROR" &gt;
086 *     &lt;appender-ref ref="STDOUT" /&gt;
087 *   &lt;/root&gt;
088 * &lt;/configuration&gt;
089 * </pre>
090 * 
091 * In the next configuration events from user1 and user2 will be logged
092 * regardless of the logger levels. Events for other users and records without a
093 * userid in the MDC will be logged if they are ERROR level messages. With this
094 * configuration, the root level is never checked since DynamicThresholdFilter
095 * will either accept or deny all records.
096 * 
097 * <pre>
098 * &lt;configuration&gt;
099 *   &lt;appender name="STDOUT"
100 *             class="ch.qos.logback.core.ConsoleAppender"&gt;
101 *     &lt;layout class="ch.qos.logback.classic.PatternLayout"&gt;
102 *        &lt;Pattern>TEST %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n&lt;/Pattern>
103 *     &lt;/layout&gt;
104 *   &lt;/appender&gt;
105 *   
106 *   &lt;turboFilter class=&quot;ch.qos.logback.classic.turbo.DynamicThresholdFilter&quot;&gt;
107 *     &lt;Key&gt;userId&lt;/Key&gt;
108 *     &lt;DefaultThreshold&gt;ERROR&lt;/DefaultThreshold&gt;
109 *     &lt;OnHigherOrEqual&gt;ACCEPT&lt;/OnHigherOrEqual&gt;
110 *     &lt;OnLower&gt;DENY&lt;/OnLower&gt;
111 *     &lt;MDCValueLevelPair&gt;
112 *       &lt;value&gt;user1&lt;/value&gt;
113 *       &lt;level&gt;TRACE&lt;/level&gt;
114 *     &lt;/MDCValueLevelPair&gt;
115 *     &lt;MDCValueLevelPair&gt;
116 *       &lt;value&gt;user2&lt;/value&gt;
117 *       &lt;level&gt;TRACE&lt;/level&gt;
118 *     &lt;/MDCValueLevelPair&gt;
119 *   &lt;/turboFilter&gt;
120 *   
121 *   &lt;root level="DEBUG" &gt;
122 *     &lt;appender-ref ref="STDOUT" /&gt;
123 *   &lt;/root&gt;
124 * &lt;/configuration&gt;
125 * </pre>
126 * 
127 * @author Ralph Goers
128 * @author Ceki G&uuml;lc&uuml;
129 */
130public 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}