[logback-dev] 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

noreply.ceki at qos.ch noreply.ceki at qos.ch
Tue Dec 23 19:17:20 CET 2008


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&uuml;lc&uuml;
+ * 
+ */
 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&uuml;lc&uuml;
+ * 
+ */
+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">&lt;configuration>
+
+  <b>&lt;appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender"></b>
+    &lt;!-- in the absence of the class attribute, it is assumed that the
+         desired discriminator type is
+         ch.qos.logback.classic.sift.MDCBasedDiscriminator -->
+    <b>&lt;discriminator></b>
+      <b>&lt;Key><span class="green">userid</span>&lt;/Key></b>
+      <b>&lt;DefaultValue>unknown&lt;/DefaultValue></b>
+    <b>&lt;/discriminator></b>
+    <b>&lt;sift></b>
+      <b>&lt;appender name="FILE-<span class="green">${userid}</span>" class="ch.qos.logback.core.FileAppender"></b>
+        <b>&lt;File><span class="green">${userid}</span>.log&lt;/File></b>
+        <b>&lt;Append>false&lt;/Append></b>
+        <b>&lt;layout class="ch.qos.logback.classic.PatternLayout"></b>
+          <b>&lt;Pattern>%d [%thread] %level %mdc %logger{35} - %msg%n&lt;/Pattern></b>
+        <b>&lt;/layout></b>
+      <b>&lt;/appender></b>
+    <b>&lt;/sift></b>
+  &lt;/appender>
+
+  &lt;root level="DEBUG">
+    &lt;appender-ref ref="SIFT" />
+  &lt;/root>
+&lt;/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&lt;LoggingEvent> {
+  static int DEFAULT_LIMIT = 16;
+  int counter = 0;
+  int limit = DEFAULT_LIMIT;
+  
+  private Layout&lt;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&lt;LoggingEvent> getLayout() {
+    return layout;
+  }
+
+  public void setLayout(Layout&lt;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>&lt;configuration>
+    <p class="source">&lt;configuration>
 
   &lt;appender name="DB" class="ch.qos.logback.access.db.DBAppender">
     &lt;connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
@@ -3293,110 +3493,32 @@
   &lt;/appender>
 
   &lt;appender-ref ref="DB" />
-&lt;/configuration></pre></div>
-
-
-    <a name="WriteYourOwnAppender"></a>
-		<h2>Writing your own Appender</h2>
+&lt;/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&lt;LoggingEvent> {
-  static int DEFAULT_LIMIT = 16;
-  int counter = 0;
-  int limit = DEFAULT_LIMIT;
-  
-  private Layout&lt;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&lt;LoggingEvent> getLayout() {
-    return layout;
-  }
-
-  public void setLayout(Layout&lt;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>
 
 


More information about the logback-dev mailing list