1 /** 2 * Logback: the reliable, generic, fast and flexible logging framework. 3 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 4 * 5 * This program and the accompanying materials are dual-licensed under 6 * either the terms of the Eclipse Public License v1.0 as published by 7 * the Eclipse Foundation 8 * 9 * or (per the licensee's choosing) 10 * 11 * under the terms of the GNU Lesser General Public License version 2.1 12 * as published by the Free Software Foundation. 13 */ 14 package ch.qos.logback.classic.turbo; 15 16 import ch.qos.logback.classic.Logger; 17 import ch.qos.logback.classic.Level; 18 import ch.qos.logback.core.spi.FilterReply; 19 import org.slf4j.Marker; 20 import org.slf4j.MDC; 21 22 import java.util.Map; 23 import java.util.HashMap; 24 25 /** 26 * This filter allows for efficient course grained filtering based on criteria 27 * such as product name or company name that would be associated with requests 28 * as they are processed. 29 * 30 * <p> 31 * This filter will allow you to associate threshold levels to a key put in the 32 * MDC. This key can be any value specified by the user. Furthermore, you can 33 * pass MDC value and level threshold associations, which are then looked up to 34 * find the level threshold to apply to the current logging request. If no level 35 * threshold could be found, then a 'default' value specified by the user is 36 * applied. We call this value 'levelAssociatedWithMDCValue'. 37 * 38 * <p> 39 * If 'levelAssociatedWithMDCValue' is higher or equal to the level of the 40 * current logger request, the 41 * {@link #decide(Marker, Logger, Level, String, Object[], Throwable) decide()} 42 * method returns the value of {@link #getOnHigherOrEqual() onHigherOrEqual}, if 43 * it is lower than the value of {@link #getOnLower() onLower} is returned. Both 44 * 'onHigherOrEqual' and 'onLower' can be set by the user. By default, 45 * 'onHigherOrEqual' is set to NEUTRAL and 'onLower' is set to DENY. Thus, if 46 * the current logger request's level is lower than 47 * 'levelAssociatedWithMDCValue', then the request is denied, and if it is 48 * higher or equal, then this filter decides NEUTRAL letting subsequent filters 49 * to make the decision on the fate of the logging request. 50 * 51 * <p> 52 * The example below illustrates how logging could be enabled for only 53 * individual users. In this example all events for logger names matching 54 * "com.mycompany" will be logged if they are for 'user1' and at a level higher 55 * than equals to DEBUG, and for 'user2' if they are at a level higher than or 56 * equal to TRACE, and for other users only if they are at level ERROR or 57 * higher. Events issued by loggers other than "com.mycompany" will only be 58 * logged if they are at level ERROR or higher since that is all the root logger 59 * allows. 60 * 61 * <pre> 62 * <configuration> 63 * <appender name="STDOUT" 64 * class="ch.qos.logback.core.ConsoleAppender"> 65 * <layout class="ch.qos.logback.classic.PatternLayout"> 66 * <Pattern>TEST %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern> 67 * </layout> 68 * </appender> 69 * 70 * <turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter"> 71 * <Key>userId</Key> 72 * <DefaultThreshold>ERROR</DefaultThreshold> 73 * <MDCValueLevelPair> 74 * <value>user1</value> 75 * <level>DEBUG</level> 76 * </MDCValueLevelPair> 77 * <MDCValueLevelPair> 78 * <value>user2</value> 79 * <level>TRACE</level> 80 * </MDCValueLevelPair> 81 * </turboFilter> 82 * 83 * <logger name="com.mycompany" level="TRACE"/> 84 * 85 * <root level="ERROR" > 86 * <appender-ref ref="STDOUT" /> 87 * </root> 88 * </configuration> 89 * </pre> 90 * 91 * In the next configuration events from user1 and user2 will be logged 92 * regardless of the logger levels. Events for other users and records without a 93 * userid in the MDC will be logged if they are ERROR level messages. With this 94 * configuration, the root level is never checked since DynamicThresholdFilter 95 * will either accept or deny all records. 96 * 97 * <pre> 98 * <configuration> 99 * <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 */ 130 public 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 }