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 * <configuration> 063 * <appender name="STDOUT" 064 * class="ch.qos.logback.core.ConsoleAppender"> 065 * <layout class="ch.qos.logback.classic.PatternLayout"> 066 * <Pattern>TEST %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern> 067 * </layout> 068 * </appender> 069 * 070 * <turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter"> 071 * <Key>userId</Key> 072 * <DefaultThreshold>ERROR</DefaultThreshold> 073 * <MDCValueLevelPair> 074 * <value>user1</value> 075 * <level>DEBUG</level> 076 * </MDCValueLevelPair> 077 * <MDCValueLevelPair> 078 * <value>user2</value> 079 * <level>TRACE</level> 080 * </MDCValueLevelPair> 081 * </turboFilter> 082 * 083 * <logger name="com.mycompany" level="TRACE"/> 084 * 085 * <root level="ERROR" > 086 * <appender-ref ref="STDOUT" /> 087 * </root> 088 * </configuration> 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 * <configuration> 099 * <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 */ 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}