svn commit: r2096 - in logback/trunk: logback-access/src/main/java/ch/qos/logback/access/sift logback-access/src/test/input/jetty logback-classic/src/main/java/ch/qos/logback/classic/sift logback-examples/src/main/java/chapter4/sift logback-site/src/site/pages/manual

Author: ceki Date: Tue Dec 23 19:17:20 2008 New Revision: 2096 Added: logback/trunk/logback-examples/src/main/java/chapter4/sift/ logback/trunk/logback-examples/src/main/java/chapter4/sift/SiftExample.java logback/trunk/logback-examples/src/main/java/chapter4/sift/byUserid.xml Modified: logback/trunk/logback-access/src/main/java/ch/qos/logback/access/sift/AccessEventDiscriminator.java logback/trunk/logback-access/src/test/input/jetty/siftingFile.xml logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/sift/MDCBasedDiscriminator.java logback/trunk/logback-site/src/site/pages/manual/appenders.html Log: Documenting SiftingAppender Modified: logback/trunk/logback-access/src/main/java/ch/qos/logback/access/sift/AccessEventDiscriminator.java ============================================================================== --- logback/trunk/logback-access/src/main/java/ch/qos/logback/access/sift/AccessEventDiscriminator.java (original) +++ logback/trunk/logback-access/src/main/java/ch/qos/logback/access/sift/AccessEventDiscriminator.java Tue Dec 23 19:17:20 2008 @@ -16,16 +16,27 @@ import ch.qos.logback.core.sift.Discriminator; import ch.qos.logback.core.spi.ContextAwareBase; +/** + * + * AccessEventDiscriminator's job is to return the value of a designated field in + * an {@link AccessEvent} instance. + * + * <p>The field is specified via the {@link FieldName} property. + + * @author Ceki Gülcü + * + */ public class AccessEventDiscriminator extends ContextAwareBase implements Discriminator<AccessEvent> { boolean started = false; /** - * Allowed field names are: COOKIE, REQUEST_ATTRIBUTE, SESSION_ATTRIBUTE, - * REMOTE_ADDRESS, LOCAL_PORT,REQUEST_URI + * At present time the followed fields can be designated: + * COOKIE, REQUEST_ATTRIBUTE, SESSION_ATTRIBUTE, REMOTE_ADDRESS, + * LOCAL_PORT,REQUEST_URI * - * <p> The first three field names require a key attribute. + * <p> The first three fields require an additional key. */ public enum FieldName { COOKIE, REQUEST_ATTRIBUTE, SESSION_ATTRIBUTE, REMOTE_ADDRESS, LOCAL_PORT, REQUEST_URI @@ -34,7 +45,7 @@ String defaultValue; String key; FieldName fieldName; - String optionalKey; + String additionalKey; public String getDiscriminatingValue(AccessEvent acccessEvent) { String rawValue = getRawDiscriminatingValue(acccessEvent); @@ -48,11 +59,11 @@ public String getRawDiscriminatingValue(AccessEvent acccessEvent) { switch (fieldName) { case COOKIE: - return acccessEvent.getCookie(optionalKey); + return acccessEvent.getCookie(additionalKey); case LOCAL_PORT: return String.valueOf(acccessEvent.getLocalPort()); case REQUEST_ATTRIBUTE: - return acccessEvent.getAttribute(optionalKey); + return acccessEvent.getAttribute(additionalKey); case SESSION_ATTRIBUTE: return getSessionAttribute(acccessEvent); case REMOTE_ADDRESS: @@ -78,7 +89,7 @@ if (req != null) { HttpSession session = req.getSession(false); if (session != null) { - Object v = session.getAttribute(optionalKey); + Object v = session.getAttribute(additionalKey); if (v != null) { return v.toString(); } @@ -107,7 +118,7 @@ case SESSION_ATTRIBUTE: case REQUEST_ATTRIBUTE: case COOKIE: - if (optionalKey == null) { + if (additionalKey == null) { addError("\"OptionalKey\" property is mandatory for field name "+fieldName.toString()); errorCount++; } @@ -130,15 +141,15 @@ return fieldName; } - public String getOptionalKey() { - return optionalKey; + + public String getAdditionalKey() { + return additionalKey; } - public void setOptionalKey(String optionalKey) { - this.optionalKey = optionalKey; + public void setAdditionalKey(String additionalKey) { + this.additionalKey = additionalKey; } - /** * @see #setDefaultValue(String) * @return Modified: logback/trunk/logback-access/src/test/input/jetty/siftingFile.xml ============================================================================== --- logback/trunk/logback-access/src/test/input/jetty/siftingFile.xml (original) +++ logback/trunk/logback-access/src/test/input/jetty/siftingFile.xml Tue Dec 23 19:17:20 2008 @@ -3,6 +3,7 @@ <appender name="SIFTING" class="ch.qos.logback.access.sift.SiftingAppender"> <KeyName>client</KeyName> <Discriminator class="ch.qos.logback.access.sift.AccessEventDiscriminator"> + <Key>client</Key> <DefaultValue>NA</DefaultValue> <FieldName>REQUEST_URI</FieldName> </Discriminator> Modified: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/sift/MDCBasedDiscriminator.java ============================================================================== --- logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/sift/MDCBasedDiscriminator.java (original) +++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/sift/MDCBasedDiscriminator.java Tue Dec 23 19:17:20 2008 @@ -16,16 +16,30 @@ import ch.qos.logback.core.spi.ContextAwareBase; import ch.qos.logback.core.util.OptionHelper; -public class MDCBasedDiscriminator extends ContextAwareBase implements Discriminator<LoggingEvent> { - - String key; - String defaultValue; - - boolean started = false; +/** + * MDCBasedDiscriminator essentially returns the value mapped to an MDC key. If + * the said value is null, then a default value is returned. + * + * <p>Both Key and the DefaultValue are user specified properties. + * + * @author Ceki Gülcü + * + */ +public class MDCBasedDiscriminator extends ContextAwareBase implements + Discriminator<LoggingEvent> { + + private String key; + private String defaultValue; + private boolean started = false; public MDCBasedDiscriminator() { } + /** + * Return the value associated with an MDC entry desginated by the Key + * property. If that value is null, then return the value assigned to the + * DefaultValue property. + */ public String getDiscriminatingValue(LoggingEvent event) { String mdcValue = MDC.get(key); if (mdcValue == null) { @@ -76,18 +90,15 @@ /** * The default MDC value in case the MDC is not set for - * {@link #setMdcKey(String) mdcKey}. + * {@link #setKey(String) mdcKey}. * - * <p> For example, if {@link #setMdcKey(String) mdcKey} is set to the value + * <p> For example, if {@link #setKey(String) Key} is set to the value * "someKey", and the MDC is not set for "someKey", then this appender will - * use the default value, which you can set with the help of method. + * use the default value, which you can set with the help of this method. * * @param defaultValue */ public void setDefaultValue(String defaultValue) { this.defaultValue = defaultValue; } - - - } Added: logback/trunk/logback-examples/src/main/java/chapter4/sift/SiftExample.java ============================================================================== --- (empty file) +++ logback/trunk/logback-examples/src/main/java/chapter4/sift/SiftExample.java Tue Dec 23 19:17:20 2008 @@ -0,0 +1,51 @@ +/** + * Logback: the generic, reliable, fast and flexible logging framework. + * + * Copyright (C) 2000-2008, 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 chapter4.sift; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; + +public class SiftExample { + + public static void main(String[] args) throws JoranException { + if (args.length != 1) { + usage("Wrong number of arguments."); + } + + String configFile = args[0]; + + LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); + JoranConfigurator configurator = new JoranConfigurator(); + lc.reset(); + configurator.setContext(lc); + configurator.doConfigure(configFile); + + + Logger logger = LoggerFactory.getLogger(SiftExample.class); + logger.debug("Application started"); + + MDC.put("userid", "Alice"); + logger.debug("Alice says hello"); + + //StatusPrinter.print(lc); + } + + static void usage(String msg) { + System.err.println(msg); + System.err.println("Usage: java " + SiftExample.class.getName() + + " configFile\n" + " configFile a logback configuration file"); + System.exit(1); + } +} Added: logback/trunk/logback-examples/src/main/java/chapter4/sift/byUserid.xml ============================================================================== --- (empty file) +++ logback/trunk/logback-examples/src/main/java/chapter4/sift/byUserid.xml Tue Dec 23 19:17:20 2008 @@ -0,0 +1,21 @@ +<configuration> + <appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender"> + <discriminator> + <Key>userid</Key> + <DefaultValue>unknown</DefaultValue> + </discriminator> + <sift> + <appender name="FILE-${userid}" class="ch.qos.logback.core.FileAppender"> + <File>${userid}.log</File>s + <Append>false</Append> + <layout class="ch.qos.logback.classic.PatternLayout"> + <Pattern>%d [%thread] %level %mdc %logger{35} - %msg%n</Pattern> + </layout> + </appender> + </sift> + </appender> + + <root level="DEBUG"> + <appender-ref ref="SIFT" /> + </root> +</configuration> Modified: logback/trunk/logback-site/src/site/pages/manual/appenders.html ============================================================================== --- logback/trunk/logback-site/src/site/pages/manual/appenders.html (original) +++ logback/trunk/logback-site/src/site/pages/manual/appenders.html Tue Dec 23 19:17:20 2008 @@ -3067,27 +3067,227 @@ deny requests coming via a network connection. </p> + + <h3><a name="SiftingAppender" + href="#SiftingAppender">SiftingAppender</a></h3> + + <p>As its name implies, a <code>SiftingAppender</code> can be used + to separate (or sift) logging according to some runtime attribute. + For example, <code>SiftingAppender</code> can separate logging + events into distinct log files, one file per user. + </p> + + + <p><code>SiftingAppender</code> embeds and manages multiple + appenders which it builds dynamically depending on discriminating + values. The built appender is specified in a configuration file + within the <code>SiftingAppender</code> definition itself. By + default, <code>SiftingAppender</code> uses MDC key/value pairs as + a discriminator. + </p> + + <p>After configuring logback, the <a + href="../xref/chapter4/sift/SiftExample.html">SiftExample</a> + application logs a message stating that the application has + started. It then sets the MDC key "userid" to "Alice" and logs a + message. Here is the salient code:</p> + + <p class="source">logger.debug("Application started"); +MDC.put("userid", "Alice"); +logger.debug("Alice says hello"); </p> + + <p>The next configuration file illustrates the use of + <code>SiftingAppender</code>.</p> + + + <em>Example 4.<span class="autoEx"/>: <code>SiftingAppender</code> + configuration + (logback-examples/src/main/java/chapter4/sift/byUserid.xml)</em> + + <p class="source"><configuration> + + <b><appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender"></b> + <!-- in the absence of the class attribute, it is assumed that the + desired discriminator type is + ch.qos.logback.classic.sift.MDCBasedDiscriminator --> + <b><discriminator></b> + <b><Key><span class="green">userid</span></Key></b> + <b><DefaultValue>unknown</DefaultValue></b> + <b></discriminator></b> + <b><sift></b> + <b><appender name="FILE-<span class="green">${userid}</span>" class="ch.qos.logback.core.FileAppender"></b> + <b><File><span class="green">${userid}</span>.log</File></b> + <b><Append>false</Append></b> + <b><layout class="ch.qos.logback.classic.PatternLayout"></b> + <b><Pattern>%d [%thread] %level %mdc %logger{35} - %msg%n</Pattern></b> + <b></layout></b> + <b></appender></b> + <b></sift></b> + </appender> + + <root level="DEBUG"> + <appender-ref ref="SIFT" /> + </root> +</configuration></p> + + + <p>In the absence of a class attribute, it is assumed that the + discriminator type is <a + href="xref/ch/qos/logback/classic/sift/MDCBasedDiscriminator.html">MDCBasedDiscriminator</a>. It + will use the MDC value associated with the <span + class="option">Key</span> property as a districimator. If that + value is null, then the value associated with the <span + class="option">DefaultValue</span> property will be used. + </p> + + <p>The <code>SiftingAppender</code> is unique in its capacity to + reference and configure nested appenders. In the above example, + within the <code>SiftingAppender</code> there will be nested + FileAppender instances, each instance identified by the value + associated with the "userid" MDC key. Whenever the "userid" MDC + key is assigned a new value, a new <code>FileAppender</code> + instance will be built from scratch. The SiftingAppender keeps + track of the appenders it creates. Appenders unused for 30 minutes + will be automatically closed and discarded. + </p> + + <p>It is not enough to have different appender instances, each + instance must output to a distinct target resource. To allow such + differentiation, within the nested appender (FileAppender above), + the key passed to the discriminator, "userid" in the above + example, becomes a <a + href="joran.html#variableSubstitution">variable</a>. Consequently, + this variable can be used to differentiate the actual resource + used by a given nested appender. + </p> + + <p>Running <code>SiftExample</code> application with the + "byUserid.xml" configuration file shown above, will result in two + distinct log files, "unknown.log" and "Alice.log". + </p> + + + <h3><a name="WriteYourOwnAppender" + href="#WriteYourOwnAppender">Writing your own Appender</a></h3> + + + <p>You can easily write your appender by sub-classing <code>AppenderBase</code>. + It handles support for filters, status among other functionality shared by most appenders. + The derived class only needs to implement one method, namely + <code>append(Object eventObject)</code>. + </p> + + <p>The <code>CountingConsoleAppender</code>, which we list next, appends a limited + number of incoming events on the console. It shuts down after the limit is reached. + It uses a <code>Layout</code> to format the events and accepts a parameter, + thus a few more methods are needed. + </p> + + <em>Example 4.<span class="autoExec"/>: <code>CountingConsoleAppender</code> (logback-examples/src/main/java/chapter4/CountingConsoleAppender.java)</em> + <p class="source">package chapter4; + +import ch.qos.logback.core.AppenderBase; +import ch.qos.logback.core.Layout; + + +public class CountingConsoleAppender extends AppenderBase<LoggingEvent> { + static int DEFAULT_LIMIT = 16; + int counter = 0; + int limit = DEFAULT_LIMIT; + + private Layout<LoggingEvent> layout; + + public CountingConsoleAppender() { + } + + public void setLimit(int limit) { + this.limit = limit; + } + + public int getLimit() { + return limit; + } + + @Override + public void start() { + if (this.layout == null) { + addError("No layout set for the appender named ["+ name +"]."); + return; + } + + super.start(); + } + + public void append(LoggingEvent event) { + + if (counter >= limit) { + return; + } + + // output the events as formatted by our layout + System.out.print(this.layout.doLayout(event)); + + // prepare for next event + counter++; + } + + public Layout<LoggingEvent> getLayout() { + return layout; + } + + public void setLayout(Layout<LoggingEvent> layout) { + this.layout = layout; + } +}</p> + + <p>The <code>start()</code> method checks for the presence of a + <code>Layout</code>. In case none is found, the appender is not + started. + </p> - <a name="Access"></a> - <h2>Logback Access</h2> + <p>This custom appender illustrates a two points: + </p> - <p>Most of the appenders found in logback classic can be used - within logback access. They function mostly in the same way as - their logback classic counterpart. In the next section, we will - cover their use, but will focuse on the differences with the - classic appenders. + <ul> + <li>All properties that follow the setter/getter JavaBeans + conventions are handled transparently. The <code>start()</code> + method, that is called automatically, has the responsability to + check that the given properties are coherent. + </li> + <li>The <code>AppenderBase.doAppend()</code> method invokes the + append() method of its derived classes where actual output + operations occur. It is in this method that appenders format + events by invoking their layouts. + </li> + </ul> + + <p>The <code>CountingConsoleAppender</code> can be configured like + any appender. See sample file + <em>logback-examples/src/main/java/chapter4/countingConsole.xml</em> + for an example. + </p> + + + <h2><a name="logback_access" href="#logback_access">Logback + Access</a></h2> + + <p>Most of the appenders found in logback-classic have their + equivalent in logback-access. These work essentialy in the same + way as their locback-classic counterparts. In the next section, we + will cover their use. </p> <a name="AccessSocketAppender"/> <h3>SocketAppender</h3> - <p> - The <a href="../xref/ch/qos/logback/access/net/SocketAppender.html"> - <code>SocketAppender</code></a> is designed to log to a - remote entity by transmitting serialized <code>AccessEvent</code> objects over the wire. - Remote logging is non-intrusive as far as the access event is concerned. - On the receiving end after de-serialization, the event can be logged as - if it were generated locally. + <p>The <a + href="../xref/ch/qos/logback/access/net/SocketAppender.html"> + <code>SocketAppender</code></a> is designed to log to a remote + entity by transmitting serialized <code>AccessEvent</code> objects + over the wire. Remote logging is non-intrusive as far as the + access event is concerned. On the receiving end after + de-serialization, the event can be logged as if it were generated + locally. </p> <p> The properties of access' <code>SocketAppender</code> are the same as those available @@ -3280,7 +3480,7 @@ </p> <em>Example 4.<span class="autoEx"/>: DBAppender configuration (logback-examples/src/main/java/chapter4/conf/access/logback-DB.xml)</em> -<div class="source"><pre><configuration> + <p class="source"><configuration> <appender name="DB" class="ch.qos.logback.access.db.DBAppender"> <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource"> @@ -3293,110 +3493,32 @@ </appender> <appender-ref ref="DB" /> -</configuration></pre></div> - - - <a name="WriteYourOwnAppender"></a> - <h2>Writing your own Appender</h2> +</configuration></p> - <p>You can easily write your appender by sub-classing <code>AppenderBase</code>. - It handles support for filters, status among other functionality shared by most appenders. - The derived class only needs to implement one method, namely - <code>append(Object eventObject)</code>. + <h3><a name="AccessSiftingAppender" + href="#AccessSiftingAppender">SiftingAppender</a></h3> + + <p>The SiftingAppender in logback-access is quite similar to its + logbacl-classic counterpart. The main difference is that in + logback-access the default discriminator, namely <a + href="../xref/ch/qos/logback/access/sift/AccessEventDiscriminator.html">AccessEventDiscriminator</a>, + is not MDC based. As its name suggests, AccessEventDiscriminator, + uses a designated field in AccessEvent as basis for selecting a + nested appender. If the value of the designated field is null, + then the value specified in the <span + class="option">DefaultValue</span> property is used. </p> - <p>The <code>CountingConsoleAppender</code>, which we list next, appends a limited - number of incoming events on the console. It shuts down after the limit is reached. - It uses a <code>Layout</code> to format the events and accepts a parameter, - thus a few more methods are needed. - </p> + <p>The desginated AccessEvent field can be one of COOKIE, + REQUEST_ATTRIBUTE, SESSION_ATTRIBUTE, REMOTE_ADDRESS, LOCAL_PORT, + REQUEST_URI. Note that the fiest three fields require that the + <span class="option">AdditionalKey</span> property to be + specified.</p> - <em>Example 4.<span class="autoExec"/>: <code>CountingConsoleAppender</code> (logback-examples/src/main/java/chapter4/CountingConsoleAppender.java)</em> - <p class="source">package chapter4; -import ch.qos.logback.core.AppenderBase; -import ch.qos.logback.core.Layout; -public class CountingConsoleAppender extends AppenderBase<LoggingEvent> { - static int DEFAULT_LIMIT = 16; - int counter = 0; - int limit = DEFAULT_LIMIT; - - private Layout<LoggingEvent> layout; - - public CountingConsoleAppender() { - } - - public void setLimit(int limit) { - this.limit = limit; - } - - public int getLimit() { - return limit; - } - - @Override - public void start() { - if (this.layout == null) { - addError("No layout set for the appender named ["+ name +"]."); - return; - } - - super.start(); - } - - public void append(LoggingEvent event) { - - if (counter >= limit) { - return; - } - - // output the events as formatted by our layout - System.out.print(this.layout.doLayout(event)); - - // prepare for next event - counter++; - } - - public Layout<LoggingEvent> getLayout() { - return layout; - } - - public void setLayout(Layout<LoggingEvent> layout) { - this.layout = layout; - } -}</p> - - <p>The <code>start()</code> method checks for the presence of a - <code>Layout</code>. In case none is found, the appender is not - started. - </p> - - <p>This custom appender illustrates a two points: - </p> - - <ul> - <li>All properties that follow the setter/getter JavaBeans - conventions are handled transparently. The <code>start()</code> - method, that is called automatically, has the responsability to - check that the given properties are coherent. - </li> - <li>The <code>AppenderBase.doAppend()</code> method invokes the - append() method of its derived classes where actual output - operations occur. It is in this method that appenders format - events by invoking their layouts. - </li> - </ul> - - <p>The <code>CountingConsoleAppender</code> can be configured like - any appender. See sample file - <em>logback-examples/src/main/java/chapter4/countingConsole.xml</em> - for an example. - </p> - - <script src="../templates/footer.js" type="text/javascript"></script>
participants (1)
-
noreply.ceki@qos.ch