http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/spi/Log4JULogger.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/spi/Log4JULogger.java 
b/src/main/java/org/apache/log4j/spi/Log4JULogger.java
new file mode 100644
index 0000000..2476810
--- /dev/null
+++ b/src/main/java/org/apache/log4j/spi/Log4JULogger.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.ULogger;
+import org.apache.log4j.helpers.MessageFormatter;
+import org.apache.log4j.Level;
+
+
+/**
+ * An implementation of ULogger on org.apache.log4j.Logger.
+ */
+public final class Log4JULogger implements ULogger {
+
+    /**
+     * Wrapped log4j logger.
+     */
+    private final Logger logger;
+
+    /**
+     * Create a new instance.
+     *
+     * @param l logger, may not be null.
+     */
+    public Log4JULogger(final Logger l) {
+        super();
+        if (l == null) {
+            throw new NullPointerException("l");
+        }
+        logger = l;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isDebugEnabled() {
+        return logger.isDebugEnabled();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void debug(final Object msg) {
+        logger.debug(msg);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void debug(final Object parameterizedMsg,
+                      final Object param1) {
+        if (logger.isDebugEnabled()) {
+            logger.debug(MessageFormatter.format(
+                    parameterizedMsg.toString(), param1));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void debug(final String parameterizedMsg,
+                      final Object param1,
+                      final Object param2) {
+        if (logger.isDebugEnabled()) {
+            logger.debug(MessageFormatter.format(
+                    parameterizedMsg.toString(), param1, param2));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void debug(final Object msg,
+                      final Throwable t) {
+        logger.debug(msg, t);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isInfoEnabled() {
+        return logger.isInfoEnabled();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void info(final Object msg) {
+        logger.info(msg);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void info(final Object parameterizedMsg,
+                     final Object param1) {
+        if (logger.isInfoEnabled()) {
+            logger.info(MessageFormatter.format(
+                    parameterizedMsg.toString(), param1));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void info(final String parameterizedMsg,
+                     final Object param1,
+                     final Object param2) {
+        if (logger.isInfoEnabled()) {
+            logger.info(MessageFormatter.format(
+                    parameterizedMsg.toString(),
+                    param1,
+                    param2));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void info(final Object msg, final Throwable t) {
+        logger.info(msg, t);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isWarnEnabled() {
+        return logger.isEnabledFor(Level.WARN);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void warn(final Object msg) {
+        logger.warn(msg);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void warn(final Object parameterizedMsg,
+                     final Object param1) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            logger.warn(MessageFormatter.format(
+                    parameterizedMsg.toString(), param1));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void warn(final String parameterizedMsg,
+                     final Object param1,
+                     final Object param2) {
+        if (logger.isEnabledFor(Level.WARN)) {
+            logger.warn(MessageFormatter.format(
+                    parameterizedMsg.toString(), param1, param2));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void warn(final Object msg, final Throwable t) {
+        logger.warn(msg, t);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isErrorEnabled() {
+        return logger.isEnabledFor(Level.ERROR);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void error(final Object msg) {
+        logger.error(msg);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void error(final Object parameterizedMsg, final Object param1) {
+        if (logger.isEnabledFor(Level.ERROR)) {
+            logger.error(MessageFormatter.format(
+                    parameterizedMsg.toString(), param1));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void error(final String parameterizedMsg,
+                      final Object param1,
+                      final Object param2) {
+        if (logger.isEnabledFor(Level.ERROR)) {
+            logger.error(MessageFormatter.format(
+                    parameterizedMsg.toString(), param1, param2));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void error(final Object msg, final Throwable t) {
+        logger.error(msg, t);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/spi/LoggerEventListener.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/spi/LoggerEventListener.java 
b/src/main/java/org/apache/log4j/spi/LoggerEventListener.java
new file mode 100644
index 0000000..b39f728
--- /dev/null
+++ b/src/main/java/org/apache/log4j/spi/LoggerEventListener.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Logger;
+
+
+/**
+  Interface used to listen for Logger related events such as
+  add/remove appender or changing levels.  Clients register an instance of
+  the interface and the instance is called back when the various events occur.
+
+  LoggerRepository provides methods for adding and removing
+  LoggerEventListener instances.
+
+  When implementing the methods of this interface, it is useful to remember
+  that the Logger can access the repository using its getRepository()
+  method.
+
+  @author Ceki Gülcü
+  @author Mark Womack
+*/
+public interface LoggerEventListener {
+  /**
+    Called when an appender is added to the logger.
+
+    @param logger The logger to which the appender was added.
+    @param appender The appender added to the logger. */
+  void appenderAddedEvent(Logger logger, Appender appender);
+
+  /**
+    Called when an appender is removed from the logger.
+
+    @param logger The logger from which the appender was removed.
+    @param appender The appender removed from the logger. */
+  void appenderRemovedEvent(Logger logger, Appender appender);
+
+  /**
+    Called when level changed on the logger.
+
+    @param logger The logger that changed levels. */
+  void levelChangedEvent(Logger logger);
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/spi/LoggerRepositoryEventListener.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/log4j/spi/LoggerRepositoryEventListener.java 
b/src/main/java/org/apache/log4j/spi/LoggerRepositoryEventListener.java
new file mode 100644
index 0000000..773a497
--- /dev/null
+++ b/src/main/java/org/apache/log4j/spi/LoggerRepositoryEventListener.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+
+/**
+  Interface used to listen for LoggerRepository related
+  events such as startup, reset, and shutdown.  Clients register
+  an instance of the interface and the instance is called back
+  when the various events occur.
+
+  LoggerRepository provides methods for adding and removing
+  LoggerRepositoryEventListener instances.
+
+  @author Ceki Gülcü
+  @author Mark Womack
+*/
+public interface LoggerRepositoryEventListener {
+  /**
+    Called when the repository configuration is reset.
+   @param repository repository
+   */
+  void configurationResetEvent(LoggerRepository repository);
+
+  /**
+    Called when the repository configuration is changed.
+   @param repository repository
+   */
+  void configurationChangedEvent(LoggerRepository repository);
+
+  /**
+    Called when the repository is shutdown. When this method is
+    invoked, the repository is still valid (ie it has not been
+    shutdown, but will be after this method returns).
+    @param repository repository.
+   */
+  void shutdownEvent(LoggerRepository repository);
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/spi/LoggerRepositoryEx.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/spi/LoggerRepositoryEx.java 
b/src/main/java/org/apache/log4j/spi/LoggerRepositoryEx.java
new file mode 100644
index 0000000..c079a2c
--- /dev/null
+++ b/src/main/java/org/apache/log4j/spi/LoggerRepositoryEx.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Category;
+import org.apache.log4j.Logger;
+import org.apache.log4j.plugins.PluginRegistry;
+import org.apache.log4j.scheduler.Scheduler;
+
+import java.util.List;
+import java.util.Map;
+
+
+/**
+   A <code>LoggerRepository</code> is used to create and retrieve
+   <code>Loggers</code>. The relation between loggers in a repository
+   depends on the repository but typically loggers are arranged in a
+   named hierarchy.
+
+   <p>In addition to the creational methods, a
+   <code>LoggerRepository</code> can be queried for existing loggers,
+   can act as a point of registry for events related to loggers.
+
+   @author Ceki G&uuml;lc&uuml;
+   @author Mark Womack
+   @author Curt Arnold
+   */
+public interface LoggerRepositoryEx extends LoggerRepository {
+  /**
+    Add a {@link LoggerRepositoryEventListener} to the repository. The
+    listener will be called when repository events occur.
+     @param listener event listener, may not be null.
+    */
+  void addLoggerRepositoryEventListener(
+    LoggerRepositoryEventListener listener);
+
+  /**
+    Remove a {@link LoggerRepositoryEventListener} from the repository.
+   @param listener listener.
+    */
+  void removeLoggerRepositoryEventListener(
+    LoggerRepositoryEventListener listener);
+
+  /**
+    Add a {@link LoggerEventListener} to the repository. The  listener
+    will be called when repository events occur.
+   @param listener listener, may not be null.
+    */
+  void addLoggerEventListener(LoggerEventListener listener);
+
+  /**
+    Remove a {@link LoggerEventListener} from the repository.
+   @param listener listener, may not be null.
+    */
+  void removeLoggerEventListener(LoggerEventListener listener);
+
+  /**
+   * Get the name of this logger repository.
+   * @return name, may not be null.
+   */
+  String getName();
+
+  /**
+   * A logger repository is a named entity.
+   * @param repoName new name, may not be null.
+   */
+  void setName(String repoName);
+
+  /**
+   * Is the current configuration of the repository in its original (pristine)
+   * state?
+   * @return true if repository is in original state.
+   *
+   */
+  boolean isPristine();
+
+  /**
+   *  Set the pristine flag.
+   * @param state state
+   *  @see #isPristine
+   */
+  void setPristine(boolean state);
+
+  /**
+    Requests that a appender removed event be sent to any registered
+    {@link LoggerEventListener}.
+    @param logger The logger from which the appender was removed.
+    @param appender The appender removed from the logger.
+    */
+  void fireRemoveAppenderEvent(Category logger, Appender appender);
+
+  /**
+    Requests that a level changed event be sent to any registered
+    {@link LoggerEventListener}.
+    @param logger The logger which changed levels.
+    */
+  void fireLevelChangedEvent(Logger logger);
+
+  /**
+    Requests that a configuration changed event be sent to any registered
+    {@link LoggerRepositoryEventListener}.
+    */
+  void fireConfigurationChangedEvent();
+
+  /**
+   * Return the PluginRegisty for this LoggerRepository.
+   * @return plug in registry.
+   */
+  PluginRegistry getPluginRegistry();
+
+  /**
+   * Return the {@link Scheduler} for this LoggerRepository.
+   * @return scheduler.
+   */
+  Scheduler getScheduler();
+
+  /**
+   * Get the properties specific for this repository.
+   * @return property map.
+   */
+  Map getProperties();
+
+  /**
+   * Get the property of this repository.
+   * @param key property key.
+   * @return key value or null if not set.
+   */
+  String getProperty(String key);
+
+  /**
+   * Set a property of this repository.
+   * @param key key, may not be null.
+   * @param value new value, if null, property will be removed.
+   */
+  void setProperty(String key, String value);
+
+  /**
+   * Errors which cannot be logged, go to the error list.
+   *
+   * @return List
+   */
+  List getErrorList();
+
+  /**
+   * Errors which cannot be logged, go to the error list.
+   *
+   * @param errorItem an ErrorItem to add to the error list
+   */
+  void addErrorItem(ErrorItem errorItem);
+
+  /**
+   * A LoggerRepository can also act as a store for various objects used
+   * by log4j components.
+   *
+   * @param key key, may not be null.
+   * @return The object stored under 'key'.
+   */
+  Object getObject(String key);
+
+  /**
+   * Store an object under 'key'. If no object can be found, null is returned.
+   *
+   * @param key key, may not be null.
+   * @param value value, may be null.
+   */
+  void putObject(String key, Object value);
+
+  /**
+   * Sets the logger factory used by LoggerRepository.getLogger(String).
+   * @param loggerFactory factory to use, may not be null
+   */
+  void setLoggerFactory(LoggerFactory loggerFactory);
+
+  /**
+   * Returns the logger factory used by
+   * LoggerRepository.getLogger(String).
+   *
+   * @return non-null factory
+   */
+  LoggerFactory getLoggerFactory();
+
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/spi/NOPULogger.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/spi/NOPULogger.java 
b/src/main/java/org/apache/log4j/spi/NOPULogger.java
new file mode 100644
index 0000000..98fe537
--- /dev/null
+++ b/src/main/java/org/apache/log4j/spi/NOPULogger.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.spi;
+
+import org.apache.log4j.ULogger;
+
+
+/**
+ * A no operation (NOP) implementation of {@link ULogger}.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public final class NOPULogger implements ULogger {
+
+    /**
+     * The unique instance of NOPLogger.
+     */
+    public static final NOPULogger NOP_LOGGER = new NOPULogger();
+
+    /**
+     * There is no point in people creating multiple instances of NullLogger.
+     * Hence, the private access modifier.
+     */
+    private NOPULogger() {
+        super();
+    }
+
+    /**
+     * Get instance.
+     * @param name logger name.
+     * @return logger.
+     */
+    public static NOPULogger getLogger(final String name) {
+        return NOP_LOGGER;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isDebugEnabled() {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void debug(final Object msg) {
+        // NOP
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void debug(final Object parameterizedMsg, final Object param1) {
+        // NOP
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void debug(final String parameterizedMsg,
+                      final Object param1,
+                      final Object param2) {
+        // NOP
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void debug(final Object msg, final Throwable t) {
+        // NOP
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isInfoEnabled() {
+        // NOP
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void info(final Object msg) {
+        // NOP
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void info(final Object parameterizedMsg, final Object param1) {
+        // NOP
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void info(final String parameterizedMsg,
+                     final Object param1, final Object param2) {
+        // NOP
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void info(final Object msg, final Throwable t) {
+        // NOP
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isWarnEnabled() {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void warn(final Object msg) {
+        // NOP
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void warn(final Object parameterizedMsg,
+                     final Object param1) {
+        // NOP
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void warn(final String parameterizedMsg,
+                     final Object param1,
+                     final Object param2) {
+        // NOP
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void warn(final Object msg, final Throwable t) {
+        // NOP
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isErrorEnabled() {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void error(final Object msg) {
+        // NOP
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void error(final Object parameterizedMsg, final Object param1) {
+        // NOP
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void error(final String parameterizedMsg,
+                      final Object param1,
+                      final Object param2) {
+        // NOP
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void error(final Object msg, final Throwable t) {
+        // NOP
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/spi/SimpleULogger.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/spi/SimpleULogger.java 
b/src/main/java/org/apache/log4j/spi/SimpleULogger.java
new file mode 100644
index 0000000..714a57a
--- /dev/null
+++ b/src/main/java/org/apache/log4j/spi/SimpleULogger.java
@@ -0,0 +1,305 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.spi;
+
+import org.apache.log4j.ULogger;
+import org.apache.log4j.helpers.MessageFormatter;
+
+
+/**
+ * A simple implementation that logs messages of level INFO or higher on
+ * the console (<code>System.out</code>).
+ * <p>
+ * The output includes the relative time in milliseconds, thread name, level,
+ * logger name, and the message followed by the line separator for the host.
+ * In log4j terms it amounts to the "%r  [%t] %level %logger - %m%n" pattern.
+ * <pre>
+176 [main] INFO examples.Sort - Populating an array of 2 elements in reverse.
+225 [main] INFO examples.SortAlgo - Entered the sort method.
+304 [main] INFO SortAlgo.DUMP - Dump of interger array:
+317 [main] INFO SortAlgo.DUMP - Element [0] = 0
+331 [main] INFO SortAlgo.DUMP - Element [1] = 1
+343 [main] INFO examples.Sort - The next log statement should be an error msg.
+346 [main] ERROR SortAlgo.DUMP - Tried to dump an uninitialized array.
+        at org.log4j.examples.SortAlgo.dump(SortAlgo.java:58)
+        at org.log4j.examples.Sort.main(Sort.java:64)
+467 [main] INFO  examples.Sort - Exiting main method.
+</pre>
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public final class SimpleULogger implements ULogger {
+
+    /**
+     * Logger name.
+     */
+  private final String loggerName;
+
+
+  /**
+   * Mark the time when this class gets loaded into memory.
+   */
+  private static long startTime = System.currentTimeMillis();
+
+    /**
+     * Line separator.
+     */
+  public static final String LINE_SEPARATOR
+            = System.getProperty("line.separator");
+
+    /**
+     * INFO string literal.
+     */
+  private static final String INFO_STR = "INFO";
+    /**
+     * WARN string literal.
+     */
+  private static final String WARN_STR = "WARN";
+    /**
+     * ERROR string literal.
+     */
+  private static final String ERROR_STR = "ERROR";
+
+  /**
+   * Constructor is private to force construction through getLogger.
+   * @param name logger name
+   */
+  private SimpleULogger(final String name) {
+    super();
+    this.loggerName = name;
+  }
+
+  /**
+   * Creates a new instance.
+   *
+   * @param name logger name
+   * @return  logger.
+   */
+  public static SimpleULogger getLogger(final String name) {
+      return new SimpleULogger(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isDebugEnabled() {
+    return false;
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public void debug(final Object msg) {
+    // NOP
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public void debug(final Object parameterizedMsg, final Object param1) {
+    // NOP
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public void debug(final String parameterizedMsg,
+                    final Object param1,
+                    final Object param2) {
+    // NOP
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public void debug(final Object msg, final Throwable t) {
+    // NOP
+  }
+
+  /**
+   * This is our internal implementation for logging regular 
(non-parameterized)
+   * log messages.
+   *
+   * @param level level
+   * @param message message
+   * @param t throwable
+   */
+  private void log(final String level,
+                   final String message,
+                   final Throwable t) {
+    StringBuffer buf = new StringBuffer();
+
+    long millis  = System.currentTimeMillis();
+    buf.append(millis - startTime);
+
+    buf.append(" [");
+    buf.append(Thread.currentThread().getName());
+    buf.append("] ");
+
+    buf.append(level);
+    buf.append(" ");
+
+    buf.append(loggerName);
+    buf.append(" - ");
+
+    buf.append(message);
+
+    buf.append(LINE_SEPARATOR);
+
+    System.out.print(buf.toString());
+    if (t != null) {
+      t.printStackTrace(System.out);
+    }
+    System.out.flush();
+  }
+  /**
+   * For parameterized messages, first substitute parameters and then log.
+   *
+   * @param level level
+   * @param parameterizedMsg message pattern
+   * @param param1 param1
+   * @param param2 param2
+   */
+  private void parameterizedLog(final String level,
+                                final Object parameterizedMsg,
+                                final Object param1,
+                                final Object param2) {
+    if (parameterizedMsg instanceof String) {
+      String msgStr = (String) parameterizedMsg;
+      msgStr = MessageFormatter.format(msgStr, param1, param2);
+      log(level, msgStr, null);
+    } else {
+      // To be failsafe, we handle the case where 'messagePattern' is not
+      // a String. Unless the user makes a mistake, this should not happen.
+      log(level, parameterizedMsg.toString(), null);
+    }
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public boolean isInfoEnabled() {
+    return true;
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public void info(final Object msg) {
+    log(INFO_STR, msg.toString(), null);
+  }
+
+
+    /**
+     * {@inheritDoc}
+     */
+  public void info(final Object parameterizedMsg, final Object param1) {
+    parameterizedLog(INFO_STR, parameterizedMsg, param1, null);
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public void info(final String parameterizedMsg,
+                   final Object param1,
+                   final Object param2) {
+    parameterizedLog(INFO_STR, parameterizedMsg, param1, param2);
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public void info(final Object msg, final Throwable t) {
+    log(INFO_STR, msg.toString(), t);
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public boolean isWarnEnabled() {
+    return true;
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public void warn(final Object msg) {
+    log(WARN_STR, msg.toString(), null);
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public void warn(final Object parameterizedMsg, final Object param1) {
+    parameterizedLog(WARN_STR, parameterizedMsg, param1, null);
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public void warn(final String parameterizedMsg,
+                   final Object param1,
+                   final Object param2) {
+    parameterizedLog(WARN_STR, parameterizedMsg, param1, param2);
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public void warn(final Object msg, final Throwable t) {
+    log(WARN_STR, msg.toString(), t);
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public boolean isErrorEnabled() {
+    return true;
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public void error(final Object msg) {
+    log(ERROR_STR, msg.toString(), null);
+  }
+
+
+    /**
+     * {@inheritDoc}
+     */
+  public void error(final Object parameterizedMsg, final Object param1) {
+    parameterizedLog(ERROR_STR, parameterizedMsg, param1, null);
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public void error(final String parameterizedMsg,
+                    final Object param1,
+                    final Object param2) {
+    parameterizedLog(ERROR_STR, parameterizedMsg, param1, param2);
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public void error(final Object msg, final Throwable t) {
+    log(ERROR_STR, msg.toString(), t);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/spi/Thresholdable.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/spi/Thresholdable.java 
b/src/main/java/org/apache/log4j/spi/Thresholdable.java
new file mode 100644
index 0000000..222345f
--- /dev/null
+++ b/src/main/java/org/apache/log4j/spi/Thresholdable.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Level;
+
+/**
+ * An interface that defines the required methods for supporting the
+ * setting and getting of a level threshold.  Components should implement
+ * this interface if logging events they process should meet a certain
+ * threshold before being processed further.  Examples of this are
+ * Appenders and Receivers which will not process logging events unless
+ * the event level is at or greater than a set threshold level.
+ *
+ * @author Paul Smith ([email protected])
+ * @author Mark Womack
+ */
+public interface Thresholdable {
+    /**
+     * Sets the component theshold to the given level.
+     *
+     * @param level The threshold level events must equal or be greater
+     *              than before further processing can be done.
+     */
+    void setThreshold(Level level);
+
+    /**
+     * Gets the current threshold setting of the component.
+     *
+     * @return Level The current threshold level of the component.
+     */
+    Level getThreshold();
+
+    /**
+     * Returns true if the given level is equals or greater than the current
+     * threshold value of the component.
+     *
+     * @param level The level to test against the component threshold.
+     * @return boolean True if level is equal or greater than the
+     *         component threshold.
+     */
+    boolean isAsSevereAsThreshold(Level level);
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/varia/ListModelAppender.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/varia/ListModelAppender.java 
b/src/main/java/org/apache/log4j/varia/ListModelAppender.java
new file mode 100644
index 0000000..ccbc9be
--- /dev/null
+++ b/src/main/java/org/apache/log4j/varia/ListModelAppender.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.varia;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.spi.LoggingEvent;
+
+import javax.swing.DefaultListModel;
+import javax.swing.ListModel;
+
+
+/**
+ * A very basic appender that takes the events and stores them in to a
+ * ListModel for late retrieval.
+ *
+ *
+ * @author Paul Smith ([email protected])
+ *
+ */
+public final class ListModelAppender extends AppenderSkeleton {
+    /**
+     * Default list model.
+     */
+  private final DefaultListModel model = new DefaultListModel();
+
+  /**
+   * Constructs a ListModelAppender.
+   */
+  public ListModelAppender() {
+      super(true);
+  }
+  /**
+   * Returns a reference to the ListModel that contains all the LoggingEvents
+   * that have been appended to this class.
+   *
+   * @return the list model
+   */
+  public ListModel getModel() {
+    return model;
+  }
+
+    /** {@inheritDoc} */
+  protected void append(final LoggingEvent event) {
+    model.addElement(event);
+  }
+
+    /** {@inheritDoc} */
+  public void close() {
+    clearModel();
+  }
+
+  /**
+   * Removes all the Events from the model.
+   */
+  public void clearModel() {
+    model.clear();
+  }
+
+    /** {@inheritDoc} */
+  public boolean requiresLayout() {
+      return false;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/varia/LogFilePatternReceiver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/varia/LogFilePatternReceiver.java 
b/src/main/java/org/apache/log4j/varia/LogFilePatternReceiver.java
new file mode 100644
index 0000000..4430205
--- /dev/null
+++ b/src/main/java/org/apache/log4j/varia/LogFilePatternReceiver.java
@@ -0,0 +1,1043 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.varia;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.helpers.Constants;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.rule.ExpressionRule;
+import org.apache.log4j.rule.Rule;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.ThrowableInformation;
+
+/**
+ * LogFilePatternReceiver can parse and tail log files, converting entries into
+ * LoggingEvents.  If the file doesn't exist when the receiver is initialized, 
the
+ * receiver will look for the file once every 10 seconds.
+ * <p>
+ * This receiver relies on java.util.regex features to perform the parsing of 
text in the
+ * log file, however the only regular expression field explicitly supported is 
+ * a glob-style wildcard used to ignore fields in the log file if needed.  All 
other
+ * fields are parsed by using the supplied keywords.
+ * <p>
+ * <b>Features:</b><br>
+ * - specify the URL of the log file to be processed<br>
+ * - specify the timestamp format in the file (if one exists, using patterns 
from {@link java.text.SimpleDateFormat})<br>
+ * - specify the pattern (logFormat) used in the log file using keywords, a 
wildcard character (*) and fixed text<br>
+ * - 'tail' the file (allows the contents of the file to be continually read 
and new events processed)<br>
+ * - supports the parsing of multi-line messages and exceptions
+ * - 'hostname' property set to URL host (or 'file' if not available)
+ * - 'application' property set to URL path (or value of fileURL if not 
available) 
+ *<p>
+ * <b>Keywords:</b><br>
+ * TIMESTAMP<br>
+ * LOGGER<br>
+ * LEVEL<br>
+ * THREAD<br>
+ * CLASS<br>
+ * FILE<br>
+ * LINE<br>
+ * METHOD<br>
+ * RELATIVETIME<br>
+ * MESSAGE<br>
+ * NDC<br>
+ * PROP(key)<br>
+ * <p>
+ * Use a * to ignore portions of the log format that should be ignored
+ * <p>
+ * Example:<br>
+ * If your file's patternlayout is this:<br>
+ * <b>%d %-5p [%t] %C{2} (%F:%L) - %m%n</b>
+ *<p>
+ * specify this as the log format:<br>
+ * <b>TIMESTAMP LEVEL [THREAD] CLASS (FILE:LINE) - MESSAGE</b>
+ *<p>
+ * To define a PROPERTY field, use PROP(key)
+ * <p>
+ * Example:<br> 
+ * If you used the RELATIVETIME pattern layout character in the file, 
+ * you can use PROP(RELATIVETIME) in the logFormat definition to assign 
+ * the RELATIVETIME field as a property on the event.
+ * <p>
+ * If your file's patternlayout is this:<br>
+ * <b>%r [%t] %-5p %c %x - %m%n</b>
+ *<p>
+ * specify this as the log format:<br>
+ * <b>PROP(RELATIVETIME) [THREAD] LEVEL LOGGER * - MESSAGE</b>
+ * <p>
+ * Note the * - it can be used to ignore a single word or sequence of words in 
the log file
+ * (in order for the wildcard to ignore a sequence of words, the text being 
ignored must be
+ *  followed by some delimiter, like '-' or '[') - ndc is being ignored in the 
following example.
+ * <p>
+ * Assign a filterExpression in order to only process events which match a 
filter.
+ * If a filterExpression is not assigned, all events are processed.
+ *<p>
+ * <b>Limitations:</b><br>
+ * - no support for the single-line version of throwable supported by 
patternlayout<br>
+ *   (this version of throwable will be included as the last line of the 
message)<br>
+ * - the relativetime patternLayout character must be set as a property: 
PROP(RELATIVETIME)<br>
+ * - messages should appear as the last field of the logFormat because the 
variability in message content<br>
+ * - exceptions are converted if the exception stack trace (other than the 
first line of the exception)<br>
+ *   is stored in the log file with a tab followed by the word 'at' as the 
first characters in the line<br>
+ * - tailing may fail if the file rolls over. 
+ *<p>
+ * <b>Example receiver configuration settings</b> (add these as params, 
specifying a LogFilePatternReceiver 'plugin'):<br>
+ * param: "timestampFormat" value="yyyy-MM-d HH:mm:ss,SSS"<br>
+ * param: "logFormat" value="PROP(RELATIVETIME) [THREAD] LEVEL LOGGER * - 
MESSAGE"<br>
+ * param: "fileURL" value="file:///c:/events.log"<br>
+ * param: "tailing" value="true"
+ *<p>
+ * This configuration will be able to process these sample events:<br>
+ * 710    [       Thread-0] DEBUG                   first.logger first - 
<test>   <test2>something here</test2>   <test3 blah=something/>   <test4>      
 <test5>something else</test5>   </test4></test><br>
+ * 880    [       Thread-2] DEBUG                   first.logger third - 
<test>   <test2>something here</test2>   <test3 blah=something/>   <test4>      
 <test5>something else</test5>   </test4></test><br>
+ * 880    [       Thread-0] INFO                    first.logger first - 
infomsg-0<br>
+ * java.lang.Exception: someexception-first<br>
+ *     at Generator2.run(Generator2.java:102)<br>
+ *
+ *@author Scott Deboy
+ */
+public class LogFilePatternReceiver extends Receiver {
+  private final List keywords = new ArrayList();
+
+  private static final String PROP_START = "PROP(";
+  private static final String PROP_END = ")";
+
+  private static final String LOGGER = "LOGGER";
+  private static final String MESSAGE = "MESSAGE";
+  private static final String TIMESTAMP = "TIMESTAMP";
+  private static final String NDC = "NDC";
+  private static final String LEVEL = "LEVEL";
+  private static final String THREAD = "THREAD";
+  private static final String CLASS = "CLASS";
+  private static final String FILE = "FILE";
+  private static final String LINE = "LINE";
+  private static final String METHOD = "METHOD";
+  
+  private static final String DEFAULT_HOST = "file";
+  
+  //all lines other than first line of exception begin with tab followed by 
'at' followed by text
+  private static final String EXCEPTION_PATTERN = "^\\s+at.*";
+  private static final String REGEXP_DEFAULT_WILDCARD = ".*?";
+  private static final String REGEXP_GREEDY_WILDCARD = ".*";
+  private static final String PATTERN_WILDCARD = "*";
+  private static final String NOSPACE_GROUP = "(\\S*\\s*?)";
+  private static final String DEFAULT_GROUP = "(" + REGEXP_DEFAULT_WILDCARD + 
")";
+  private static final String GREEDY_GROUP = "(" + REGEXP_GREEDY_WILDCARD + 
")";
+  private static final String MULTIPLE_SPACES_REGEXP = "[ ]+";
+  private final String newLine = System.getProperty("line.separator");
+
+  private final String[] emptyException = new String[] { "" };
+
+  private SimpleDateFormat dateFormat;
+  private String timestampFormat = "yyyy-MM-d HH:mm:ss,SSS";
+  private String logFormat;
+  private String customLevelDefinitions;
+  private String fileURL;
+  private String host;
+  private String path;
+  private boolean tailing;
+  private String filterExpression;
+  private long waitMillis = 2000; //default 2 seconds
+
+  private static final String VALID_DATEFORMAT_CHARS = "GyMwWDdFEaHkKhmsSzZ";
+  private static final String VALID_DATEFORMAT_CHAR_PATTERN = "[" + 
VALID_DATEFORMAT_CHARS + "]";
+
+  private Rule expressionRule;
+
+  private Map currentMap;
+  private List additionalLines;
+  private List matchingKeywords;
+
+  private String regexp;
+  private Reader reader;
+  private Pattern regexpPattern;
+  private Pattern exceptionPattern;
+  private String timestampPatternText;
+
+  private boolean useCurrentThread;
+  public static final int MISSING_FILE_RETRY_MILLIS = 10000;
+  private boolean appendNonMatches;
+  private final Map customLevelDefinitionMap = new HashMap();
+
+    public LogFilePatternReceiver() {
+    keywords.add(TIMESTAMP);
+    keywords.add(LOGGER);
+    keywords.add(LEVEL);
+    keywords.add(THREAD);
+    keywords.add(CLASS);
+    keywords.add(FILE);
+    keywords.add(LINE);
+    keywords.add(METHOD);
+    keywords.add(MESSAGE);
+    keywords.add(NDC);
+    try {
+        exceptionPattern = Pattern.compile(EXCEPTION_PATTERN);
+    } catch (PatternSyntaxException pse) {
+        //shouldn't happen
+    }
+  }
+
+  /**
+   * Accessor
+   * 
+   * @return file URL
+   */
+  public String getFileURL() {
+    return fileURL;
+  }
+
+  /**
+   * Mutator
+   * 
+   * @param fileURL
+   */
+  public void setFileURL(String fileURL) {
+    this.fileURL = fileURL;
+  }
+
+    /**
+     * If the log file contains non-log4j level strings, they can be mapped to 
log4j levels using the format (android example):
+     * V=TRACE,D=DEBUG,I=INFO,W=WARN,E=ERROR,F=FATAL,S=OFF
+     *
+     * @param customLevelDefinitions the level definition string
+     */
+  public void setCustomLevelDefinitions(String customLevelDefinitions) {
+    this.customLevelDefinitions = customLevelDefinitions;
+  }
+
+  public String getCustomLevelDefinitions() {
+    return customLevelDefinitions;
+  }
+
+  /**
+   * Accessor
+   * @return append non matches
+   */
+  public boolean isAppendNonMatches() {
+      return appendNonMatches;
+  }
+
+  /**
+   * Mutator
+   * @param appendNonMatches
+   */
+  public void setAppendNonMatches(boolean appendNonMatches) {
+      this.appendNonMatches = appendNonMatches;
+  }
+
+  /**
+   * Accessor
+   * 
+   * @return filter expression
+   */
+  public String getFilterExpression() {
+    return filterExpression;
+  }
+
+  /**
+   * Mutator
+   * 
+   * @param filterExpression
+   */
+  public void setFilterExpression(String filterExpression) {
+    this.filterExpression = filterExpression;
+  }
+
+  /**
+   * Accessor
+   * 
+   * @return tailing
+   */
+  public boolean isTailing() {
+    return tailing;
+  }
+
+  /**
+   * Mutator
+   * 
+   * @param tailing
+   */
+  public void setTailing(boolean tailing) {
+    this.tailing = tailing;
+  }
+
+  /**
+   * When true, this property uses the current Thread to perform the import,
+   * otherwise when false (the default), a new Thread is created and started 
to manage
+   * the import.
+   * @return
+   */ 
+ public final boolean isUseCurrentThread() {
+     return useCurrentThread;
+ }
+
+ /**
+  * Sets whether the current Thread or a new Thread is created to perform the 
import,
+  * the default being false (new Thread created).
+  * 
+  * @param useCurrentThread
+  */
+ public final void setUseCurrentThread(boolean useCurrentThread) {
+     this.useCurrentThread = useCurrentThread;
+ }
+
+  /**
+   * Accessor
+   * 
+   * @return log format
+   */
+  public String getLogFormat() {
+    return logFormat;
+  }
+
+    /**
+   * Mutator
+   *
+   * @param logFormat
+   *          the format
+   */
+  public void setLogFormat(String logFormat) {
+    this.logFormat = logFormat;
+  }
+
+    /**
+   * Mutator.  Specify a pattern from {@link java.text.SimpleDateFormat}
+   *
+   * @param timestampFormat
+   */
+  public void setTimestampFormat(String timestampFormat) {
+    this.timestampFormat = timestampFormat;
+  }
+
+    /**
+   * Accessor
+   *
+   * @return timestamp format
+   */
+  public String getTimestampFormat() {
+    return timestampFormat;
+  }
+
+  /**
+   * Accessor
+   * @return millis between retrieves of content
+   */
+  public long getWaitMillis() {
+    return waitMillis;
+  }
+
+  /**
+   * Mutator
+   * @param waitMillis
+   */
+  public void setWaitMillis(long waitMillis) {
+    this.waitMillis = waitMillis;
+  }
+
+    /**
+   * Walk the additionalLines list, looking for the EXCEPTION_PATTERN.
+   * <p>
+   * Return the index of the first matched line
+   * (the match may be the 1st line of an exception)
+   * <p>
+   * Assumptions: <br>
+   * - the additionalLines list may contain both message and exception 
lines<br>
+   * - message lines are added to the additionalLines list and then
+   * exception lines (all message lines occur in the list prior to all
+   * exception lines)
+   *
+   * @return -1 if no exception line exists, line number otherwise
+   */
+  private int getExceptionLine() {
+    for (int i = 0; i < additionalLines.size(); i++) {
+      Matcher exceptionMatcher = 
exceptionPattern.matcher((String)additionalLines.get(i));
+      if (exceptionMatcher.matches()) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+    /**
+   * Combine all message lines occuring in the additionalLines list, adding
+   * a newline character between each line
+   * <p>
+   * the event will already have a message - combine this message
+   * with the message lines in the additionalLines list
+   * (all entries prior to the exceptionLine index)
+   *
+   * @param firstMessageLine primary message line
+   * @param exceptionLine index of first exception line
+   * @return message
+   */
+  private String buildMessage(String firstMessageLine, int exceptionLine) {
+    if (additionalLines.size() == 0) {
+      return firstMessageLine;
+    }
+    StringBuffer message = new StringBuffer();
+    if (firstMessageLine != null) {
+      message.append(firstMessageLine);
+    }
+
+    int linesToProcess = (exceptionLine == -1?additionalLines.size(): 
exceptionLine);
+
+    for (int i = 0; i < linesToProcess; i++) {
+      message.append(newLine);
+      message.append(additionalLines.get(i));
+    }
+    return message.toString();
+  }
+
+    /**
+   * Combine all exception lines occuring in the additionalLines list into a
+   * String array
+   * <p>
+   * (all entries equal to or greater than the exceptionLine index)
+   *
+   * @param exceptionLine index of first exception line
+   * @return exception
+   */
+  private String[] buildException(int exceptionLine) {
+    if (exceptionLine == -1) {
+      return emptyException;
+    }
+    String[] exception = new String[additionalLines.size() - exceptionLine - 
1];
+    for (int i = 0; i < exception.length; i++) {
+      exception[i] = (String) additionalLines.get(i + exceptionLine);
+    }
+    return exception;
+  }
+
+    /**
+   * Construct a logging event from currentMap and additionalLines
+   * (additionalLines contains multiple message lines and any exception lines)
+   * <p>
+   * CurrentMap and additionalLines are cleared in the process
+   *
+   * @return event
+   */
+  private LoggingEvent buildEvent() {
+    if (currentMap.size() == 0) {
+      if (additionalLines.size() > 0) {
+        for (Iterator iter = additionalLines.iterator();iter.hasNext();) {
+          getLogger().info("found non-matching line: " + iter.next());
+        }
+      }
+      additionalLines.clear();
+      return null;
+    }
+    //the current map contains fields - build an event
+    int exceptionLine = getExceptionLine();
+    String[] exception = buildException(exceptionLine);
+
+    //messages are listed before exceptions in additionallines
+    if (additionalLines.size() > 0 && exception.length > 0) {
+      currentMap.put(MESSAGE, buildMessage((String) currentMap.get(MESSAGE),
+          exceptionLine));
+    }
+    LoggingEvent event = convertToEvent(currentMap, exception);
+    currentMap.clear();
+    additionalLines.clear();
+    return event;
+  }
+
+    /**
+   * Read, parse and optionally tail the log file, converting entries into 
logging events.
+   *
+   * A runtimeException is thrown if the logFormat pattern is malformed.
+   *
+   * @param bufferedReader
+   * @throws IOException
+   */
+  protected void process(BufferedReader bufferedReader) throws IOException {
+        Matcher eventMatcher;
+        Matcher exceptionMatcher;
+        String line;
+        while ((line = bufferedReader.readLine()) != null) {
+            //skip empty line entries
+            eventMatcher = regexpPattern.matcher(line);
+            if (line.trim().equals("")) {continue;}
+            exceptionMatcher = exceptionPattern.matcher(line);
+            if (eventMatcher.matches()) {
+                //build an event from the previous match (held in current map)
+                LoggingEvent event = buildEvent();
+                if (event != null) {
+                    if (passesExpression(event)) {
+                        doPost(event);
+                    }
+                }
+                currentMap.putAll(processEvent(eventMatcher.toMatchResult()));
+            } else if (exceptionMatcher.matches()) {
+                //an exception line
+                additionalLines.add(line);
+            } else {
+                //neither...either post an event with the line or append as 
additional lines
+                //if this was a logging event with multiple lines, each line 
will show up as its own event instead of being
+                //appended as multiple lines on the same event..
+                //choice is to have each non-matching line show up as its own 
line, or append them all to a previous event
+                if (appendNonMatches) {
+                    //hold on to the previous time, so we can do our best to 
preserve time-based ordering if the event is a non-match
+                    String lastTime = (String)currentMap.get(TIMESTAMP);
+                    //build an event from the previous match (held in current 
map)
+                    if (currentMap.size() > 0) {
+                        LoggingEvent event = buildEvent();
+                        if (event != null) {
+                            if (passesExpression(event)) {
+                              doPost(event);
+                            }
+                        }
+                    }
+                    if (lastTime != null) {
+                        currentMap.put(TIMESTAMP, lastTime);
+                    }
+                    currentMap.put(MESSAGE, line);
+                } else {
+                    additionalLines.add(line);
+                }
+            }
+        }
+
+        //process last event if one exists
+        LoggingEvent event = buildEvent();
+        if (event != null) {
+            if (passesExpression(event)) {
+                doPost(event);
+            }
+        }
+    }
+
+    protected void createPattern() {
+        regexpPattern = Pattern.compile(regexp);
+    }
+
+    /**
+   * Helper method that supports the evaluation of the expression
+   *
+   * @param event
+   * @return true if expression isn't set, or the result of the evaluation 
otherwise
+   */
+  private boolean passesExpression(LoggingEvent event) {
+    if (event != null) {
+      if (expressionRule != null) {
+        return (expressionRule.evaluate(event, null));
+      }
+    }
+    return true;
+  }
+
+    /**
+   * Convert the match into a map.
+   * <p>
+   * Relies on the fact that the matchingKeywords list is in the same
+   * order as the groups in the regular expression
+   *
+   * @param result
+   * @return map
+   */
+  private Map processEvent(MatchResult result) {
+    Map map = new HashMap();
+    //group zero is the entire match - process all other groups
+    for (int i = 1; i < result.groupCount() + 1; i++) {
+      Object key = matchingKeywords.get(i - 1);
+      Object value = result.group(i);
+      map.put(key, value);
+
+    }
+    return map;
+  }
+
+    /**
+   * Helper method that will convert timestamp format to a pattern
+   *
+   *
+   * @return string
+   */
+  private String convertTimestamp() {
+    //some locales (for example, French) generate timestamp text with 
characters not included in \w -
+    // now using \S (all non-whitespace characters) instead of /w 
+    String result = timestampFormat.replaceAll(VALID_DATEFORMAT_CHAR_PATTERN + 
"+", "\\\\S+");
+    //make sure dots in timestamp are escaped
+    result = result.replaceAll(Pattern.quote("."), "\\\\.");
+    return result;
+  }
+
+    protected void setHost(String host) {
+         this.host = host;
+  }
+
+    protected void setPath(String path) {
+         this.path = path;
+  }
+
+  public String getPath() {
+      return path;
+  }
+
+    /**
+   * Build the regular expression needed to parse log entries
+   *
+   */
+  protected void initialize() {
+       if (host == null && path == null) {
+               try {
+                       URL url = new URL(fileURL);
+                       host = url.getHost();
+                       path = url.getPath();
+               } catch (MalformedURLException e1) {
+                       // TODO Auto-generated catch block
+                       e1.printStackTrace();
+               }
+       }
+       if (host == null || host.trim().equals("")) {
+               host = DEFAULT_HOST;
+       }
+       if (path == null || path.trim().equals("")) {
+               path = fileURL;
+       }
+
+    currentMap = new HashMap();
+    additionalLines = new ArrayList();
+    matchingKeywords = new ArrayList();
+
+    if (timestampFormat != null) {
+      dateFormat = new SimpleDateFormat(quoteTimeStampChars(timestampFormat));
+      timestampPatternText = convertTimestamp();
+    }
+    //if custom level definitions exist, parse them
+    updateCustomLevelDefinitionMap();
+    try {
+      if (filterExpression != null) {
+        expressionRule = ExpressionRule.getRule(filterExpression);
+      }
+    } catch (Exception e) {
+      getLogger().warn("Invalid filter expression: " + filterExpression, e);
+    }
+
+    List buildingKeywords = new ArrayList();
+
+    String newPattern = logFormat;
+
+    int index = 0;
+    String current = newPattern;
+    //build a list of property names and temporarily replace the property with 
an empty string,
+    //we'll rebuild the pattern later
+    List propertyNames = new ArrayList();
+    while (index > -1) {
+        if (current.indexOf(PROP_START) > -1 && current.indexOf(PROP_END) > 
-1) {
+            index = current.indexOf(PROP_START);
+            String longPropertyName = 
current.substring(current.indexOf(PROP_START), current.indexOf(PROP_END) + 1);
+            String shortProp = getShortPropertyName(longPropertyName);
+            buildingKeywords.add(shortProp);
+            propertyNames.add(longPropertyName);
+            current = current.substring(longPropertyName.length() + 1 + index);
+            newPattern = singleReplace(newPattern, longPropertyName, new 
Integer(buildingKeywords.size() -1).toString());
+        } else {
+            //no properties
+            index = -1;
+        }
+    }
+
+    /*
+     * we're using a treemap, so the index will be used as the key to ensure
+     * keywords are ordered correctly
+     *
+     * examine pattern, adding keywords to an index-based map patterns can
+     * contain only one of these per entry...properties are the only 'keyword'
+     * that can occur multiple times in an entry
+     */
+    Iterator iter = keywords.iterator();
+    while (iter.hasNext()) {
+      String keyword = (String) iter.next();
+      int index2 = newPattern.indexOf(keyword);
+      if (index2 > -1) {
+        buildingKeywords.add(keyword);
+        newPattern = singleReplace(newPattern, keyword, new 
Integer(buildingKeywords.size() -1).toString());
+      }
+    }
+
+    String buildingInt = "";
+
+    for (int i=0;i<newPattern.length();i++) {
+        String thisValue = String.valueOf(newPattern.substring(i, i+1));
+        if (isInteger(thisValue)) {
+            buildingInt = buildingInt + thisValue;
+        } else {
+            if (isInteger(buildingInt)) {
+                
matchingKeywords.add(buildingKeywords.get(Integer.parseInt(buildingInt)));
+            }
+            //reset
+            buildingInt = "";
+        }
+    }
+
+    //if the very last value is an int, make sure to add it
+    if (isInteger(buildingInt)) {
+        
matchingKeywords.add(buildingKeywords.get(Integer.parseInt(buildingInt)));
+    }
+
+    newPattern = replaceMetaChars(newPattern);
+
+    //compress one or more spaces in the pattern into the [ ]+ regexp
+    //(supports padding of level in log files)
+    newPattern = newPattern.replaceAll(MULTIPLE_SPACES_REGEXP, 
MULTIPLE_SPACES_REGEXP);
+    newPattern = newPattern.replaceAll(Pattern.quote(PATTERN_WILDCARD), 
REGEXP_DEFAULT_WILDCARD);
+    //use buildingKeywords here to ensure correct order
+    for (int i = 0;i<buildingKeywords.size();i++) {
+      String keyword = (String) buildingKeywords.get(i);
+      //make the final keyword greedy (we're assuming it's the message)
+      if (i == (buildingKeywords.size() - 1)) {
+        newPattern = singleReplace(newPattern, String.valueOf(i), 
GREEDY_GROUP);
+      } else if (TIMESTAMP.equals(keyword)) {
+        newPattern = singleReplace(newPattern, String.valueOf(i), "(" + 
timestampPatternText + ")");
+      } else if (LOGGER.equals(keyword) || LEVEL.equals(keyword)) {
+        newPattern = singleReplace(newPattern, String.valueOf(i), 
NOSPACE_GROUP);
+      } else {
+        newPattern = singleReplace(newPattern, String.valueOf(i), 
DEFAULT_GROUP);
+      }
+    }
+
+    regexp = newPattern;
+    getLogger().debug("regexp is " + regexp);
+  }
+
+    private void updateCustomLevelDefinitionMap() {
+        if (customLevelDefinitions != null) {
+            StringTokenizer entryTokenizer = new 
StringTokenizer(customLevelDefinitions, ",");
+
+            customLevelDefinitionMap.clear();
+            while (entryTokenizer.hasMoreTokens()) {
+                StringTokenizer innerTokenizer = new 
StringTokenizer(entryTokenizer.nextToken(), "=");
+                customLevelDefinitionMap.put(innerTokenizer.nextToken(), 
Level.toLevel(innerTokenizer.nextToken()));
+            }
+        }
+    }
+
+    private boolean isInteger(String value) {
+        try {
+            Integer.parseInt(value);
+            return true;
+        } catch (NumberFormatException nfe) {
+            return false;
+        }
+    }
+
+    private String quoteTimeStampChars(String input) {
+        //put single quotes around text that isn't a supported dateformat char
+        StringBuffer result = new StringBuffer();
+        //ok to default to false because we also check for index zero below
+        boolean lastCharIsDateFormat = false;
+        for (int i = 0;i<input.length();i++) {
+            String thisVal = input.substring(i, i + 1);
+            boolean thisCharIsDateFormat = 
VALID_DATEFORMAT_CHARS.contains(thisVal);
+            //we have encountered a non-dateformat char
+            if (!thisCharIsDateFormat && (i == 0 || lastCharIsDateFormat)) {
+                result.append("'");
+            }
+            //we have encountered a dateformat char after previously 
encountering a non-dateformat char
+            if (thisCharIsDateFormat && i > 0 && !lastCharIsDateFormat) {
+                result.append("'");
+            }
+            lastCharIsDateFormat = thisCharIsDateFormat;
+            result.append(thisVal);
+        }
+        //append an end single-quote if we ended with non-dateformat char
+        if (!lastCharIsDateFormat) {
+            result.append("'");
+        }
+        return result.toString();
+    }
+
+    private String singleReplace(String inputString, String oldString, String 
newString)
+    {
+        int propLength = oldString.length();
+        int startPos = inputString.indexOf(oldString);
+        if (startPos == -1)
+        {
+            getLogger().info("string: " + oldString + " not found in input: " 
+ inputString + " - returning input");
+            return inputString;
+        }
+        if (startPos == 0)
+        {
+            inputString = inputString.substring(propLength);
+            inputString = newString + inputString;
+        } else {
+            inputString = inputString.substring(0, startPos) + newString + 
inputString.substring(startPos + propLength);
+        }
+        return inputString;
+    }
+
+    private String getShortPropertyName(String longPropertyName)
+  {
+      String currentProp = 
longPropertyName.substring(longPropertyName.indexOf(PROP_START));
+      String prop = currentProp.substring(0, currentProp.indexOf(PROP_END) + 
1);
+      String shortProp = prop.substring(PROP_START.length(), prop.length() - 
1);
+      return shortProp;
+  }
+
+    /**
+   * Some perl5 characters may occur in the log file format.
+   * Escape these characters to prevent parsing errors.
+   *
+   * @param input
+   * @return string
+   */
+  private String replaceMetaChars(String input) {
+    //escape backslash first since that character is used to escape the 
remaining meta chars
+    input = input.replaceAll("\\\\", "\\\\\\");
+
+    //don't escape star - it's used as the wildcard
+    input = input.replaceAll(Pattern.quote("]"), "\\\\]");
+    input = input.replaceAll(Pattern.quote("["), "\\\\[");
+    input = input.replaceAll(Pattern.quote("^"), "\\\\^");
+    input = input.replaceAll(Pattern.quote("$"), "\\\\$");
+    input = input.replaceAll(Pattern.quote("."), "\\\\.");
+    input = input.replaceAll(Pattern.quote("|"), "\\\\|");
+    input = input.replaceAll(Pattern.quote("?"), "\\\\?");
+    input = input.replaceAll(Pattern.quote("+"), "\\\\+");
+    input = input.replaceAll(Pattern.quote("("), "\\\\(");
+    input = input.replaceAll(Pattern.quote(")"), "\\\\)");
+    input = input.replaceAll(Pattern.quote("-"), "\\\\-");
+    input = input.replaceAll(Pattern.quote("{"), "\\\\{");
+    input = input.replaceAll(Pattern.quote("}"), "\\\\}");
+    input = input.replaceAll(Pattern.quote("#"), "\\\\#");
+    return input;
+  }
+
+    /**
+   * Convert a keyword-to-values map to a LoggingEvent
+   *
+   * @param fieldMap
+   * @param exception
+   *
+   * @return logging event
+   */
+  private LoggingEvent convertToEvent(Map fieldMap, String[] exception) {
+    if (fieldMap == null) {
+      return null;
+    }
+
+    //a logger must exist at a minimum for the event to be processed
+    if (!fieldMap.containsKey(LOGGER)) {
+      fieldMap.put(LOGGER, "Unknown");
+    }
+    if (exception == null) {
+      exception = emptyException;
+    }
+
+    Logger logger = null;
+    long timeStamp = 0L;
+    String level = null;
+    String threadName = null;
+    Object message = null;
+    String ndc = null;
+    String className = null;
+    String methodName = null;
+    String eventFileName = null;
+    String lineNumber = null;
+    Hashtable properties = new Hashtable();
+
+    logger = Logger.getLogger((String) fieldMap.remove(LOGGER));
+
+    if ((dateFormat != null) && fieldMap.containsKey(TIMESTAMP)) {
+      try {
+        timeStamp = dateFormat.parse((String) fieldMap.remove(TIMESTAMP))
+            .getTime();
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+    //use current time if timestamp not parseable
+    if (timeStamp == 0L) {
+      timeStamp = System.currentTimeMillis();
+    }
+
+    message = fieldMap.remove(MESSAGE);
+    if (message == null) {
+      message = "";
+    }
+
+    level = (String) fieldMap.remove(LEVEL);
+    Level levelImpl;
+    if (level == null) {
+        levelImpl = Level.DEBUG;
+    } else {
+        //first try to resolve against custom level definition map, then fall 
back to regular levels
+        levelImpl = (Level) customLevelDefinitionMap.get(level);
+        if (levelImpl == null) {
+            levelImpl = Level.toLevel(level.trim());
+            if (!level.equals(levelImpl.toString())) {
+                //check custom level map
+                if (levelImpl == null) {
+                    levelImpl = Level.DEBUG;
+                    getLogger().debug("found unexpected level: " + level + ", 
logger: " + logger.getName() + ", msg: " + message);
+                    //make sure the text that couldn't match a level is added 
to the message
+                    message = level + " " + message;
+                }
+            }
+        }
+    }
+
+    threadName = (String) fieldMap.remove(THREAD);
+
+    ndc = (String) fieldMap.remove(NDC);
+
+    className = (String) fieldMap.remove(CLASS);
+
+    methodName = (String) fieldMap.remove(METHOD);
+
+    eventFileName = (String) fieldMap.remove(FILE);
+
+    lineNumber = (String) fieldMap.remove(LINE);
+
+    properties.put(Constants.HOSTNAME_KEY, host);
+    properties.put(Constants.APPLICATION_KEY, path);
+    properties.put(Constants.RECEIVER_NAME_KEY, getName());
+
+    //all remaining entries in fieldmap are properties
+    properties.putAll(fieldMap);
+
+    LocationInfo info = null;
+
+    if ((eventFileName != null) || (className != null) || (methodName != null)
+        || (lineNumber != null)) {
+      info = new LocationInfo(eventFileName, className, methodName, 
lineNumber);
+    } else {
+      info = LocationInfo.NA_LOCATION_INFO;
+    }
+
+    LoggingEvent event = new LoggingEvent(null,
+            logger, timeStamp, levelImpl, message,
+            threadName,
+            new ThrowableInformation(exception),
+            ndc,
+            info,
+            properties);
+
+    return event;
+  }
+
+//  public static void main(String[] args) {
+//    org.apache.log4j.Logger rootLogger = 
org.apache.log4j.Logger.getRootLogger();
+//    org.apache.log4j.ConsoleAppender appender = new 
org.apache.log4j.ConsoleAppender(new org.apache.log4j.SimpleLayout());
+//    appender.setName("console");
+//    rootLogger.addAppender(appender);
+//    LogFilePatternReceiver test = new LogFilePatternReceiver();
+//    org.apache.log4j.spi.LoggerRepository repo = new 
org.apache.log4j.LoggerRepositoryExImpl(org.apache.log4j.LogManager.getLoggerRepository());
+//    test.setLoggerRepository(repo);
+//    test.setLogFormat("PROP(RELATIVETIME) [THREAD] LEVEL LOGGER * - 
MESSAGE");
+//    test.setTailing(false);
+//    test.setAppendNonMatches(true);
+//    test.setTimestampFormat("yyyy-MM-d HH:mm:ss,SSS");
+//    test.setFileURL("file:///C:/log/test.log");
+//    test.initialize();
+//    test.activateOptions();
+//  }
+
+    /**
+   * Close the reader.
+   */
+  public void shutdown() {
+    getLogger().info(getPath() + " shutdown");
+    active = false;
+    try {
+      if (reader != null) {
+        reader.close();
+        reader = null;
+      }
+    } catch (IOException ioe) {
+      ioe.printStackTrace();
+    }
+  }
+
+    /**
+   * Read and process the log file.
+   */
+  public void activateOptions() {
+    getLogger().info("activateOptions");
+    active = true;
+       Runnable runnable = new Runnable() {
+         public void run() {
+        initialize();
+            while (reader == null) {
+                getLogger().info("attempting to load file: " + getFileURL());
+                try {
+                    reader = new InputStreamReader(new 
URL(getFileURL()).openStream());
+                } catch (FileNotFoundException fnfe) {
+                    getLogger().info("file not available - will try again");
+                    synchronized (this) {
+                        try {
+                            wait(MISSING_FILE_RETRY_MILLIS);
+                        } catch (InterruptedException ie) {}
+                    }
+                } catch (IOException ioe) {
+                    getLogger().warn("unable to load file", ioe);
+                    return;
+                }
+            }
+            try {
+                BufferedReader bufferedReader = new BufferedReader(reader);
+                createPattern();
+                do {
+                    process(bufferedReader);
+                    try {
+                        synchronized (this) {
+                            wait(waitMillis);
+                        }
+                    } catch (InterruptedException ie) {}
+                    if (tailing) {
+                      getLogger().debug("tailing file");
+                    }
+                } while (tailing);
+
+            } catch (IOException ioe) {
+                //io exception - probably shut down
+                getLogger().info("stream closed");
+            }
+            getLogger().debug("processing " + path + " complete");
+            shutdown();
+          }
+        };
+        if(useCurrentThread) {
+            runnable.run();
+        }else {
+            new Thread(runnable, "LogFilePatternReceiver-"+getName()).start();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/varia/LogFilePatternReceiverBeanInfo.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/log4j/varia/LogFilePatternReceiverBeanInfo.java 
b/src/main/java/org/apache/log4j/varia/LogFilePatternReceiverBeanInfo.java
new file mode 100644
index 0000000..f99f289
--- /dev/null
+++ b/src/main/java/org/apache/log4j/varia/LogFilePatternReceiverBeanInfo.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.varia;
+
+import java.beans.PropertyDescriptor;
+import java.beans.SimpleBeanInfo;
+
+
+/**
+ * BeanInfo class for the meta-data of the LogFilePatternReceiver.
+ *
+ */
+public class LogFilePatternReceiverBeanInfo extends SimpleBeanInfo {
+  /* (non-Javadoc)
+   * @see java.beans.BeanInfo#getPropertyDescriptors()
+   */
+  public PropertyDescriptor[] getPropertyDescriptors() {
+    try {
+      return new PropertyDescriptor[] {
+        new PropertyDescriptor("fileURL", LogFilePatternReceiver.class),
+        new PropertyDescriptor(
+          "timestampFormat", LogFilePatternReceiver.class),
+        new PropertyDescriptor("logFormat", LogFilePatternReceiver.class),
+        new PropertyDescriptor("name", LogFilePatternReceiver.class),
+        new PropertyDescriptor("tailing", LogFilePatternReceiver.class),
+        new PropertyDescriptor(
+          "filterExpression", LogFilePatternReceiver.class),
+        new PropertyDescriptor("waitMillis", LogFilePatternReceiver.class),
+        new PropertyDescriptor("appendNonMatches", 
LogFilePatternReceiver.class),
+        new PropertyDescriptor("customLevelDefinitions", 
LogFilePatternReceiver.class),
+        new PropertyDescriptor("useCurrentThread", 
LogFilePatternReceiver.class),
+      };
+    } catch (Exception e) {
+    }
+
+    return null;
+  }
+}

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/xml/LogFileXMLReceiver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/xml/LogFileXMLReceiver.java 
b/src/main/java/org/apache/log4j/xml/LogFileXMLReceiver.java
new file mode 100644
index 0000000..945d4f0
--- /dev/null
+++ b/src/main/java/org/apache/log4j/xml/LogFileXMLReceiver.java
@@ -0,0 +1,310 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.xml;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.log4j.helpers.Constants;
+import org.apache.log4j.plugins.Receiver;
+import org.apache.log4j.rule.ExpressionRule;
+import org.apache.log4j.rule.Rule;
+import org.apache.log4j.spi.Decoder;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * LogFileXMLReceiver will read an xml-formated log file and make the events 
in the log file
+ * available to the log4j framework.
+ * <p>
+ * This receiver supports log files created using log4j's XMLLayout, as well 
as java.util.logging
+ * XMLFormatter (via the org.apache.log4j.spi.Decoder interface).
+ * <p>
+ * By default, log4j's XMLLayout is supported (no need to specify a decoder in 
that case).
+ * <p>
+ * To configure this receiver to support java.util.logging's XMLFormatter, 
specify a 'decoder' param
+ * of org.apache.log4j.xml.UtilLoggingXMLDecoder.
+ * <p>
+ * Tailing -may- work, but not in all cases (try using a file:// URL). If a 
process has a log file
+ * open, the receiver may be able to read and tail the file. If the process 
closes the file and
+ * reopens the file, the receiver may not be able to continue tailing the file.
+ * <p>
+ * An expressionFilter may be specified. Only events passing the expression 
will be forwarded to the
+ * log4j framework.
+ * <p>
+ * Once the event has been "posted", it will be handled by the appenders 
currently configured in the
+ * LoggerRespository.
+ * 
+ * @author Scott Deboy <[email protected]>
+ * @since 1.3
+ */
+
+public class LogFileXMLReceiver extends Receiver {
+    private String fileURL;
+    private Rule expressionRule;
+    private String filterExpression;
+    private String decoder = "org.apache.log4j.xml.XMLDecoder";
+    private boolean tailing = false;
+
+    private Decoder decoderInstance;
+    private Reader reader;
+    private static final String FILE_KEY = "file";
+    private String host;
+    private String path;
+    private boolean useCurrentThread;
+
+    /**
+     * Accessor
+     * 
+     * @return file URL
+     */
+    public String getFileURL() {
+        return fileURL;
+    }
+
+    /**
+     * Specify the URL of the XML-formatted file to process.
+     * 
+     * @param fileURL
+     */
+    public void setFileURL(String fileURL) {
+        this.fileURL = fileURL;
+    }
+
+    /**
+     * Accessor
+     * 
+     * @return
+     */
+    public String getDecoder() {
+        return decoder;
+    }
+
+    /**
+     * Specify the class name implementing org.apache.log4j.spi.Decoder that 
can process the file.
+     * 
+     * @param _decoder
+     */
+    public void setDecoder(String _decoder) {
+        decoder = _decoder;
+    }
+
+    /**
+     * Accessor
+     * 
+     * @return filter expression
+     */
+    public String getFilterExpression() {
+        return filterExpression;
+    }
+
+    /**
+     * Accessor
+     * 
+     * @return tailing flag
+     */
+    public boolean isTailing() {
+        return tailing;
+    }
+
+    /**
+     * Set the 'tailing' flag - may only work on file:// URLs and may stop 
tailing if the writing
+     * process closes the file and reopens.
+     * 
+     * @param tailing
+     */
+    public void setTailing(boolean tailing) {
+        this.tailing = tailing;
+    }
+
+    /**
+     * Set the filter expression that will cause only events which pass the 
filter to be forwarded
+     * to the log4j framework.
+     * 
+     * @param filterExpression
+     */
+    public void setFilterExpression(String filterExpression) {
+        this.filterExpression = filterExpression;
+    }
+
+    private boolean passesExpression(LoggingEvent event) {
+        if (event != null) {
+            if (expressionRule != null) {
+                return (expressionRule.evaluate(event, null));
+            }
+        }
+        return true;
+    }
+
+    public static void main(String[] args) {
+        /*
+         * LogFileXMLReceiver test = new LogFileXMLReceiver();
+         * test.setFileURL("file:///c:/samplelog.xml"); 
test.setFilterExpression("level >= TRACE");
+         * test.activateOptions();
+         */
+    }
+
+    /**
+     * Close the receiver, release any resources that are accessing the file.
+     */
+    public void shutdown() {
+        try {
+            if (reader != null) {
+                reader.close();
+                reader = null;
+            }
+        } catch (IOException ioe) {
+            ioe.printStackTrace();
+        }
+    }
+
+    /**
+     * Process the file
+     */
+    public void activateOptions() {
+        Runnable runnable = new Runnable() {
+            public void run() {
+                try {
+                    URL url = new URL(fileURL);
+                    host = url.getHost();
+                    if (host != null && host.equals("")) {
+                        host = FILE_KEY;
+                    }
+                    path = url.getPath();
+                } catch (MalformedURLException e1) {
+                    // TODO Auto-generated catch block
+                    e1.printStackTrace();
+                }
+
+                try {
+                    if (filterExpression != null) {
+                        expressionRule = 
ExpressionRule.getRule(filterExpression);
+                    }
+                } catch (Exception e) {
+                    getLogger().warn("Invalid filter expression: " + 
filterExpression, e);
+                }
+
+                Class c;
+                try {
+                    c = Class.forName(decoder);
+                    Object o = c.newInstance();
+                    if (o instanceof Decoder) {
+                        decoderInstance = (Decoder) o;
+                    }
+                } catch (ClassNotFoundException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                } catch (InstantiationException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                } catch (IllegalAccessException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }
+
+                try {
+                    reader = new InputStreamReader(new 
URL(getFileURL()).openStream());
+                    process(reader);
+                } catch (FileNotFoundException fnfe) {
+                    getLogger().info("file not available");
+                } catch (IOException ioe) {
+                    getLogger().warn("unable to load file", ioe);
+                    return;
+                }
+            }
+        };
+        if (useCurrentThread) {
+            runnable.run();
+        } else {
+            Thread thread = new Thread(runnable, "LogFileXMLReceiver-" + 
getName());
+
+            thread.start();
+
+        }
+    }
+
+    private void process(Reader unbufferedReader) throws IOException {
+        BufferedReader bufferedReader = new BufferedReader(unbufferedReader);
+        char[] content = new char[10000];
+        getLogger().debug("processing starting: " + fileURL);
+        int length = 0;
+        do {
+            System.out.println("in do loop-about to process");
+            while ((length = bufferedReader.read(content)) > -1) {
+                
processEvents(decoderInstance.decodeEvents(String.valueOf(content, 0, length)));
+            }
+            if (tailing) {
+                try {
+                    Thread.sleep(5000);
+                } catch (InterruptedException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }
+            }
+        } while (tailing);
+        getLogger().debug("processing complete: " + fileURL);
+
+        shutdown();
+    }
+
+    private void processEvents(Collection c) {
+        if (c == null) {
+            return;
+        }
+
+        for (Iterator iter = c.iterator(); iter.hasNext();) {
+            LoggingEvent evt = (LoggingEvent) iter.next();
+            if (passesExpression(evt)) {
+                if (evt.getProperty(Constants.HOSTNAME_KEY) != null) {
+                    evt.setProperty(Constants.HOSTNAME_KEY, host);
+                }
+                if (evt.getProperty(Constants.APPLICATION_KEY) != null) {
+                    evt.setProperty(Constants.APPLICATION_KEY, path);
+                }
+                doPost(evt);
+            }
+        }
+    }
+
+    /**
+     * When true, this property uses the current Thread to perform the import, 
otherwise when false
+     * (the default), a new Thread is created and started to manage the import.
+     * 
+     * @return
+     */
+    public final boolean isUseCurrentThread() {
+        return useCurrentThread;
+    }
+
+    /**
+     * Sets whether the current Thread or a new Thread is created to perform 
the import, the default
+     * being false (new Thread created).
+     * 
+     * @param useCurrentThread
+     */
+    public final void setUseCurrentThread(boolean useCurrentThread) {
+        this.useCurrentThread = useCurrentThread;
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/logging-chainsaw/blob/08c7be5c/src/main/java/org/apache/log4j/xml/UtilLoggingEntityResolver.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/log4j/xml/UtilLoggingEntityResolver.java 
b/src/main/java/org/apache/log4j/xml/UtilLoggingEntityResolver.java
new file mode 100644
index 0000000..71a349c
--- /dev/null
+++ b/src/main/java/org/apache/log4j/xml/UtilLoggingEntityResolver.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.xml;
+
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+
+import java.io.ByteArrayInputStream;
+
+
+/**
+ * An {@link EntityResolver} specifically designed to return
+ * an empty InputSource for logger.dtd.
+ *
+ */
+public final class UtilLoggingEntityResolver implements EntityResolver {
+
+    /**
+     * Create new instance.
+     */
+    public UtilLoggingEntityResolver() {
+        super();
+    }
+
+
+    /** {@inheritDoc} */
+  public InputSource resolveEntity(final String publicId,
+                                   final String systemId) {
+    if (systemId.endsWith("logger.dtd")) {
+      return new InputSource(new ByteArrayInputStream(new byte[0]));
+    } else {
+      return null;
+    }
+  }
+}

Reply via email to