/** * Logback: the reliable, generic, fast and flexible logging framework. * Copyright (C) 1999-2015, QOS.ch. All rights reserved. * * This program and the accompanying materials are dual-licensed under * either the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation * * or (per the licensee's choosing) * * under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation. */ package ch.qos.logback.classic.turbo; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.Level; import ch.qos.logback.core.spi.FilterReply; import org.slf4j.Marker; import org.slf4j.MDC; import java.util.Map; import java.util.HashMap; /** * This filter allows for efficient course grained filtering based on criteria * such as product name or company name that would be associated with requests * as they are processed. * * <p> This filter will allow you to associate threshold levels to a key put in * the MDC. This key can be any value specified by the user. Furthermore, you * can pass MDC value and level threshold associations, which are then looked up * to find the level threshold to apply to the current logging request. If no * level threshold could be found, then a 'default' value specified by the user * is applied. We call this value 'levelAssociatedWithMDCValue'. * * <p> If 'levelAssociatedWithMDCValue' is higher or equal to the level of the * current logger request, the * {@link #decide(Marker, Logger, Level, String, Object[], Throwable) decide()} * method returns the value of {@link #getOnHigherOrEqual() onHigherOrEqual}, * if it is lower then the value of {@link #getOnLower() onLower} is returned. * Both 'onHigherOrEqual' and 'onLower' can be set by the user. By default, * 'onHigherOrEqual' is set to NEUTRAL and 'onLower' is set to DENY. Thus, if * the current logger request's level is lower than * 'levelAssociatedWithMDCValue', then the request is denied, and if it is * higher or equal, then this filter decides NEUTRAL letting subsequent filters * to make the decision on the fate of the logging request. * * <p> The example below illustrates how logging could be enabled for only * individual users. In this example all events for logger names matching * "com.mycompany" will be logged if they are for 'user1' and at a level higher * than equals to DEBUG, and for 'user2' if they are at a level higher than or * equal to TRACE, and for other users only if they are at level ERROR or * higher. Events issued by loggers other than "com.mycompany" will only be * logged if they are at level ERROR or higher since that is all the root logger * allows. * * <pre> * <configuration> * <appender name="STDOUT" * class="ch.qos.logback.core.ConsoleAppender"> * <layout class="ch.qos.logback.classic.PatternLayout"> * <Pattern>TEST %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern> * </layout> * </appender> * * <turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter"> * <Key>userId</Key> * <DefaultThreshold>ERROR</DefaultThreshold> * <MDCValueLevelPair> * <value>user1</value> * <level>DEBUG</level> * </MDCValueLevelPair> * <MDCValueLevelPair> * <value>user2</value> * <level>TRACE</level> * </MDCValueLevelPair> * </turboFilter> * * <logger name="com.mycompany" level="TRACE"/> * * <root level="ERROR" > * <appender-ref ref="STDOUT" /> * </root> * </configuration> * </pre> * * In the next configuration events from user1 and user2 will be logged * regardless of the logger levels. Events for other users and records without a * userid in the MDC will be logged if they are ERROR level messages. With this * configuration, the root level is never checked since DynamicThresholdFilter * will either accept or deny all records. * * <pre> * <configuration> * <appender name="STDOUT" * class="ch.qos.logback.core.ConsoleAppender"> * <layout class="ch.qos.logback.classic.PatternLayout"> * <Pattern>TEST %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern> * </layout> * </appender> * * <turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter"> * <Key>userId</Key> * <DefaultThreshold>ERROR</DefaultThreshold> * <OnHigherOrEqual>ACCEPT</OnHigherOrEqual> * <OnLower>DENY</OnLower> * <MDCValueLevelPair> * <value>user1</value> * <level>TRACE</level> * </MDCValueLevelPair> * <MDCValueLevelPair> * <value>user2</value> * <level>TRACE</level> * </MDCValueLevelPair> * </turboFilter> * * <root level="DEBUG" > * <appender-ref ref="STDOUT" /> * </root> * </configuration> * </pre> * * @author Ralph Goers * @author Ceki Gülcü */ public class DynamicThresholdFilter extends TurboFilter { private Map<String, Level> valueLevelMap = new HashMap<String, Level>(); private Level defaultThreshold = Level.ERROR; private String key; private FilterReply onHigherOrEqual = FilterReply.NEUTRAL; private FilterReply onLower = FilterReply.DENY; /** * Get the MDC key whose value will be used as a level threshold * * @return the name of the MDC key. */ public String getKey() { return this.key; } /** * @see setKey */ public void setKey(String key) { this.key = key; } /** * Get the default threshold value when the MDC key is not set. * * @return the default threshold value in the absence of a set MDC key */ public Level getDefaultThreshold() { return defaultThreshold; } public void setDefaultThreshold(Level defaultThreshold) { this.defaultThreshold = defaultThreshold; } /** * Get the FilterReply when the effective level is higher or equal to the * level of current logging request * * @return FilterReply */ public FilterReply getOnHigherOrEqual() { return onHigherOrEqual; } public void setOnHigherOrEqual(FilterReply onHigherOrEqual) { this.onHigherOrEqual = onHigherOrEqual; } /** * Get the FilterReply when the effective level is lower than the level of * current logging request * * @return FilterReply */ public FilterReply getOnLower() { return onLower; } public void setOnLower(FilterReply onLower) { this.onLower = onLower; } /** * Add a new MDCValuePair */ public void addMDCValueLevelPair(MDCValueLevelPair mdcValueLevelPair) { if (valueLevelMap.containsKey(mdcValueLevelPair.getValue())) { addError(mdcValueLevelPair.getValue() + " has been already set"); } else { valueLevelMap.put(mdcValueLevelPair.getValue(), mdcValueLevelPair.getLevel()); } } /** * */ @Override public void start() { if (this.key == null) { addError("No key name was specified"); } super.start(); } /** * This method first finds the MDC value for 'key'. It then finds the level * threshold associated with this MDC value from the list of MDCValueLevelPair * passed to this filter. This value is stored in a variable called * 'levelAssociatedWithMDCValue'. If it null, then it is set to the * * @{link #defaultThreshold} value. * * If no such value exists, then * * * @param marker * @param logger * @param level * @param s * @param objects * @param throwable * * @return FilterReply - this filter's decision */ @Override public FilterReply decide(Marker marker, Logger logger, Level level, String s, Object[] objects, Throwable throwable) { String mdcValue = MDC.get(this.key); if (!isStarted()) { return FilterReply.NEUTRAL; } Level levelAssociatedWithMDCValue = null; if (mdcValue != null) { levelAssociatedWithMDCValue = valueLevelMap.get(mdcValue); } if (levelAssociatedWithMDCValue == null) { levelAssociatedWithMDCValue = defaultThreshold; } if (level.isGreaterOrEqual(levelAssociatedWithMDCValue)) { return onHigherOrEqual; } else { return onLower; } } }