More centralized logging work.

* Embedded mode implementation
* Logging implementation
* WebAppContext Configuration
* Logging Configuration implementation

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/sandbox/trunk@807 7e9141cc-0065-0410-87d8-b60c137991c4
diff --git a/dummy-logging-webapps/dummy-webapp-logging-java/src/main/java/org/eclipse/jetty/tests/webapp/LoggingServlet.java b/dummy-logging-webapps/dummy-webapp-logging-java/src/main/java/org/eclipse/jetty/tests/webapp/LoggingServlet.java
index a4b0466..a6e05da 100644
--- a/dummy-logging-webapps/dummy-webapp-logging-java/src/main/java/org/eclipse/jetty/tests/webapp/LoggingServlet.java
+++ b/dummy-logging-webapps/dummy-webapp-logging-java/src/main/java/org/eclipse/jetty/tests/webapp/LoggingServlet.java
@@ -42,12 +42,11 @@
     }
 
     /**
-     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
-     *      response)
+     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
      */
+    @Override
     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
     {
-        log.log(Level.INFO,"LoggingServlet(log4j) initialized");
+        log.log(Level.INFO,"LoggingServlet(java) initialized");
     }
-
 }
diff --git a/jetty-centralized-logging/pom.xml b/jetty-centralized-logging/pom.xml
index 8017d30..79eb409 100644
--- a/jetty-centralized-logging/pom.xml
+++ b/jetty-centralized-logging/pom.xml
@@ -4,7 +4,7 @@
   <parent>
     <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-project</artifactId>
-    <version>7.0.0.RC6-SNAPSHOT</version>
+    <version>7.0.1-SNAPSHOT</version>
   </parent>
   <groupId>org.eclipse.jetty</groupId>
   <artifactId>jetty-centralized-logging</artifactId>
@@ -43,6 +43,33 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <version>1.5.6</version>
+    </dependency>
+    <!-- 
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-simple</artifactId>
+      <version>1.5.6</version>
+    </dependency>
+     -->
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>jul-to-slf4j</artifactId>
+      <version>1.5.6</version>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>jcl-over-slf4j</artifactId>
+      <version>1.5.6</version>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>log4j-over-slf4j</artifactId>
+      <version>1.5.6</version>
+    </dependency>
+    <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <scope>test</scope>
@@ -80,7 +107,7 @@
             <executions>
               <execution>
                 <id>copy-testing-wars</id>
-                <phase>verify</phase>
+                <phase>validate</phase>
                 <goals>
                   <goal>copy</goal>
                 </goals>
@@ -112,6 +139,7 @@
                     </artifactItem>
                   </artifactItems>
                   <outputDirectory>${basedir}/src/test/resources/webapps</outputDirectory>
+                  <overWriteIfNewer>true</overWriteIfNewer>
                   <stripVersion>true</stripVersion>
                 </configuration>
               </execution>
diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/CentralizedLogging.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/CentralizedLogging.java
index 83b9979..45cde32 100644
--- a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/CentralizedLogging.java
+++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/CentralizedLogging.java
@@ -1,26 +1,32 @@
 package org.eclipse.jetty.logging;
 
-import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.deploy.ContextDeployer;
 import org.eclipse.jetty.util.component.AbstractLifeCycle;
 
 public class CentralizedLogging extends AbstractLifeCycle
 {
-    private Server _server;
+    private ContextDeployer contextDeployer;
+    private CentralizedWebAppLoggingConfiguration webAppConfiguration;
 
-    public Server getServer()
+    public ContextDeployer getContextDeployer()
     {
-        return _server;
+        return contextDeployer;
     }
 
-    public void setServer(Server server)
+    public void setContextDeployer(ContextDeployer contextDeployer)
     {
-        this._server = server;
+        this.contextDeployer = contextDeployer;
     }
 
     @Override
     protected void doStart() throws Exception
     {
-        // TODO Auto-generated method stub
+    	webAppConfiguration = new CentralizedWebAppLoggingConfiguration();
+    	
+        if(contextDeployer != null) {
+//            contextDeployer.addWebAppConfiguration(webAppConfiguration);
+        }
+        
         super.doStart();
     }
 }
diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/CentralizedWebAppLoggingConfiguration.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/CentralizedWebAppLoggingConfiguration.java
new file mode 100644
index 0000000..0e1cfa9
--- /dev/null
+++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/CentralizedWebAppLoggingConfiguration.java
@@ -0,0 +1,29 @@
+package org.eclipse.jetty.logging;
+
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+public class CentralizedWebAppLoggingConfiguration implements Configuration
+{
+    public void configure(WebAppContext context) throws Exception
+    {
+        System.out.println("configure(" + context + ")");
+        context.addSystemClass("org.apache.log4j.");
+        context.addSystemClass("org.slf4j.impl.");
+    }
+
+    public void deconfigure(WebAppContext context) throws Exception
+    {
+        System.out.println("deconfigure(" + context + ")");
+    }
+
+    public void postConfigure(WebAppContext context) throws Exception
+    {
+        System.out.println("postConfigure(" + context + ")");
+    }
+
+    public void preConfigure(WebAppContext context) throws Exception
+    {
+        System.out.println("preConfigure(" + context + ")");
+    }
+}
diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/Appender.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/Appender.java
new file mode 100644
index 0000000..d2783a4
--- /dev/null
+++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/Appender.java
@@ -0,0 +1,32 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+package org.eclipse.jetty.logging.impl;
+
+import java.io.IOException;
+
+/**
+ * Appender for log content.
+ */
+public interface Appender
+{
+    void append(String date, int ms, Severity severity, String name, String message, Throwable t) throws IOException;
+
+    void setProperty(String key, String value) throws Exception;
+
+    void open() throws IOException;
+
+    void close() throws IOException;
+}
diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralLogger.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralLogger.java
new file mode 100644
index 0000000..1372940
--- /dev/null
+++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralLogger.java
@@ -0,0 +1,239 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+package org.eclipse.jetty.logging.impl;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.util.DateCache;
+import org.slf4j.helpers.MarkerIgnoringBase;
+import org.slf4j.helpers.MessageFormatter;
+
+/**
+ * Centralized Logger implementation.
+ */
+public class CentralLogger extends MarkerIgnoringBase
+{
+    private static final long serialVersionUID = 385001265755850685L;
+    private static DateCache dateCache;
+    private Severity level = Severity.INFO;
+    private String name;
+    private Appender appenders[];
+
+    static
+    {
+        try
+        {
+            dateCache = new DateCache("yyyy-MM-dd HH:mm:ss");
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace(System.err);
+        }
+    }
+
+    protected CentralLogger(String name, Appender appenders[], Severity severity)
+    {
+        this.name = name;
+        this.appenders = appenders;
+        this.level = severity;
+    }
+
+    private void log(Severity severity, String message, Throwable t)
+    {
+        String now = dateCache.now();
+        int ms = dateCache.lastMs();
+
+        for (Appender appender : appenders)
+        {
+            try
+            {
+                appender.append(now,ms,severity,name,message,t);
+            }
+            catch (IOException e)
+            {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private void logFormatted(Severity severity, String format, Object arg)
+    {
+        String msg = MessageFormatter.format(format,arg);
+        log(severity,msg,null);
+    }
+
+    private void logFormatted(Severity severity, String format, Object arg1, Object arg2)
+    {
+        String msg = MessageFormatter.format(format,arg1,arg2);
+        log(severity,msg,null);
+    }
+
+    private void logFormatted(Severity severity, String format, Object[] argArray)
+    {
+        String msg = MessageFormatter.arrayFormat(format,argArray);
+        log(severity,msg,null);
+    }
+
+    public void debug(String msg)
+    {
+        log(Severity.DEBUG,msg,null);
+    }
+
+    public void debug(String format, Object arg)
+    {
+        logFormatted(Severity.DEBUG,format,arg);
+    }
+
+    public void debug(String format, Object arg1, Object arg2)
+    {
+        logFormatted(Severity.DEBUG,format,arg1,arg2);
+    }
+
+    public void debug(String format, Object[] argArray)
+    {
+        logFormatted(Severity.DEBUG,format,argArray);
+    }
+
+    public void debug(String msg, Throwable t)
+    {
+        log(Severity.DEBUG,msg,t);
+    }
+
+    public void error(String msg)
+    {
+        log(Severity.ERROR,msg,null);
+    }
+
+    public void error(String format, Object arg)
+    {
+        logFormatted(Severity.ERROR,format,arg);
+    }
+
+    public void error(String format, Object arg1, Object arg2)
+    {
+        logFormatted(Severity.ERROR,format,arg1,arg2);
+    }
+
+    public void error(String format, Object[] argArray)
+    {
+        logFormatted(Severity.ERROR,format,argArray);
+    }
+
+    public void error(String msg, Throwable t)
+    {
+        log(Severity.ERROR,msg,t);
+    }
+
+    public void info(String msg)
+    {
+        log(Severity.INFO,msg,null);
+    }
+
+    public void info(String format, Object arg)
+    {
+        logFormatted(Severity.INFO,format,arg);
+    }
+
+    public void info(String format, Object arg1, Object arg2)
+    {
+        logFormatted(Severity.INFO,format,arg1,arg2);
+    }
+
+    public void info(String format, Object[] argArray)
+    {
+        logFormatted(Severity.INFO,format,argArray);
+    }
+
+    public void info(String msg, Throwable t)
+    {
+        log(Severity.INFO,msg,t);
+    }
+
+    public boolean isDebugEnabled()
+    {
+        return Severity.DEBUG.isEnabled(level);
+    }
+
+    public boolean isErrorEnabled()
+    {
+        return Severity.ERROR.isEnabled(level);
+    }
+
+    public boolean isInfoEnabled()
+    {
+        return Severity.INFO.isEnabled(level);
+    }
+
+    public boolean isTraceEnabled()
+    {
+        return Severity.TRACE.isEnabled(level);
+    }
+
+    public boolean isWarnEnabled()
+    {
+        return Severity.WARN.isEnabled(level);
+    }
+
+    public void trace(String msg)
+    {
+        log(Severity.TRACE,msg,null);
+    }
+
+    public void trace(String format, Object arg)
+    {
+        logFormatted(Severity.TRACE,format,arg);
+    }
+
+    public void trace(String format, Object arg1, Object arg2)
+    {
+        logFormatted(Severity.TRACE,format,arg1,arg2);
+    }
+
+    public void trace(String format, Object[] argArray)
+    {
+        logFormatted(Severity.TRACE,format,argArray);
+    }
+
+    public void trace(String msg, Throwable t)
+    {
+        log(Severity.TRACE,msg,t);
+    }
+
+    public void warn(String msg)
+    {
+        log(Severity.WARN,msg,null);
+    }
+
+    public void warn(String format, Object arg)
+    {
+        logFormatted(Severity.WARN,format,arg);
+    }
+
+    public void warn(String format, Object arg1, Object arg2)
+    {
+        logFormatted(Severity.WARN,format,arg1,arg2);
+    }
+
+    public void warn(String format, Object[] argArray)
+    {
+        logFormatted(Severity.WARN,format,argArray);
+    }
+
+    public void warn(String msg, Throwable t)
+    {
+        log(Severity.WARN,msg,t);
+    }
+}
diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralLoggerFactory.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralLoggerFactory.java
new file mode 100644
index 0000000..2b58e2e
--- /dev/null
+++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/CentralLoggerFactory.java
@@ -0,0 +1,53 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+package org.eclipse.jetty.logging.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.slf4j.ILoggerFactory;
+import org.slf4j.Logger;
+
+/**
+ * The Logger Factory for CentralLoggers.
+ */
+public class CentralLoggerFactory implements ILoggerFactory
+{
+    private ConfiguredLogger root;
+    private Map<String, CentralLogger> loggers;
+
+    public CentralLoggerFactory(ConfiguredLogger root)
+    {
+        this.root = root;
+        this.loggers = new HashMap<String, CentralLogger>();
+    }
+
+    public Logger getLogger(String name)
+    {
+        CentralLogger ret = null;
+        synchronized (this)
+        {
+            ret = loggers.get(name);
+            if (ret == null)
+            {
+                ConfiguredLogger clogger = root.getConfiguredLogger(name);
+                ret = clogger.getLogger();
+                loggers.put(name,ret);
+            }
+        }
+        return ret;
+    }
+}
diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/ConfiguredLogger.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/ConfiguredLogger.java
new file mode 100644
index 0000000..3881b19
--- /dev/null
+++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/ConfiguredLogger.java
@@ -0,0 +1,368 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+package org.eclipse.jetty.logging.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+
+/**
+ * Represents the ConfiguredLogger
+ */
+public class ConfiguredLogger
+{
+    private static String between(String line, String start, String end)
+    {
+        if (line.startsWith(start) && line.endsWith(end))
+        {
+            return line.substring(start.length(),line.length() - end.length());
+        }
+        return null;
+    }
+
+    private static void configureAppender(Properties props, String id, Appender appender)
+    {
+        // Collect configuration fields for appender id
+        Pattern appenderIdRegex = Pattern.compile("^appender\\." + id + "\\.([^\\.]*)$");
+        Matcher match;
+
+        @SuppressWarnings("unchecked")
+        Enumeration<String> enNames = (Enumeration<String>)props.propertyNames();
+        while (enNames.hasMoreElements())
+        {
+            String name = enNames.nextElement();
+            match = appenderIdRegex.matcher(name);
+            if (match.matches())
+            {
+                String fieldName = match.group(1);
+                if (fieldName.equals("class"))
+                {
+                    continue; // Not meant to be set.
+                }
+
+                String value = props.getProperty(name);
+
+                try
+                {
+                    appender.setProperty(fieldName,value);
+                }
+                catch (Exception e)
+                {
+                    System.err.printf("Unable to set property %s on appender %s to %s%n",fieldName,appender.getClass().getName(),value);
+                    e.printStackTrace(System.err);
+                }
+            }
+        }
+    }
+
+    private static void dumpDetails(PrintStream out, String prefix, ConfiguredLogger cl)
+    {
+        out.printf("%sName: %s%n",prefix,cl.name);
+        out.printf("%sLevel: %s%n",prefix,cl.level.name());
+        out.printf("%sAppenders: ",prefix);
+        for (Iterator<Appender> it = cl.getAppenders().iterator(); it.hasNext();)
+        {
+            out.print(it.next().getClass().getSimpleName());
+            if (it.hasNext())
+            {
+                out.print(", ");
+            }
+        }
+        out.println();
+        if (cl.children != null)
+        {
+            out.printf("%sChildren.count: %d%n",prefix,cl.children.size());
+            String childPrefix = prefix + "  ";
+            for (Map.Entry<String, ConfiguredLogger> entry : cl.children.entrySet())
+            {
+                out.printf("%sChild[%s]%n",prefix,entry.getKey());
+                dumpDetails(out,childPrefix,entry.getValue());
+            }
+        }
+        else
+        {
+            out.printf("%sChildren: <null>%n",prefix);
+        }
+    }
+
+    private static List<Appender> getAppenders(Properties props, String key, Map<String, Appender> declaredAppenders)
+    {
+        String value = props.getProperty(key);
+        if (value == null)
+        {
+            return null;
+        }
+
+        String ids[] = value.split(",");
+        List<Appender> appenders = new ArrayList<Appender>();
+        // ensure ids exist as declared as well.
+        for (int i = 0, n = ids.length; i < n; i++)
+        {
+            if (declaredAppenders.containsKey(ids[i]))
+            {
+                appenders.add(declaredAppenders.get(ids[i]));
+            }
+            else
+            {
+                System.err.println("No such Appender: " + ids[i]);
+            }
+        }
+
+        return appenders;
+    }
+
+    private static Map<String, Appender> getDeclaredAppenders(Properties props)
+    {
+        Set<String> ids = new TreeSet<String>();
+
+        // Collect IDs
+        Pattern appenderIdRegex = Pattern.compile("appender\\.([^\\.]*).class");
+        Matcher match;
+
+        @SuppressWarnings("unchecked")
+        Enumeration<String> enNames = (Enumeration<String>)props.propertyNames();
+        while (enNames.hasMoreElements())
+        {
+            String name = enNames.nextElement();
+            match = appenderIdRegex.matcher(name);
+            if (match.matches())
+            {
+                ids.add(match.group(1));
+            }
+        }
+
+        Map<String, Appender> appenders = new HashMap<String, Appender>();
+
+        if (ids.isEmpty())
+        {
+            return appenders;
+        }
+
+        // Instantiate > Configure > Open the Appenders
+        for (String id : ids)
+        {
+            String clazzName = props.getProperty("appender." + id + ".class");
+            if (clazzName == null)
+            {
+                continue; // skip
+            }
+
+            try
+            {
+                // Instantiate Appender
+                Class<?> clazzAppender = Class.forName(clazzName);
+                Appender appender = (Appender)clazzAppender.newInstance();
+
+                // Configure Appender
+                configureAppender(props,id,appender);
+
+                // Open Appender
+                appender.open();
+
+                appenders.put(id,appender);
+            }
+            catch (ClassNotFoundException e)
+            {
+                System.err.println("Unable to find class: " + clazzName);
+                e.printStackTrace(System.err);
+            }
+            catch (InstantiationException e)
+            {
+                System.err.println("Unable to Instantiate: " + clazzName);
+                e.printStackTrace(System.err);
+            }
+            catch (IllegalAccessException e)
+            {
+                System.err.println("Unable to Access: " + clazzName);
+                e.printStackTrace(System.err);
+            }
+            catch (IOException e)
+            {
+                System.err.println("Unable to open Appender: " + clazzName);
+                e.printStackTrace(System.err);
+            }
+        }
+
+        return appenders;
+    }
+
+    public static ConfiguredLogger load(InputStream stream) throws IOException
+    {
+        Properties props = new Properties();
+        props.load(stream);
+        return load(props);
+    }
+
+    public static ConfiguredLogger load(Properties props) throws IOException
+    {
+        ConfiguredLogger root = new ConfiguredLogger(Logger.ROOT_LOGGER_NAME);
+
+        // Default for root
+        root.level = Severity.INFO;
+
+        // Collect all possible appenders, by id.
+        Map<String, Appender> declaredAppenders = getDeclaredAppenders(props);
+
+        root.appenders = getAppenders(props,"root.appenders",declaredAppenders);
+        if (root.appenders == null)
+        {
+            // Default (if not specified for root)
+            root.appenders.add(new ConsoleAppender());
+        }
+
+        // Set logger & level of ROOT
+        root.logger = new CentralLogger(root.name,root.appenders.toArray(new Appender[] {}),root.level);
+        root.level = Severity.valueOf(props.getProperty("root.level"));
+
+        // Collect other configured loggers
+        Set<String> ids = new TreeSet<String>(); // Use TreeSet to get sort order that we need.
+
+        @SuppressWarnings("unchecked")
+        Enumeration<String> enNames = (Enumeration<String>)props.propertyNames();
+        while (enNames.hasMoreElements())
+        {
+            String name = enNames.nextElement();
+            String id = between(name,"logger.",".level");
+            if (id == null)
+            {
+                id = between(name,"logger.",".appenders");
+                if (id == null)
+                {
+                    continue; // not something we care about
+                }
+            }
+
+            ids.add(id);
+        }
+
+        // Set loggers & levels of OTHER nodes
+        for (String id : ids)
+        {
+            System.out.println("Processing child id: " + id);
+            ConfiguredLogger childlog = root.getConfiguredLogger(id);
+            childlog.level = Severity.valueOf(props.getProperty("logger." + id + ".level","INFO"));
+            childlog.addAppenders(getAppenders(props,"logger." + id + ".appenders",declaredAppenders));
+        }
+
+        return root;
+    }
+
+    private String name;
+    private List<Appender> appenders = new ArrayList<Appender>();
+    private Severity level;
+    private CentralLogger logger;
+    private Map<String, ConfiguredLogger> children;
+
+    private ConfiguredLogger(ConfiguredLogger copyLogger, String name)
+    {
+        if (copyLogger.name.equals(CentralLogger.ROOT_LOGGER_NAME))
+        {
+            this.name = name;
+        }
+        else
+        {
+            this.name = copyLogger.name + "." + name;
+        }
+
+        this.appenders.addAll(copyLogger.appenders);
+        this.level = copyLogger.level;
+        this.logger = new CentralLogger(name,appenders.toArray(new Appender[] {}),level);
+    }
+
+    private ConfiguredLogger(String name)
+    {
+        this.name = name;
+    }
+
+    private void addAppenders(List<Appender> moreAppenders)
+    {
+        if (moreAppenders == null)
+        {
+            return;
+        }
+        getAppenders().addAll(moreAppenders);
+        this.logger = new CentralLogger(name,appenders.toArray(new Appender[] {}),level);
+    }
+
+    public void dumpTree(PrintStream out)
+    {
+        dumpDetails(out,"",this);
+    }
+
+    public List<Appender> getAppenders()
+    {
+        return appenders;
+    }
+
+    private ConfiguredLogger getChildLogger(String name)
+    {
+        ConfiguredLogger child = getChildren().get(name);
+        if (child == null)
+        {
+            child = new ConfiguredLogger(this,name);
+            this.children.put(name,child);
+        }
+        return child;
+    }
+
+    public Map<String, ConfiguredLogger> getChildren()
+    {
+        if (children == null)
+        {
+            children = new HashMap<String, ConfiguredLogger>();
+        }
+        return children;
+    }
+
+    public ConfiguredLogger getConfiguredLogger(String name)
+    {
+        String parts[] = name.split("\\.");
+        ConfiguredLogger ret = this;
+        for (String part : parts)
+        {
+            ret = ret.getChildLogger(part);
+        }
+        return ret;
+    }
+
+    public Severity getLevel()
+    {
+        return level;
+    }
+
+    public CentralLogger getLogger()
+    {
+        return logger;
+    }
+
+    public String getName()
+    {
+        return name;
+    }
+}
\ No newline at end of file
diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/ConsoleAppender.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/ConsoleAppender.java
new file mode 100644
index 0000000..386f82f
--- /dev/null
+++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/ConsoleAppender.java
@@ -0,0 +1,68 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+package org.eclipse.jetty.logging.impl;
+
+import java.io.IOException;
+
+/**
+ * Standard Appender to the STDOUT Console
+ */
+public class ConsoleAppender implements Appender
+{
+    public void append(String date, int ms, Severity severity, String name, String message, Throwable t)
+    {
+        StringBuffer buf = new StringBuffer();
+        buf.append(date);
+        if (ms > 99)
+        {
+            buf.append(".");
+        }
+        else if (ms > 0)
+        {
+            buf.append(".0");
+        }
+        else
+        {
+            buf.append(".00");
+        }
+        buf.append(ms);
+        buf.append(':').append(severity.name()).append(':');
+        buf.append(name);
+        buf.append(':').append(message);
+
+        System.out.println(buf.toString());
+        if (t != null)
+        {
+            t.printStackTrace(System.out);
+        }
+        System.out.flush();
+    }
+
+    public void setProperty(String key, String value) throws Exception
+    {
+        /* nothing to do here */
+    }
+
+    public void open() throws IOException
+    {
+        /* nothing to do here */
+    }
+
+    public void close() throws IOException
+    {
+        /* nothing to do here */
+    }
+}
diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/RollingFileAppender.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/RollingFileAppender.java
new file mode 100644
index 0000000..e15626e
--- /dev/null
+++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/RollingFileAppender.java
@@ -0,0 +1,176 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+package org.eclipse.jetty.logging.impl;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.TimeZone;
+
+import org.eclipse.jetty.util.RolloverFileOutputStream;
+
+/**
+ * Rolling File Appender
+ */
+public class RollingFileAppender implements Appender
+{
+    private RolloverFileOutputStream out;
+    private String filename;
+    private boolean append = true;
+    private int retainDays = 31;
+    private TimeZone zone = TimeZone.getDefault();
+    private String dateFormat = "yyyy_MM_dd";
+    private String backupFormat = "HHmmssSSS";
+
+    public void append(String date, int ms, Severity severity, String name, String message, Throwable t) throws IOException
+    {
+        StringBuffer buf = new StringBuffer();
+        buf.append(date);
+        if (ms > 99)
+        {
+            buf.append(".");
+        }
+        else if (ms > 0)
+        {
+            buf.append(".0");
+        }
+        else
+        {
+            buf.append(".00");
+        }
+        buf.append(ms);
+        buf.append(':').append(severity.name()).append(':');
+        buf.append(name);
+        buf.append(':').append(message);
+
+        out.write(buf.toString().getBytes());
+        if (t != null)
+        {
+            t.printStackTrace(new PrintStream(out));
+        }
+        out.flush();
+    }
+
+    public void close() throws IOException
+    {
+        out.close();
+    }
+
+    public String getBackupFormat()
+    {
+        return backupFormat;
+    }
+
+    public String getDateFormat()
+    {
+        return dateFormat;
+    }
+
+    public String getFilename()
+    {
+        return filename;
+    }
+
+    public int getRetainDays()
+    {
+        return retainDays;
+    }
+
+    public TimeZone getZone()
+    {
+        return zone;
+    }
+
+    public boolean isAppend()
+    {
+        return append;
+    }
+
+    public void open() throws IOException
+    {
+        out = new RolloverFileOutputStream(filename,append,retainDays,zone,dateFormat,backupFormat);
+    }
+
+    public void setAppend(boolean append)
+    {
+        this.append = append;
+    }
+
+    public void setBackupFormat(String backupFormat)
+    {
+        this.backupFormat = backupFormat;
+    }
+
+    public void setDateFormat(String dateFormat)
+    {
+        this.dateFormat = dateFormat;
+    }
+
+    public void setFilename(String filename)
+    {
+        this.filename = filename;
+    }
+
+    public void setProperty(String key, String value) throws Exception
+    {
+        if ("filename".equals(key))
+        {
+            setFilename(value);
+            return;
+        }
+
+        if ("append".equals(key))
+        {
+            setAppend(Boolean.parseBoolean(value));
+            return;
+        }
+
+        if ("retainDays".equals(key))
+        {
+            setRetainDays(Integer.parseInt(value));
+            return;
+        }
+
+        if ("zone".equals(key))
+        {
+            setZone(TimeZone.getTimeZone(value));
+            return;
+        }
+
+        if ("dateFormat".equals(key))
+        {
+            setDateFormat(value);
+            return;
+        }
+
+        if ("backupFormat".equals(key))
+        {
+            setBackupFormat(value);
+            return;
+        }
+
+        throw new IllegalArgumentException("No such key \"" + key + "\"");
+    }
+
+    public void setRetainDays(int retainDays)
+    {
+        this.retainDays = retainDays;
+    }
+
+    public void setZone(TimeZone zone)
+    {
+        this.zone = zone;
+    }
+}
diff --git a/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/Severity.java b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/Severity.java
new file mode 100644
index 0000000..f36f6f5
--- /dev/null
+++ b/jetty-centralized-logging/src/main/java/org/eclipse/jetty/logging/impl/Severity.java
@@ -0,0 +1,27 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.logging.impl;
+
+public enum Severity
+{
+    TRACE, DEBUG, INFO, WARN, ERROR;
+
+    public boolean isEnabled(Severity severity)
+    {
+        return (severity.ordinal() >= ordinal());
+    }
+}
diff --git a/jetty-centralized-logging/src/main/java/org/slf4j/impl/StaticLoggerBinder.java b/jetty-centralized-logging/src/main/java/org/slf4j/impl/StaticLoggerBinder.java
new file mode 100644
index 0000000..7c85b39
--- /dev/null
+++ b/jetty-centralized-logging/src/main/java/org/slf4j/impl/StaticLoggerBinder.java
@@ -0,0 +1,93 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+package org.slf4j.impl;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import org.eclipse.jetty.logging.impl.CentralLoggerFactory;
+import org.eclipse.jetty.logging.impl.ConfiguredLogger;
+import org.eclipse.jetty.logging.impl.ConsoleAppender;
+import org.slf4j.ILoggerFactory;
+import org.slf4j.spi.LoggerFactoryBinder;
+
+/**
+ * Standard entry point for Slf4J, used to configure the desired {@link ILoggerFactory}.
+ */
+public class StaticLoggerBinder implements LoggerFactoryBinder
+{
+    /**
+     * Required by {@link org.slf4j.LoggerFactory}
+     */
+    private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
+
+    /**
+     * Required by {@link org.slf4j.LoggerFactory}
+     */
+    public static final StaticLoggerBinder getSingleton()
+    {
+        return SINGLETON;
+    }
+
+    /**
+     * Required by {@link org.slf4j.LoggerFactory}, based on information from slf4j, this field should not be declared
+     * final (it can cause problems with optimizations occurring within the java compiler)
+     */
+    public static/* final */String REQUESTED_API_VERSION = "1.5.6";
+
+    private static final String loggerFactoryClassName = CentralLoggerFactory.class.getName();
+    private ILoggerFactory loggerFactory;
+    private ConfiguredLogger root;
+
+    public ConfiguredLogger getRoot()
+    {
+        if(root == null) {
+            try
+            {
+                Properties props = new Properties();
+                props.setProperty("root.level","DEBUG");
+                props.setProperty("root.appenders","console");
+                props.setProperty("appender.console.class",ConsoleAppender.class.getName());
+                root = ConfiguredLogger.load(props);
+            }
+            catch (IOException e)
+            {
+                e.printStackTrace(System.err);
+            }
+        }
+        return root;
+    }
+
+    public void setRoot(ConfiguredLogger root)
+    {
+        this.root = root;
+    }
+
+    public ILoggerFactory getLoggerFactory()
+    {
+        if (loggerFactory == null)
+        {
+            loggerFactory = new CentralLoggerFactory(getRoot());
+        }
+
+        return loggerFactory;
+    }
+
+    public String getLoggerFactoryClassStr()
+    {
+        return loggerFactoryClassName;
+    }
+}
diff --git a/jetty-centralized-logging/src/main/java/org/slf4j/impl/StaticMDCBinder.java b/jetty-centralized-logging/src/main/java/org/slf4j/impl/StaticMDCBinder.java
new file mode 100644
index 0000000..0c6202a
--- /dev/null
+++ b/jetty-centralized-logging/src/main/java/org/slf4j/impl/StaticMDCBinder.java
@@ -0,0 +1,42 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+package org.slf4j.impl;
+
+import org.slf4j.helpers.NOPMakerAdapter;
+import org.slf4j.spi.MDCAdapter;
+
+/**
+ * Standard entry point for Slf4J, used to configure the desired {@link MDCAdapter}.
+ */
+public class StaticMDCBinder
+{
+    public static final StaticMDCBinder SINGLETON = new StaticMDCBinder();
+
+    private StaticMDCBinder()
+    {
+        /* prevent external instantiation */
+    }
+
+    public MDCAdapter getMDCA()
+    {
+        return new NOPMakerAdapter();
+    }
+
+    public String getMDCAdapterClassStr()
+    {
+        return NOPMakerAdapter.class.getName();
+    }
+}
diff --git a/jetty-centralized-logging/src/main/java/org/slf4j/impl/StaticMarkerBinder.java b/jetty-centralized-logging/src/main/java/org/slf4j/impl/StaticMarkerBinder.java
new file mode 100644
index 0000000..4a62163
--- /dev/null
+++ b/jetty-centralized-logging/src/main/java/org/slf4j/impl/StaticMarkerBinder.java
@@ -0,0 +1,44 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+package org.slf4j.impl;
+
+import org.slf4j.IMarkerFactory;
+import org.slf4j.MarkerFactory;
+import org.slf4j.helpers.BasicMarkerFactory;
+import org.slf4j.spi.MarkerFactoryBinder;
+
+/**
+ * Standard entry point for Slf4J, used to configure the desired {@link IMarkerFactory}.
+ */
+public class StaticMarkerBinder implements MarkerFactoryBinder
+{
+    /**
+     * Required by {@link MarkerFactory}
+     */
+    public static final StaticMarkerBinder SINGLETON = new StaticMarkerBinder();
+
+    private final IMarkerFactory markerFactory = new BasicMarkerFactory();
+
+    public IMarkerFactory getMarkerFactory()
+    {
+        return markerFactory;
+    }
+
+    public String getMarkerFactoryClassStr()
+    {
+        return markerFactory.getClass().getName();
+    }
+}
diff --git a/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/CentralizedLoggingTest.java b/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/CentralizedLoggingTest.java
index 753a881..bbbac10 100644
--- a/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/CentralizedLoggingTest.java
+++ b/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/CentralizedLoggingTest.java
@@ -16,13 +16,36 @@
 package org.eclipse.jetty.logging;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
 import junit.framework.TestCase;
 
 public class CentralizedLoggingTest extends TestCase
 {
+    class LogEntry
+    {
+        int severity;
+        String msg;
+
+        public LogEntry(int severity, String msg)
+        {
+            this.severity = severity;
+            this.msg = msg;
+        }
+    }
+    private static final int DEBUG = 0;
+
+    private static final int INFO = 1;
+
     private XmlConfiguredJetty jetty;
 
+    private void assertLogsExist(List<LogEntry> expected)
+    {
+        // TODO Auto-generated method stub
+
+    }
+
     @Override
     protected void setUp() throws Exception
     {
@@ -45,24 +68,47 @@
         super.tearDown();
     }
 
-    public void testLog4jRouting() throws IOException
-    {
-        SimpleRequest.get(jetty,"/commons/logging");
-        SimpleRequest.get(jetty,"/log4j/logging");
-    }
-
     public void testCommonsRouting() throws IOException
     {
         SimpleRequest.get(jetty,"/commons/logging");
-    }
 
-    public void testSlf4jRouting() throws IOException
-    {
-        SimpleRequest.get(jetty,"/slf4j/logging");
+        List<LogEntry> expected = new ArrayList<LogEntry>();
+        expected.add(new LogEntry(DEBUG,"LoggingServlet(commons-logging) initialized"));
+        expected.add(new LogEntry(INFO,"LoggingServlet(commons-logging) GET requested"));
+
+        assertLogsExist(expected);
     }
 
     public void testJavaUtilRouting() throws IOException
     {
         SimpleRequest.get(jetty,"/java/logging");
+
+        List<LogEntry> expected = new ArrayList<LogEntry>();
+        expected.add(new LogEntry(DEBUG,"LoggingServlet(java) initialized"));
+        expected.add(new LogEntry(INFO,"LoggingServlet(java) GET requested"));
+
+        assertLogsExist(expected);
+    }
+
+    public void testLog4jRouting() throws IOException
+    {
+        SimpleRequest.get(jetty,"/log4j/logging");
+
+        List<LogEntry> expected = new ArrayList<LogEntry>();
+        expected.add(new LogEntry(DEBUG,"LoggingServlet(log4j) initialized"));
+        expected.add(new LogEntry(INFO,"LoggingServlet(log4j) GET requested"));
+
+        assertLogsExist(expected);
+    }
+
+    public void testSlf4jRouting() throws IOException
+    {
+        SimpleRequest.get(jetty,"/slf4j/logging");
+
+        List<LogEntry> expected = new ArrayList<LogEntry>();
+        expected.add(new LogEntry(DEBUG,"LoggingServlet(slf4j) initialized"));
+        expected.add(new LogEntry(INFO,"LoggingServlet(slf4j) GET requested"));
+
+        assertLogsExist(expected);
     }
 }
diff --git a/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/EmbeddedCentralizedLoggingTest.java b/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/EmbeddedCentralizedLoggingTest.java
new file mode 100644
index 0000000..76c36ba
--- /dev/null
+++ b/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/EmbeddedCentralizedLoggingTest.java
@@ -0,0 +1,56 @@
+package org.eclipse.jetty.logging;
+
+import java.io.File;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+public class EmbeddedCentralizedLoggingTest extends TestCase
+{
+    @Override
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+
+        File testTmpDir = new File(MavenTestingUtils.getTargetTestingDir(this),"workdir");
+        testTmpDir.mkdirs();
+        System.setProperty("java.io.tmpdir",testTmpDir.getAbsolutePath());
+    }
+
+    protected Server createWebAppServer(String contextPath, String webappName) throws Exception
+    {
+        Server server = new Server();
+
+        Connector connector = new SelectChannelConnector();
+        connector.setPort(0);
+        server.setConnectors(new Connector[]
+        { connector });
+
+        File webappFile = MavenTestingUtils.getTestResourceFile("webapps/" + webappName);
+        System.out.println("war = " + webappFile.getAbsolutePath());
+
+        WebAppContext webapp = new WebAppContext();
+        webapp.setContextPath(contextPath);
+        webapp.setWar(webappFile.getAbsolutePath());
+        webapp.addConfiguration(new CentralizedWebAppLoggingConfiguration());
+
+        server.setHandler(webapp);
+
+        return server;
+    }
+
+    public void testEmbeddedWebappLog4j() throws Exception
+    {
+        Server server = createWebAppServer("/log4j","dummy-webapp-logging-log4j.war");
+
+        server.start();
+
+        SimpleRequest.get(server,"/log4j/logging");
+
+        server.stop();
+    }
+}
diff --git a/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/SimpleRequest.java b/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/SimpleRequest.java
index f9ee11b..26b7f7b 100644
--- a/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/SimpleRequest.java
+++ b/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/SimpleRequest.java
@@ -5,13 +5,13 @@
 // are made available under the terms of the Eclipse Public License v1.0
 // and Apache License v2.0 which accompanies this distribution.
 //
-// The Eclipse Public License is available at 
+// The Eclipse Public License is available at
 // http://www.eclipse.org/legal/epl-v10.html
 //
 // The Apache License v2.0 is available at
 // http://www.apache.org/licenses/LICENSE-2.0.txt
 //
-// You may elect to redistribute this code under either of these licenses. 
+// You may elect to redistribute this code under either of these licenses.
 // ========================================================================
 package org.eclipse.jetty.logging;
 
@@ -19,10 +19,13 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
+import java.net.InetAddress;
 import java.net.URI;
 
 import junit.framework.Assert;
 
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.util.IO;
 
 /**
@@ -40,7 +43,42 @@
     public static String get(XmlConfiguredJetty jetty, String path) throws IOException
     {
         URI fullUri = jetty.getServerURI().resolve(path);
+        return get(fullUri);
+    }
 
+    /**
+     * Issue an HTTP GET to the server, on the path specified.
+     * 
+     * @param jetty
+     * @param path
+     * @throws IOException
+     */
+    public static String get(Server server, String path) throws IOException
+    {
+        // Find the active server port.
+        int serverPort = (-1);
+        Connector connectors[] = server.getConnectors();
+        for (int i = 0; i < connectors.length; i++)
+        {
+            Connector connector = connectors[i];
+            if (connector.getLocalPort() > 0)
+            {
+                serverPort = connector.getLocalPort();
+                break;
+            }
+        }
+
+        StringBuffer uri = new StringBuffer();
+        uri.append("http://");
+        uri.append(InetAddress.getLocalHost().getHostAddress());
+        uri.append(":").append(serverPort);
+
+        URI fullUri = URI.create(uri.toString()).resolve(path);
+        return get(fullUri);
+    }
+
+    private static String get(URI fullUri) throws IOException
+    {
         System.out.println("GET: " + fullUri.toASCIIString());
 
         HttpURLConnection conn = (HttpURLConnection)fullUri.toURL().openConnection();
diff --git a/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/impl/ConfiguredLoggerTest.java b/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/impl/ConfiguredLoggerTest.java
new file mode 100644
index 0000000..6a7dc4b
--- /dev/null
+++ b/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/impl/ConfiguredLoggerTest.java
@@ -0,0 +1,184 @@
+package org.eclipse.jetty.logging.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jetty.logging.MavenTestingUtils;
+import org.slf4j.Logger;
+
+public class ConfiguredLoggerTest extends TestCase
+{
+    private void assertAppenders(ConfiguredLogger logger, Class<?>... clazzes)
+    {
+        assertNotNull("Appenders should not be null",logger.getAppenders());
+        assertTrue("Should have appenders",logger.getAppenders().size() >= 1);
+
+        List<String> expectedAppenders = new ArrayList<String>();
+        List<String> actualAppenders = new ArrayList<String>();
+
+        for (Class<?> clazz : clazzes)
+        {
+            expectedAppenders.add(clazz.getName());
+        }
+
+        for (Appender appender : logger.getAppenders())
+        {
+            actualAppenders.add(appender.getClass().getName());
+        }
+
+        // Sort
+        Collections.sort(expectedAppenders);
+        Collections.sort(actualAppenders);
+
+        // Same Size?
+        if (expectedAppenders.size() != actualAppenders.size())
+        {
+            System.out.println("/* Actual */");
+            for (String name : actualAppenders)
+            {
+                System.out.println(name);
+            }
+            System.out.println("/* Expected */");
+            for (String name : expectedAppenders)
+            {
+                System.out.println(name);
+            }
+            assertEquals("Appender count",expectedAppenders.size(),actualAppenders.size());
+        }
+
+        // Same Content?
+        for (int i = 0, n = expectedAppenders.size(); i < n; i++)
+        {
+            assertEquals("Appender[" + i + "]",expectedAppenders.get(i),actualAppenders.get(i));
+        }
+    }
+
+    private void assertSeverityLevel(ConfiguredLogger logger, Severity severity)
+    {
+        assertEquals("Severity",severity,logger.getLevel());
+    }
+
+    public void testRootDebug() throws Exception
+    {
+        Properties props = new Properties();
+        props.setProperty("root.level","DEBUG");
+
+        ConfiguredLogger root = ConfiguredLogger.load(props);
+        assertNotNull("Root Logger should not be null",root);
+        assertSeverityLevel(root,Severity.DEBUG);
+        assertAppenders(root,ConsoleAppender.class);
+    }
+
+    public void testRootTrace() throws Exception
+    {
+        Properties props = new Properties();
+        props.setProperty("root.level","TRACE");
+
+        ConfiguredLogger root = ConfiguredLogger.load(props);
+        assertNotNull("Root Logger should not be null",root);
+        assertSeverityLevel(root,Severity.TRACE);
+        assertAppenders(root,ConsoleAppender.class);
+    }
+
+    public void testRootWarn() throws Exception
+    {
+        Properties props = new Properties();
+        props.setProperty("root.level","WARN");
+
+        ConfiguredLogger root = ConfiguredLogger.load(props);
+        assertNotNull("Root Logger should not be null",root);
+        assertSeverityLevel(root,Severity.WARN);
+        assertAppenders(root,ConsoleAppender.class);
+    }
+
+    public void testSimpleConfig() throws Exception
+    {
+        Properties props = new Properties();
+        props.setProperty("root.level","DEBUG");
+
+        ConfiguredLogger root = ConfiguredLogger.load(props);
+        assertNotNull("Root Logger should not be null",root);
+        assertSeverityLevel(root,Severity.DEBUG);
+        assertAppenders(root,ConsoleAppender.class);
+    }
+
+    public void testCapturedAppender() throws Exception
+    {
+        Properties props = new Properties();
+        props.setProperty("root.level","DEBUG");
+        props.setProperty("root.appenders","test");
+        props.setProperty("appender.test.class",TestAppender.class.getName());
+
+        ConfiguredLogger root = ConfiguredLogger.load(props);
+        assertNotNull("Root Logger should not be null",root);
+        assertSeverityLevel(root,Severity.DEBUG);
+        assertAppenders(root,TestAppender.class);
+    }
+
+    public void testRollingFileAppender() throws Exception
+    {
+        Properties props = new Properties();
+
+        File testLoggingDir = new File(MavenTestingUtils.getTargetTestingDir(this),"logs");
+        testLoggingDir.mkdirs();
+        File logFile = new File(testLoggingDir,"rolling.log");
+
+        props.setProperty("root.level","DEBUG");
+        props.setProperty("root.appenders","roll");
+        props.setProperty("appender.roll.class",RollingFileAppender.class.getName());
+        props.setProperty("appender.roll.filename",logFile.getAbsolutePath());
+        props.setProperty("appender.roll.append","true");
+        props.setProperty("appender.roll.retainDays","120");
+        props.setProperty("appender.roll.zone","GMT");
+        props.setProperty("appender.roll.dateFormat","yyyy-MM-dd");
+        props.setProperty("appender.roll.backupFormat","HH-mm-ss.SSS");
+
+        ConfiguredLogger root = ConfiguredLogger.load(props);
+        assertNotNull("Root Logger should not be null",root);
+        assertSeverityLevel(root,Severity.DEBUG);
+        assertAppenders(root,RollingFileAppender.class);
+
+        RollingFileAppender actualAppender = (RollingFileAppender)root.getAppenders().get(0);
+        assertEquals("RollingFileAppender.filename",logFile.getAbsolutePath(),actualAppender.getFilename());
+        assertEquals("RollingFileAppender.append",true,actualAppender.isAppend());
+        assertEquals("RollingFileAppender.retainDays",120,actualAppender.getRetainDays());
+        assertEquals("RollingFileAppender.zone","GMT",actualAppender.getZone().getID());
+        assertEquals("RollingFileAppender.dateFormat","yyyy-MM-dd",actualAppender.getDateFormat());
+        assertEquals("RollingFileAppender.backupFormat","HH-mm-ss.SSS",actualAppender.getBackupFormat());
+    }
+
+    public void testGetConfiguredLogger() throws IOException
+    {
+        Properties props = new Properties();
+        props.setProperty("root.level","DEBUG");
+        props.setProperty("root.appenders","console");
+        props.setProperty("logger.org.eclipse.jetty.logging.level","WARN");
+        props.setProperty("logger.org.eclipse.jetty.logging.appenders","test");
+        props.setProperty("appender.test.class",TestAppender.class.getName());
+        props.setProperty("appender.console.class",ConsoleAppender.class.getName());
+
+        ConfiguredLogger root = ConfiguredLogger.load(props);
+        assertNotNull("Root Logger should not be null",root);
+        assertEquals("Root Logger.name",Logger.ROOT_LOGGER_NAME,root.getName());
+        assertSeverityLevel(root,Severity.DEBUG);
+        assertAppenders(root,ConsoleAppender.class);
+
+        ConfiguredLogger jettyLogger = root.getConfiguredLogger("org.eclipse.jetty");
+        assertNotNull("Jetty Logger should not be null",jettyLogger);
+        assertEquals("Jetty Logger.name","org.eclipse.jetty",jettyLogger.getName());
+        assertSeverityLevel(jettyLogger,Severity.DEBUG);
+        assertAppenders(jettyLogger,ConsoleAppender.class);
+
+        ConfiguredLogger implLogger = root.getConfiguredLogger("org.eclipse.jetty.logging.impl");
+        assertNotNull("Jetty Logging Impl Logger should not be null",implLogger);
+        assertEquals("Jetty Logging Impl Logger.name","org.eclipse.jetty.logging.impl",implLogger.getName());
+        assertSeverityLevel(implLogger,Severity.WARN);
+        assertAppenders(implLogger,ConsoleAppender.class,TestAppender.class);
+    }
+}
diff --git a/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/impl/SeverityTest.java b/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/impl/SeverityTest.java
new file mode 100644
index 0000000..dcf1563
--- /dev/null
+++ b/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/impl/SeverityTest.java
@@ -0,0 +1,28 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+package org.eclipse.jetty.logging.impl;
+
+import junit.framework.TestCase;
+
+public class SeverityTest extends TestCase
+{
+    public void testIsEnabled()
+    {
+        assertTrue("DEBUG.isEnabled(INFO)",Severity.DEBUG.isEnabled(Severity.INFO));
+        assertTrue("DEBUG.isEnabled(DEBUG)",Severity.DEBUG.isEnabled(Severity.DEBUG));
+        assertFalse("DEBUG.isEnabled(TRACE)",Severity.DEBUG.isEnabled(Severity.TRACE));
+    }
+}
diff --git a/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/impl/TestAppender.java b/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/impl/TestAppender.java
new file mode 100644
index 0000000..039fd6d
--- /dev/null
+++ b/jetty-centralized-logging/src/test/java/org/eclipse/jetty/logging/impl/TestAppender.java
@@ -0,0 +1,79 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+package org.eclipse.jetty.logging.impl;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test Appender, records the logging events.
+ */
+public class TestAppender implements Appender
+{
+    class LogEvent
+    {
+        String date;
+        int ms;
+        Severity severity;
+        String name;
+        String message;
+        Throwable t;
+
+        public LogEvent(String date, int ms, Severity severity, String name, String message, Throwable t)
+        {
+            super();
+            this.date = date;
+            this.ms = ms;
+            this.severity = severity;
+            this.name = name;
+            this.message = message;
+            this.t = t;
+        }
+    }
+
+    private List<LogEvent> events = new ArrayList<LogEvent>();
+
+    public void append(String date, int ms, Severity severity, String name, String message, Throwable t)
+    {
+        events.add(new LogEvent(date,ms,severity,name,message,t));
+    }
+
+    public void close() throws IOException
+    {
+        /* nothing to do here */
+    }
+
+    public List<LogEvent> getEvents()
+    {
+        return events;
+    }
+
+    public void open() throws IOException
+    {
+        /* nothing to do here */
+    }
+
+    public void reset()
+    {
+        events.clear();
+    }
+
+    public void setProperty(String key, String value) throws Exception
+    {
+        /* nothing to do here */
+    }
+}
diff --git a/jetty-centralized-logging/src/test/resources/contexts/context-dummy-webapp-slf4j.xml b/jetty-centralized-logging/src/test/resources/contexts/context-dummy-webapp-slf4j.xml
index a586c86..515b9d3 100644
--- a/jetty-centralized-logging/src/test/resources/contexts/context-dummy-webapp-slf4j.xml
+++ b/jetty-centralized-logging/src/test/resources/contexts/context-dummy-webapp-slf4j.xml
@@ -1,8 +1,8 @@
 <?xml version="1.0"  encoding="ISO-8859-1"?>
 <!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
 <Configure class="org.eclipse.jetty.webapp.WebAppContext">
-    <Set name="contextPath">/java</Set>
+    <Set name="contextPath">/slf4j</Set>
     <Set name="war">
-        <Property name="test.webapps" default="." />/dummy-webapp-logging-java.war
+        <Property name="test.webapps" default="." />/dummy-webapp-logging-slf4j.war
     </Set>
 </Configure>
diff --git a/jetty-centralized-logging/src/test/resources/jetty.xml b/jetty-centralized-logging/src/test/resources/jetty.xml
index 7c70523..83b1b3c 100644
--- a/jetty-centralized-logging/src/test/resources/jetty.xml
+++ b/jetty-centralized-logging/src/test/resources/jetty.xml
@@ -98,7 +98,7 @@
     
     <Call name="addLifeCycle">
       <Arg>
-        <New class="org.eclipse.jetty.deploy.ContextDeployer">
+        <New id="ContextDeployer" class="org.eclipse.jetty.deploy.ContextDeployer">
           <Set name="contexts"><Ref id="WebappContexts" /></Set>
           <Set name="configurationDir">
             <Property name="test.resourcesdir" default="src/test/resources" />/contexts
diff --git a/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-commons.war b/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-commons.war
index ad7bddd..edc5c6c 100644
--- a/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-commons.war
+++ b/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-commons.war
Binary files differ
diff --git a/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-java.war b/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-java.war
index 0727c85..454539d 100644
--- a/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-java.war
+++ b/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-java.war
Binary files differ
diff --git a/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-log4j.war b/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-log4j.war
index aca0079..5a260ca 100644
--- a/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-log4j.war
+++ b/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-log4j.war
Binary files differ
diff --git a/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-slf4j.war b/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-slf4j.war
index bb575dd..4390233 100644
--- a/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-slf4j.war
+++ b/jetty-centralized-logging/src/test/resources/webapps/dummy-webapp-logging-slf4j.war
Binary files differ