
Author: ceki Date: Fri Jul 18 15:53:00 2008 New Revision: 1712 Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/turbo/DynamicThresholdFilter.java logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/turbo/MDCValueLevelPair.java logback/trunk/logback-classic/src/test/input/joran/turboDynamicThreshold.xml logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/joran/XConfiguratorTestX.java Modified: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/Level.java Log: This patch is related to LBCLASSIC-53 - This is a modified and hopefully improved version of Raph Goer's patch. MDCLevelFilter was renamed as DynamicThresholdFilter. In particular, the syntax for configuring a DynamicThresholdFilter is much shorter and convenient than previously. Here is an example:. <turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter"> <Key>userId</Key> <DefaultThreshold>ERROR</DefaultThreshold> <MDCValueLevelPair> <value>user1</value> <level>INFO</level> </MDCValueLevelPair> <MDCValueLevelPair> <value>user2</value> <level>TRACE</level> </MDCValueLevelPair> </turboFilter> See also turboDynamicThreshold.xml. However, I am not 100% satisfied with the semantics of DynamicThresholdFilter. Expect it to change according to feedback received on the mailing lists. Modified: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/Level.java ============================================================================== --- logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/Level.java (original) +++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/Level.java Fri Jul 18 15:53:00 2008 @@ -144,6 +144,17 @@ return toLevel(sArg, Level.DEBUG); } + + /** + * This method exists in order to comply with Joran's valueOf convention. + * @param sArg + * @return + */ + public static Level valueOf(String sArg) { + return toLevel(sArg, Level.DEBUG); + } + + /** * Convert an integer passed as argument to a Level. If the conversion fails, * then this method returns {@link #DEBUG}. Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/turbo/DynamicThresholdFilter.java ============================================================================== --- (empty file) +++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/turbo/DynamicThresholdFilter.java Fri Jul 18 15:53:00 2008 @@ -0,0 +1,129 @@ +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 will allow you to associate threshold levels to values found + * in the MDC. The threshold/value associations are looked up in MDC using + * a key. This key can be any value specified by the user. + * + * <p>TO BE DISCUSSED... + * + * <p>This provides very efficient course grained filtering based on things like a + * product name or a company name that would be associated with requests as they + * are being processed. + * + * The example configuration below illustrates how debug logging could be + * enabled for only individual users. + * + * <pre> + * <turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter"> + * <Key>userId</Key> + * <DefaultTheshold>ERROR</DefaultTheshold> + * <MDCValueLevelPair> + * <value>user1</value> + * <level>DEBUG</level> + * </MDCValueLevelPair> + * <MDCValueLevelPair> + * <value>user2</value> + * <level>TRACE</level> + * </MDCValueLevelPair> + * </turboFilter> + * </pre> + * + * @author Raplh Goers + * @author Ceki Gulcu + */ +public class DynamicThresholdFilter extends TurboFilter { + private Map<String, Level> valueLevelMap = new HashMap<String, Level>(); + private Level defaultThreshold = Level.ERROR; + private String key; + + /** + * The MDC key that will be filtered against + * + * @param key + * The name of the key. + */ + public void setKey(String key) { + this.key = key; + } + + /** + * + * @return The name of the key being filtered + */ + public String getKey() { + return this.key; + } + + public Level getDefaultThreshold() { + return defaultThreshold; + } + + public void setDefaultThreshold(Level defaultThreshold) { + this.defaultThreshold = defaultThreshold; + } + + /** + * 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(); + } + + /** + * + * @param marker + * @param logger + * @param level + * @param s + * @param objects + * @param throwable + * @return + */ + @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 FilterReply.NEUTRAL; + } else { + return FilterReply.DENY; + } + } +} Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/turbo/MDCValueLevelPair.java ============================================================================== --- (empty file) +++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/turbo/MDCValueLevelPair.java Fri Jul 18 15:53:00 2008 @@ -0,0 +1,31 @@ +package ch.qos.logback.classic.turbo; + +import ch.qos.logback.classic.Level; + + +/** + * Bean pairing an MDC value with a log level. + * + * @author Raplh Goers + * @author Ceki Gulcu + */ +public class MDCValueLevelPair { + private String value; + private Level level; + + public String getValue() { + return value; + } + + public void setValue(String name) { + this.value = name; + } + + public Level getLevel() { + return level; + } + + public void setLevel(Level level) { + this.level = level; + } +} Added: logback/trunk/logback-classic/src/test/input/joran/turboDynamicThreshold.xml ============================================================================== --- (empty file) +++ logback/trunk/logback-classic/src/test/input/joran/turboDynamicThreshold.xml Fri Jul 18 15:53:00 2008 @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE configuration> + +<configuration> + + <turboFilter class="ch.qos.logback.classic.turbo.DynamicThresholdFilter"> + <Key>userId</Key> + <DefaultThreshold>ERROR</DefaultThreshold> + <MDCValueLevelPair> + <value>user1</value> + <level>INFO</level> + </MDCValueLevelPair> + <MDCValueLevelPair> + <value>user2</value> + <level>TRACE</level> + </MDCValueLevelPair> + + </turboFilter> + + + <appender name="LIST" + class="ch.qos.logback.core.read.ListAppender"> + </appender> + + <root> + <level value="DEBUG" /> + <appender-ref ref="LIST" /> + </root> +</configuration> Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/joran/XConfiguratorTestX.java ============================================================================== --- (empty file) +++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/joran/XConfiguratorTestX.java Fri Jul 18 15:53:00 2008 @@ -0,0 +1,62 @@ +/** + * LOGBack: the generic, reliable, fast and flexible logging framework. + * + * Copyright (C) 1999-2006, QOS.ch + * + * This library is free software, you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation. + */ +package ch.qos.logback.classic.joran; + +import junit.framework.TestCase; + +import org.slf4j.MDC; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.LoggingEvent; +import ch.qos.logback.classic.util.TeztConstants; +import ch.qos.logback.core.joran.spi.JoranException; +import ch.qos.logback.core.read.ListAppender; +import ch.qos.logback.core.util.StatusPrinter; + +public class XConfiguratorTestX extends TestCase { + + public XConfiguratorTestX(String name) { + super(name); + } + + public void testSimpleList() throws JoranException { + JoranConfigurator jc = new JoranConfigurator(); + LoggerContext loggerContext = new LoggerContext(); + jc.setContext(loggerContext); + jc.doConfigure(TeztConstants.TEST_DIR_PREFIX + "input/joran/turboDynamicThreshold.xml"); + + StatusPrinter.print(loggerContext.getStatusManager()); + + Logger logger = loggerContext.getLogger(this.getClass().getName()); + Logger root = loggerContext.getLogger(LoggerContext.ROOT_NAME); + ListAppender listAppender = (ListAppender) root.getAppender("LIST"); + assertEquals(0, listAppender.list.size()); + + // this one should be denied + MDC.put("userId", "user1"); + logger.debug("hello user1"); + // this one should log + MDC.put("userId", "user2"); + logger.debug("hello user2"); + + assertEquals(1, listAppender.list.size()); + LoggingEvent le = (LoggingEvent) listAppender.list.get(0); + assertEquals("hello user2", le.getMessage()); + } + + +// public static Test suite() { +// TestSuite suite = new TestSuite(); +// suite.addTestSuite(XConfiguratorTest.class); +// //suite.addTest(new JoranConfiguratorTest("testEvaluatorFilter")); +// return suite; +// } +}