1 package ch.qos.logback.classic.turbo; 2 3 import ch.qos.logback.classic.Logger; 4 import ch.qos.logback.classic.Level; 5 import ch.qos.logback.core.spi.FilterReply; 6 import org.slf4j.Marker; 7 import org.slf4j.MDC; 8 9 import java.util.Map; 10 import java.util.HashMap; 11 12 /** 13 * This filter allows for efficient course grained filtering based on criteria 14 * such as product name or company name that would be associated with requests 15 * as they are processed. 16 * 17 * <p> This filter will allow you to associate threshold levels to a key put in 18 * the MDC. This key can be any value specified by the user. Furthermore, you 19 * can pass MDC value and level threshold associations, which are then looked up 20 * to find the level threshold to apply to the current logging request. If no 21 * level threshold could be found, then a 'default' value specified by the user 22 * is applied. We call this value 'levelAssociatedWithMDCValue'. 23 * 24 * <p> If 'levelAssociatedWithMDCValue' is higher or equal to the level of the 25 * current logger request, the 26 * {@link #decide(Marker, Logger, Level, String, Object[], Throwable) decide()} 27 * method returns the value of {@link #getOnHigherOrEqual() onHigherOrEqual}, 28 * if it is lower then the value of {@link #getOnLower() onLower} is returned. 29 * Both 'onHigherOrEqual' and 'onLower' can be set by the user. By default, 30 * 'onHigherOrEqual' is set to NEUTRAL and 'onLower' is set to DENY. Thus, if 31 * the current logger request's level is lower than 32 * 'levelAssociatedWithMDCValue', then the request is denied, and if it is 33 * higher or equal, then this filter decides NEUTRAL letting subsequent filters 34 * to make the decision on the fate of the logging request. 35 * 36 * <p> The example below illustrates how logging could be enabled for only 37 * individual users. In this example all events for logger names matching 38 * "com.mycompany" will be logged if they are for 'user1' and at a level higher 39 * than equals to DEBUG, and for 'user2' if they are at a level higher than or 40 * equal to TRACE, and for other users only if they are at level ERROR or 41 * higher. Events issued by loggers other than "com.mycompany" will only be 42 * logged if they are at level ERROR or higher since that is all the root logger 43 * allows. 44 * 45 * <pre> 46 * <configuration> 47 * <appender name="STDOUT" 48 * class="ch.qos.logback.core.ConsoleAppender"> 49 * <layout class="ch.qos.logback.classic.PatternLayout"> 50 * <Pattern>TEST %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern> 51 * </layout> 52 * </appender> 53 * 54 * <turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter"> 55 * <Key>userId</Key> 56 * <DefaultTheshold>ERROR</DefaultTheshold> 57 * <MDCValueLevelPair> 58 * <value>user1</value> 59 * <level>DEBUG</level> 60 * </MDCValueLevelPair> 61 * <MDCValueLevelPair> 62 * <value>user2</value> 63 * <level>TRACE</level> 64 * </MDCValueLevelPair> 65 * </turboFilter> 66 * 67 * <logger name="com.mycompany" level="TRACE"/> 68 * 69 * <root level="ERROR" > 70 * <appender-ref ref="STDOUT" /> 71 * </root> 72 * </configuration> 73 * </pre> 74 * 75 * In the next configuration events from user1 and user2 will be logged 76 * regardless of the logger levels. Events for other users and records without a 77 * userid in the MDC will be logged if they are ERROR level messages. With this 78 * configuration, the root level is never checked since DynamicThresholdFilter 79 * will either accept or deny all records. 80 * 81 * <pre> 82 * <configuration> 83 * <appender name="STDOUT" 84 * class="ch.qos.logback.core.ConsoleAppender"> 85 * <layout class="ch.qos.logback.classic.PatternLayout"> 86 * <Pattern>TEST %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern> 87 * </layout> 88 * </appender> 89 * 90 * <turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter"> 91 * <Key>userId</Key> 92 * <DefaultTheshold>ERROR</DefaultTheshold> 93 * <OnHigherOrEqual>ACCEPT</OnHigherOrEqual> 94 * <OnLower>DENY</OnLower> 95 * <MDCValueLevelPair> 96 * <value>user1</value> 97 * <level>TRACE</level> 98 * </MDCValueLevelPair> 99 * <MDCValueLevelPair> 100 * <value>user2</value> 101 * <level>TRACE</level> 102 * </MDCValueLevelPair> 103 * </turboFilter> 104 * 105 * <root level="DEBUG" > 106 * <appender-ref ref="STDOUT" /> 107 * </root> 108 * </configuration> 109 * </pre> 110 * 111 * @author Raplh Goers 112 * @author Ceki Gülcü 113 */ 114 public class DynamicThresholdFilter extends TurboFilter { 115 private Map<String, Level> valueLevelMap = new HashMap<String, Level>(); 116 private Level defaultThreshold = Level.ERROR; 117 private String key; 118 119 private FilterReply onHigherOrEqual = FilterReply.NEUTRAL; 120 private FilterReply onLower = FilterReply.DENY; 121 122 /** 123 * Get the MDC key whose value will be used as a level threshold 124 * 125 * @return the name of the MDC key. 126 */ 127 public String getKey() { 128 return this.key; 129 } 130 131 /** 132 * @see setKey 133 */ 134 public void setKey(String key) { 135 this.key = key; 136 } 137 138 /** 139 * Get the default threshold value when the MDC key is not set. 140 * 141 * @return the default threshold value in the absence of a set MDC key 142 */ 143 public Level getDefaultThreshold() { 144 return defaultThreshold; 145 } 146 147 public void setDefaultThreshold(Level defaultThreshold) { 148 this.defaultThreshold = defaultThreshold; 149 } 150 151 /** 152 * Get the FilterReply when the effective level is higher or equal to the 153 * level of current logging request 154 * 155 * @return FilterReply 156 */ 157 public FilterReply getOnHigherOrEqual() { 158 return onHigherOrEqual; 159 } 160 161 public void setOnHigherOrEqual(FilterReply onHigherOrEqual) { 162 this.onHigherOrEqual = onHigherOrEqual; 163 } 164 165 /** 166 * Get the FilterReply when the effective level is lower than the level of 167 * current logging request 168 * 169 * @return FilterReply 170 */ 171 public FilterReply getOnLower() { 172 return onLower; 173 } 174 175 public void setOnLower(FilterReply onLower) { 176 this.onLower = onLower; 177 } 178 179 /** 180 * Add a new MDCValuePair 181 */ 182 public void addMDCValueLevelPair(MDCValueLevelPair mdcValueLevelPair) { 183 if (valueLevelMap.containsKey(mdcValueLevelPair.getValue())) { 184 addError(mdcValueLevelPair.getValue() + " has been already set"); 185 } else { 186 valueLevelMap.put(mdcValueLevelPair.getValue(), mdcValueLevelPair 187 .getLevel()); 188 } 189 } 190 191 /** 192 * 193 */ 194 @Override 195 public void start() { 196 if (this.key == null) { 197 addError("No key name was specified"); 198 } 199 super.start(); 200 } 201 202 /** 203 * This method first finds the MDC value for 'key'. It then finds the level 204 * threshold associated with this MDC value from the list of MDCValueLevelPair 205 * passed to this filter. This value is stored in a variable called 206 * 'levelAssociatedWithMDCValue'. If it null, then it is set to the 207 * 208 * @{link #defaultThreshold} value. 209 * 210 * If no such value exists, then 211 * 212 * 213 * @param marker 214 * @param logger 215 * @param level 216 * @param s 217 * @param objects 218 * @param throwable 219 * 220 * @return FilterReply - this filter's decision 221 */ 222 @Override 223 public FilterReply decide(Marker marker, Logger logger, Level level, 224 String s, Object[] objects, Throwable throwable) { 225 226 String mdcValue = MDC.get(this.key); 227 if (!isStarted()) { 228 return FilterReply.NEUTRAL; 229 } 230 231 Level levelAssociatedWithMDCValue = null; 232 if (mdcValue != null) { 233 levelAssociatedWithMDCValue = valueLevelMap.get(mdcValue); 234 } 235 if (levelAssociatedWithMDCValue == null) { 236 levelAssociatedWithMDCValue = defaultThreshold; 237 } 238 if (level.isGreaterOrEqual(levelAssociatedWithMDCValue)) { 239 return onHigherOrEqual; 240 } else { 241 return onLower; 242 } 243 } 244 }