You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by gg...@apache.org on 2022/01/14 18:33:14 UTC

[logging-log4j2] branch release-2.x updated (0633259 -> 73a2cd1)

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a change to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git.


    from 0633259  Javadoc class with a table that describes all levels.
     new c959ef9  Add classes for BC.
     new 73a2cd1  Implement configuration APIs in and PropertyConfigurator and BasicConfigurator.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../java/org/apache/log4j/BasicConfigurator.java   |  32 +-
 .../src/main/java/org/apache/log4j/Category.java   | 704 ++++++++++++---------
 .../main/java/org/apache/log4j/FileAppender.java   | 306 +++++++++
 .../src/main/java/org/apache/log4j/Hierarchy.java  | 143 +++--
 .../src/main/java/org/apache/log4j/LogManager.java | 105 ++-
 .../java/org/apache/log4j/LoggerRepository2.java   |  31 -
 .../org/apache/log4j/PropertyConfigurator.java     | 603 +++++++++++++++++-
 .../java/org/apache/log4j/RollingFileAppender.java | 253 ++++++++
 .../log4j/config/PropertiesConfiguration.java      | 272 ++++----
 ...ppenderTest.java => BasicConfiguratorTest.java} |  42 +-
 .../org/apache/log4j/PropertyConfiguratorTest.java | 427 +++++++++++++
 11 files changed, 2317 insertions(+), 601 deletions(-)
 create mode 100644 log4j-1.2-api/src/main/java/org/apache/log4j/FileAppender.java
 delete mode 100644 log4j-1.2-api/src/main/java/org/apache/log4j/LoggerRepository2.java
 create mode 100644 log4j-1.2-api/src/main/java/org/apache/log4j/RollingFileAppender.java
 copy log4j-1.2-api/src/test/java/org/apache/log4j/{ConsoleAppenderTest.java => BasicConfiguratorTest.java} (50%)
 create mode 100644 log4j-1.2-api/src/test/java/org/apache/log4j/PropertyConfiguratorTest.java

[logging-log4j2] 02/02: Implement configuration APIs in and PropertyConfigurator and BasicConfigurator.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 73a2cd1cd0e94c7f4f36e4ac9dc72380d30750ef
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Fri Jan 14 13:33:10 2022 -0500

    Implement configuration APIs in and PropertyConfigurator and
    BasicConfigurator.
    
    - Call sites of public APIs now pick up the proper logger context based
    on the call sites' class loader.
    - Drop package private interface LoggerRepository2.
    - Can't break up this commit into smaller digestible chunks.
---
 .../java/org/apache/log4j/BasicConfigurator.java   |  32 +-
 .../src/main/java/org/apache/log4j/Category.java   | 704 ++++++++++++---------
 .../src/main/java/org/apache/log4j/Hierarchy.java  | 143 +++--
 .../src/main/java/org/apache/log4j/LogManager.java | 105 ++-
 .../java/org/apache/log4j/LoggerRepository2.java   |  31 -
 .../org/apache/log4j/PropertyConfigurator.java     | 603 +++++++++++++++++-
 .../log4j/config/PropertiesConfiguration.java      | 272 ++++----
 .../org/apache/log4j/BasicConfiguratorTest.java    |  56 ++
 .../org/apache/log4j/PropertyConfiguratorTest.java | 427 +++++++++++++
 9 files changed, 1790 insertions(+), 583 deletions(-)

diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java
index da90e2c..14910d8 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java
@@ -16,27 +16,45 @@
  */
 package org.apache.log4j;
 
+import org.apache.logging.log4j.util.StackLocatorUtil;
+
 /**
- * Provided for compatibility with Log4j 1.x.
- *
+ * Configures the package.
+ * 
+ * <p>
+ * For file based configuration, see {@link PropertyConfigurator}. For XML based configuration, see
+ * {@link org.apache.log4j.xml.DOMConfigurator DOMConfigurator}.
+ * </p>
+ * 
  * @since 0.8.1
  */
 public class BasicConfigurator {
 
+    /**
+     * Adds a {@link ConsoleAppender} that uses {@link PatternLayout} using the
+     * {@link PatternLayout#TTCC_CONVERSION_PATTERN} and prints to <code>System.out</code> to the root category.
+     */
     public static void configure() {
-        LogManager.reconfigure();
+        LogManager.reconfigure(StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
-     * No-op implementation.
-     * @param appender The appender.
+     * Adds <code>appender</code> to the root category.
+     *
+     * @param appender The appender to add to the root category.
      */
     public static void configure(final Appender appender) {
-        // no-op
+        LogManager.getRootLogger(StackLocatorUtil.getCallerClassLoader(2)).addAppender(appender);
     }
 
+    /**
+     * Resets the default hierarchy to its default. It is equivalent to calling
+     * <code>Category.getDefaultHierarchy().resetConfiguration()</code>.
+     *
+     * See {@link Hierarchy#resetConfiguration()} for more details.
+     */
     public static void resetConfiguration() {
-        LogManager.resetConfiguration();
+        LogManager.resetConfiguration(StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java
index 286ae32..90857cb 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java
@@ -19,13 +19,17 @@ package org.apache.log4j;
 import java.util.Enumeration;
 import java.util.Map;
 import java.util.ResourceBundle;
+import java.util.Vector;
 import java.util.concurrent.ConcurrentMap;
 
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.helpers.AppenderAttachableImpl;
 import org.apache.log4j.helpers.NullEnumeration;
 import org.apache.log4j.legacy.core.CategoryUtil;
 import org.apache.log4j.or.ObjectRenderer;
 import org.apache.log4j.or.RendererMap;
 import org.apache.log4j.spi.AppenderAttachable;
+import org.apache.log4j.spi.HierarchyEventListener;
 import org.apache.log4j.spi.LoggerRepository;
 import org.apache.log4j.spi.LoggingEvent;
 import org.apache.logging.log4j.message.LocalizedMessage;
@@ -46,6 +50,78 @@ public class Category implements AppenderAttachable {
     private static final String FQCN = Category.class.getName();
 
     /**
+     * Tests if the named category exists (in the default hierarchy).
+     *
+     * @param name The name to test.
+     * @return Whether the name exists.
+     *
+     * @deprecated Please use {@link LogManager#exists(String)} instead.
+     * @since 0.8.5
+     */
+    @Deprecated
+    public static Logger exists(final String name) {
+        return LogManager.exists(name, StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    /**
+     * Returns all the currently defined categories in the default hierarchy as an {@link java.util.Enumeration
+     * Enumeration}.
+     *
+     * <p>
+     * The root category is <em>not</em> included in the returned {@link Enumeration}.
+     * </p>
+     *
+     * @return and Enumeration of the Categories.
+     *
+     * @deprecated Please use {@link LogManager#getCurrentLoggers()} instead.
+     */
+    @SuppressWarnings("rawtypes")
+    @Deprecated
+    public static Enumeration getCurrentCategories() {
+        return LogManager.getCurrentLoggers(StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    /**
+     * Gets the default LoggerRepository instance.
+     *
+     * @return the default LoggerRepository instance.
+     * @deprecated Please use {@link LogManager#getLoggerRepository()} instead.
+     * @since 1.0
+     */
+    @Deprecated
+    public static LoggerRepository getDefaultHierarchy() {
+        return LogManager.getLoggerRepository();
+    }
+
+    public static Category getInstance(@SuppressWarnings("rawtypes") final Class clazz) {
+        return LogManager.getLogger(clazz.getName(), StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    public static Category getInstance(final String name) {
+        return LogManager.getLogger(name, StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    public static Category getRoot() {
+        return LogManager.getRootLogger(StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    private static String getSubName(final String name) {
+        if (Strings.isEmpty(name)) {
+            return null;
+        }
+        final int i = name.lastIndexOf('.');
+        return i > 0 ? name.substring(0, i) : Strings.EMPTY;
+    }
+
+    /**
+     * Shuts down the current configuration.
+     */
+    public static void shutdown() {
+        // Depth 2 gets the call site of this method.
+        LogManager.shutdown(StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    /**
      * The name of this category.
      */
     protected String name;
@@ -81,8 +157,11 @@ public class Category implements AppenderAttachable {
     /** Categories need to know what Hierarchy they are in. */
     protected LoggerRepository repository;
 
+    AppenderAttachableImpl aai;
+
     /**
      * Constructor used by Logger to specify a LoggerContext.
+     *
      * @param context The LoggerContext.
      * @param name The name of the Logger.
      */
@@ -90,158 +169,101 @@ public class Category implements AppenderAttachable {
         this.name = name;
         this.logger = context.getLogger(name);
         this.repository = LogManager.getLoggerRepository();
-        //this.rendererMap = ((RendererSupport) repository).getRendererMap();
+        // this.rendererMap = ((RendererSupport) repository).getRendererMap();
+    }
+
+    Category(final org.apache.logging.log4j.Logger logger) {
+        this.logger = logger;
+        // rendererMap = ((RendererSupport) LogManager.getLoggerRepository()).getRendererMap();
     }
 
     /**
      * Constructor exposed by Log4j 1.2.
+     *
      * @param name The name of the Logger.
      */
     protected Category(final String name) {
         this(Hierarchy.getContext(), name);
     }
 
-    Category(final org.apache.logging.log4j.Logger logger) {
-        this.logger = logger;
-        //rendererMap = ((RendererSupport) LogManager.getLoggerRepository()).getRendererMap();
-    }
-
-    public static Category getInstance(final String name) {
-        // Depth 2 gets the call site of this method.
-        return LogManager.getLogger(name, StackLocatorUtil.getCallerClassLoader(2));
-    }
-
-    public static Category getInstance(@SuppressWarnings("rawtypes") final Class clazz) {
-        // Depth 2 gets the call site of this method.
-        return LogManager.getLogger(clazz.getName(), StackLocatorUtil.getCallerClassLoader(2));
-    }
-
-    public final String getName() {
-        return logger.getName();
-    }
-
-    org.apache.logging.log4j.Logger getLogger() {
-        return logger;
-    }
-
-    public final Category getParent() {
-        if (!LogManager.isLog4jCorePresent()) {
-            return null;
+    /**
+     * Add <code>newAppender</code> to the list of appenders of this Category instance.
+     * <p>
+     * If <code>newAppender</code> is already in the list of appenders, then it won't be added again.
+     * </p>
+     */
+    @Override
+    public void addAppender(final Appender appender) {
+        if (aai == null) {
+            aai = new AppenderAttachableImpl();
         }
-        final org.apache.logging.log4j.Logger parent = CategoryUtil.getParent(logger);
-        final LoggerContext loggerContext = CategoryUtil.getLoggerContext(logger);
-        if (parent == null || loggerContext == null) {
-            return null;
+        aai.addAppender(appender);
+        if (appender != null) {
+            repository.fireAddAppenderEvent(this, appender);
         }
-        final ConcurrentMap<String, Logger> loggers = Hierarchy.getLoggersMap(loggerContext);
-        final Logger parentLogger = loggers.get(parent.getName());
-        return parentLogger == null ? new Category(parent) : parentLogger;
-    }
-
-    public static Category getRoot() {
-        return getInstance(Strings.EMPTY);
     }
 
     /**
-     * Returns all the currently defined categories in the default hierarchy as an
-     * {@link java.util.Enumeration Enumeration}.
+     * If <code>assertion</code> parameter is {@code false}, then logs <code>msg</code> as an {@link #error(Object) error}
+     * statement.
      *
      * <p>
-     * The root category is <em>not</em> included in the returned
-     * {@link Enumeration}.
+     * The <code>assert</code> method has been renamed to <code>assertLog</code> because <code>assert</code> is a language
+     * reserved word in JDK 1.4.
      * </p>
      *
-     * @return and Enumeration of the Categories.
-     *
-     * @deprecated Please use {@link LogManager#getCurrentLoggers()} instead.
-     */
-    @SuppressWarnings("rawtypes")
-    @Deprecated
-    public static Enumeration getCurrentCategories() {
-        return LogManager.getCurrentLoggers(StackLocatorUtil.getCallerClassLoader(2));
-    }
-
-    /**
-     * Gets the default LoggerRepository instance.
+     * @param assertion The assertion.
+     * @param msg The message to print if <code>assertion</code> is false.
      *
-     * @return the default LoggerRepository instance.
-     * @deprecated Please use {@link LogManager#getLoggerRepository()} instead.
-     * @since 1.0
+     * @since 1.2
      */
-    @Deprecated
-    public static LoggerRepository getDefaultHierarchy() {
-        return LogManager.getLoggerRepository();
-    }
-
-    public Level getEffectiveLevel() {
-        switch (logger.getLevel().getStandardLevel()) {
-        case ALL:
-            return Level.ALL;
-        case TRACE:
-            return Level.TRACE;
-        case DEBUG:
-            return Level.DEBUG;
-        case INFO:
-            return Level.INFO;
-        case WARN:
-            return Level.WARN;
-        case ERROR:
-            return Level.ERROR;
-        case FATAL:
-            return Level.FATAL;
-        default:
-            // TODO Should this be an IllegalStateException?
-            return Level.OFF;
+    public void assertLog(final boolean assertion, final String msg) {
+        if (!assertion) {
+            this.error(msg);
         }
     }
 
     /**
-     * Gets the the {@link LoggerRepository} where this <code>Category</code> instance is attached.
+     * Call the appenders in the hierrachy starting at <code>this</code>. If no appenders could be found, emit a warning.
+     * <p>
+     * This method calls all the appenders inherited from the hierarchy circumventing any evaluation of whether to log or
+     * not to log the particular log request.
+     * </p>
      *
-     * @deprecated Please use {@link #getLoggerRepository()} instead.
-     * @since 1.1
+     * @param event the event to log.
      */
-    @Deprecated
-    public LoggerRepository getHierarchy() {
-        return repository;
+    public void callAppenders(final LoggingEvent event) {
+        int writes = 0;
+        for (Category c = this; c != null; c = c.parent) {
+            // Protected against simultaneous call to addAppender, removeAppender,...
+            synchronized (c) {
+                if (c.aai != null) {
+                    writes += c.aai.appendLoopOnAppenders(event);
+                }
+                if (!c.additive) {
+                    break;
+                }
+            }
+        }
+        if (writes == 0) {
+            repository.emitNoAppenderWarning(this);
+        }
     }
 
     /**
-     * Gets the the {@link LoggerRepository} where this <code>Category</code> is attached.
+     * Closes all attached appenders implementing the AppenderAttachable interface.
      *
-     * @since 1.2
+     * @since 1.0
      */
-    public LoggerRepository getLoggerRepository() {
-        return repository;
-    }
-
-    public Priority getChainedPriority() {
-        return getEffectiveLevel();
-    }
-
-    public final Level getLevel() {
-        return getEffectiveLevel();
-    }
-
-    private String getLevelStr(final Priority priority) {
-        return priority == null ? null : priority.levelStr;
-    }
-
-    public void setLevel(final Level level) {
-        setLevel(getLevelStr(level));
-    }
-
-    public final Level getPriority() {
-        return getEffectiveLevel();
-    }
-
-    public void setPriority(final Priority priority) {
-        setLevel(getLevelStr(priority));
-    }
-
-    private void setLevel(final String levelStr) {
-        if (LogManager.isLog4jCorePresent()) {
-            CategoryUtil.setLevel(logger, org.apache.logging.log4j.Level.toLevel(levelStr));
+    synchronized void closeNestedAppenders() {
+        final Enumeration enumeration = this.getAllAppenders();
+        if (enumeration != null) {
+            while (enumeration.hasMoreElements()) {
+                final Appender a = (Appender) enumeration.nextElement();
+                if (a instanceof AppenderAttachable) {
+                    a.close();
+                }
+            }
         }
     }
 
@@ -253,10 +275,6 @@ public class Category implements AppenderAttachable {
         maybeLog(FQCN, org.apache.logging.log4j.Level.DEBUG, message, t);
     }
 
-    public boolean isDebugEnabled() {
-        return logger.isDebugEnabled();
-    }
-
     public void error(final Object message) {
         maybeLog(FQCN, org.apache.logging.log4j.Level.ERROR, message, null);
     }
@@ -265,22 +283,6 @@ public class Category implements AppenderAttachable {
         maybeLog(FQCN, org.apache.logging.log4j.Level.ERROR, message, t);
     }
 
-    public boolean isErrorEnabled() {
-        return logger.isErrorEnabled();
-    }
-
-    public void warn(final Object message) {
-        maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, null);
-    }
-
-    public void warn(final Object message, final Throwable t) {
-        maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, t);
-    }
-
-    public boolean isWarnEnabled() {
-        return logger.isWarnEnabled();
-    }
-
     public void fatal(final Object message) {
         maybeLog(FQCN, org.apache.logging.log4j.Level.FATAL, message, null);
     }
@@ -289,163 +291,164 @@ public class Category implements AppenderAttachable {
         maybeLog(FQCN, org.apache.logging.log4j.Level.FATAL, message, t);
     }
 
-    public boolean isFatalEnabled() {
-        return logger.isFatalEnabled();
-    }
-
-    public void info(final Object message) {
-        maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, null);
-    }
-
-    public void info(final Object message, final Throwable t) {
-        maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, t);
-    }
-
-    public boolean isInfoEnabled() {
-        return logger.isInfoEnabled();
-    }
-
-    public boolean isEnabledFor(final Priority level) {
-        final org.apache.logging.log4j.Level lvl = org.apache.logging.log4j.Level.toLevel(level.toString());
-        return isEnabledFor(lvl);
-    }
-
     /**
-     * No-op implementation.
-     * @param appender The Appender to add.
-     */
-    @Override
-    public void addAppender(final Appender appender) {
-    }
-
-    /**
-     * No-op implementation.
-     * @param event The logging event.
-     */
-    public void callAppenders(final LoggingEvent event) {
-    }
-
-    /**
-     * Closes all attached appenders implementing the AppenderAttachable interface.
+     * LoggerRepository forgot the fireRemoveAppenderEvent method, if using the stock Hierarchy implementation, then call
+     * its fireRemove. Custom repositories can implement HierarchyEventListener if they want remove notifications.
      *
-     * @since 1.0
+     * @param appender appender, may be null.
      */
-    synchronized void closeNestedAppenders() {
-        final Enumeration enumeration = this.getAllAppenders();
-        if (enumeration != null) {
-            while (enumeration.hasMoreElements()) {
-                final Appender a = (Appender) enumeration.nextElement();
-                if (a instanceof AppenderAttachable) {
-                    a.close();
-                }
+    private void fireRemoveAppenderEvent(final Appender appender) {
+        if (appender != null) {
+            if (repository instanceof Hierarchy) {
+                ((Hierarchy) repository).fireRemoveAppenderEvent(this, appender);
+            } else if (repository instanceof HierarchyEventListener) {
+                ((HierarchyEventListener) repository).removeAppenderEvent(this, appender);
             }
         }
     }
 
-    @Override
-    @SuppressWarnings("rawtypes")
-    public Enumeration getAllAppenders() {
-        return NullEnumeration.getInstance();
-    }
-
-    /**
-     * No-op implementation.
-     * @param name The name of the Appender.
-     * @return null.
-     */
-    @Override
-    public Appender getAppender(final String name) {
-        return null;
+    public void forcedLog(final String fqcn, final Priority level, final Object message, final Throwable t) {
+        final org.apache.logging.log4j.Level lvl = org.apache.logging.log4j.Level.toLevel(level.toString());
+        if (logger instanceof ExtendedLogger) {
+            @SuppressWarnings("unchecked")
+            final Message msg = message instanceof Message ? (Message) message
+                : message instanceof Map ? new MapMessage((Map) message) : new ObjectMessage(message);
+            ((ExtendedLogger) logger).logMessage(fqcn, lvl, null, msg, t);
+        } else {
+            final ObjectRenderer renderer = get(message.getClass());
+            final Message msg = message instanceof Message ? (Message) message
+                : renderer != null ? new RenderedMessage(renderer, message) : new ObjectMessage(message);
+            logger.log(lvl, msg, t);
+        }
     }
 
-    /**
-     Is the appender passed as parameter attached to this category?
-     * @param appender The Appender to add.
-     * @return true if the appender is attached.
-     */
-    @Override
-    public boolean isAttached(final Appender appender) {
-        return false;
+    private <T> ObjectRenderer get(final Class<T> clazz) {
+        ObjectRenderer renderer = null;
+        for (Class<? super T> c = clazz; c != null; c = c.getSuperclass()) {
+            renderer = rendererMap.get(c);
+            if (renderer != null) {
+                return renderer;
+            }
+            renderer = searchInterfaces(c);
+            if (renderer != null) {
+                return renderer;
+            }
+        }
+        return null;
     }
 
-    /**
-     * No-op implementation.
-     */
-    @Override
-    public void removeAllAppenders() {
+    public boolean getAdditivity() {
+        return LogManager.isLog4jCorePresent() ? CategoryUtil.isAdditive(logger) : false;
     }
 
     /**
-     * No-op implementation.
-     * @param appender The Appender to remove.
+     * Get the appenders contained in this category as an {@link Enumeration}. If no appenders can be found, then a
+     * {@link NullEnumeration} is returned.
+     *
+     * @return Enumeration An enumeration of the appenders in this category.
      */
     @Override
-    public void removeAppender(final Appender appender) {
+    @SuppressWarnings("rawtypes")
+    public Enumeration getAllAppenders() {
+        return aai == null ? NullEnumeration.getInstance() : aai.getAllAppenders();
     }
 
     /**
-     * No-op implementation.
-     * @param name The Appender to remove.
+     * Look for the appender named as <code>name</code>.
+     * <p>
+     * Return the appender with that name if in the list. Return <code>null</code> otherwise.
+     * </p>
      */
     @Override
-    public void removeAppender(final String name) {
+    public Appender getAppender(final String name) {
+        Appender appender = aai != null ? aai.getAppender(name) : null;
+        if (appender == null && LogManager.isLog4jCorePresent()) {
+            final org.apache.logging.log4j.core.Appender coreAppender = CategoryUtil.getAppenders(logger).get(name);
+            if (coreAppender != null) {
+                addAppender(appender = new AppenderWrapper(coreAppender));
+            }
+        }
+        return appender;
     }
 
-    /**
-     * No-op implementation.
-     */
-    public static void shutdown() {
+    public Priority getChainedPriority() {
+        return getEffectiveLevel();
     }
 
-    public void forcedLog(final String fqcn, final Priority level, final Object message, final Throwable t) {
-        final org.apache.logging.log4j.Level lvl = org.apache.logging.log4j.Level.toLevel(level.toString());
-        if (logger instanceof ExtendedLogger) {
-            @SuppressWarnings("unchecked")
-            final
-            Message msg = message instanceof Message ? (Message) message : message instanceof Map ?
-                    new MapMessage((Map) message) : new ObjectMessage(message);
-            ((ExtendedLogger) logger).logMessage(fqcn, lvl, null, msg, t);
-        } else {
-            final ObjectRenderer renderer = get(message.getClass());
-            final Message msg = message instanceof Message ? (Message) message : renderer != null ?
-                    new RenderedMessage(renderer, message) : new ObjectMessage(message);
-            logger.log(lvl, msg, t);
+    public Level getEffectiveLevel() {
+        switch (logger.getLevel().getStandardLevel()) {
+        case ALL:
+            return Level.ALL;
+        case TRACE:
+            return Level.TRACE;
+        case DEBUG:
+            return Level.DEBUG;
+        case INFO:
+            return Level.INFO;
+        case WARN:
+            return Level.WARN;
+        case ERROR:
+            return Level.ERROR;
+        case FATAL:
+            return Level.FATAL;
+        default:
+            // TODO Should this be an IllegalStateException?
+            return Level.OFF;
         }
     }
 
     /**
-     * Tests if the named category exists (in the default hierarchy).
-     *
-     * @param name The name to test.
-     * @return Whether the name exists.
+     * Gets the the {@link LoggerRepository} where this <code>Category</code> instance is attached.
      *
-     * @deprecated Please use {@link LogManager#exists(String)} instead.
-     * @since 0.8.5
+     * @deprecated Please use {@link #getLoggerRepository()} instead.
+     * @since 1.1
      */
     @Deprecated
-    public static Logger exists(final String name) {
-        return LogManager.exists(name);
+    public LoggerRepository getHierarchy() {
+        return repository;
     }
 
-    public boolean getAdditivity() {
-        return LogManager.isLog4jCorePresent() ? CategoryUtil.isAdditive(logger) : false;
+    public final Level getLevel() {
+        return getEffectiveLevel();
     }
 
-    public void setAdditivity(final boolean additivity) {
-        if (LogManager.isLog4jCorePresent()) {
-            CategoryUtil.setAdditivity(logger, additivity);
-        }
+    private String getLevelStr(final Priority priority) {
+        return priority == null ? null : priority.levelStr;
+    }
+
+    org.apache.logging.log4j.Logger getLogger() {
+        return logger;
     }
 
     /**
-     * Only the Hiearchy class can set the hiearchy of a category. Default package access is MANDATORY here.
+     * Gets the the {@link LoggerRepository} where this <code>Category</code> is attached.
+     *
+     * @since 1.2
      */
-    final void setHierarchy(final LoggerRepository repository) {
-        this.repository = repository;
+    public LoggerRepository getLoggerRepository() {
+        return repository;
     }
 
-    public void setResourceBundle(final ResourceBundle bundle) {
-        this.bundle = bundle;
+    public final String getName() {
+        return logger.getName();
+    }
+
+    public final Category getParent() {
+        if (!LogManager.isLog4jCorePresent()) {
+            return null;
+        }
+        final org.apache.logging.log4j.Logger parent = CategoryUtil.getParent(logger);
+        final LoggerContext loggerContext = CategoryUtil.getLoggerContext(logger);
+        if (parent == null || loggerContext == null) {
+            return null;
+        }
+        final ConcurrentMap<String, Logger> loggers = Hierarchy.getLoggersMap(loggerContext);
+        final Logger parentLogger = loggers.get(parent.getName());
+        return parentLogger == null ? new Category(parent) : parentLogger;
+    }
+
+    public final Level getPriority() {
+        return getEffectiveLevel();
     }
 
     public ResourceBundle getResourceBundle() {
@@ -471,39 +474,51 @@ public class Category implements AppenderAttachable {
         return null;
     }
 
-    private static  String getSubName(final String name) {
-        if (Strings.isEmpty(name)) {
-            return null;
-        }
-        final int i = name.lastIndexOf('.');
-        return i > 0 ? name.substring(0, i) : Strings.EMPTY;
+    public void info(final Object message) {
+        maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, null);
+    }
+
+    public void info(final Object message, final Throwable t) {
+        maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, t);
     }
 
     /**
-     * If <code>assertion</code> parameter is {@code false}, then logs
-     * <code>msg</code> as an {@link #error(Object) error} statement.
+     * Is the appender passed as parameter attached to this category?
      *
-     * <p>
-     * The <code>assert</code> method has been renamed to <code>assertLog</code>
-     * because <code>assert</code> is a language reserved word in JDK 1.4.
-     * </p>
-     *
-     * @param assertion The assertion.
-     * @param msg       The message to print if <code>assertion</code> is false.
-     *
-     * @since 1.2
+     * @param appender The Appender to add.
+     * @return true if the appender is attached.
      */
-    public void assertLog(final boolean assertion, final String msg) {
-        if (!assertion) {
-            this.error(msg);
-        }
+    @Override
+    public boolean isAttached(final Appender appender) {
+        return aai == null ? false : aai.isAttached(appender);
     }
 
-    public void l7dlog(final Priority priority, final String key, final Throwable t) {
-        if (isEnabledFor(priority)) {
-            final Message msg = new LocalizedMessage(bundle, key, null);
-            forcedLog(FQCN, priority, msg, t);
-        }
+    public boolean isDebugEnabled() {
+        return logger.isDebugEnabled();
+    }
+
+    private boolean isEnabledFor(final org.apache.logging.log4j.Level level) {
+        return logger.isEnabled(level);
+    }
+
+    public boolean isEnabledFor(final Priority level) {
+        return isEnabledFor(org.apache.logging.log4j.Level.toLevel(level.toString()));
+    }
+
+    public boolean isErrorEnabled() {
+        return logger.isErrorEnabled();
+    }
+
+    public boolean isFatalEnabled() {
+        return logger.isFatalEnabled();
+    }
+
+    public boolean isInfoEnabled() {
+        return logger.isInfoEnabled();
+    }
+
+    public boolean isWarnEnabled() {
+        return logger.isWarnEnabled();
     }
 
     public void l7dlog(final Priority priority, final String key, final Object[] params, final Throwable t) {
@@ -513,10 +528,9 @@ public class Category implements AppenderAttachable {
         }
     }
 
-    public void log(final Priority priority, final Object message, final Throwable t) {
+    public void l7dlog(final Priority priority, final String key, final Throwable t) {
         if (isEnabledFor(priority)) {
-            @SuppressWarnings("unchecked")
-            final Message msg = message instanceof Map ? new MapMessage((Map) message) : new ObjectMessage(message);
+            final Message msg = new LocalizedMessage(bundle, key, null);
             forcedLog(FQCN, priority, msg, t);
         }
     }
@@ -529,6 +543,14 @@ public class Category implements AppenderAttachable {
         }
     }
 
+    public void log(final Priority priority, final Object message, final Throwable t) {
+        if (isEnabledFor(priority)) {
+            @SuppressWarnings("unchecked")
+            final Message msg = message instanceof Map ? new MapMessage((Map) message) : new ObjectMessage(message);
+            forcedLog(FQCN, priority, msg, t);
+        }
+    }
+
     public void log(final String fqcn, final Priority priority, final Object message, final Throwable t) {
         if (isEnabledFor(priority)) {
             final Message msg = new ObjectMessage(message);
@@ -536,18 +558,13 @@ public class Category implements AppenderAttachable {
         }
     }
 
-    void maybeLog(
-            final String fqcn,
-            final org.apache.logging.log4j.Level level,
-            final Object message,
-            final Throwable throwable) {
+    void maybeLog(final String fqcn, final org.apache.logging.log4j.Level level, final Object message, final Throwable throwable) {
         if (logger.isEnabled(level)) {
             final Message msg;
             if (message instanceof String) {
                 msg = new SimpleMessage((String) message);
             }
-            // SimpleMessage treats String and CharSequence differently, hence
-            // this else-if block.
+            // SimpleMessage treats String and CharSequence differently, hence this else-if block.
             else if (message instanceof CharSequence) {
                 msg = new SimpleMessage((CharSequence) message);
             } else if (message instanceof Map) {
@@ -565,23 +582,61 @@ public class Category implements AppenderAttachable {
         }
     }
 
-    private boolean isEnabledFor(final org.apache.logging.log4j.Level level) {
-        return logger.isEnabled(level);
-    }
-
-    private <T> ObjectRenderer get(final Class<T> clazz) {
-        ObjectRenderer renderer = null;
-        for (Class<? super T> c = clazz; c != null; c = c.getSuperclass()) {
-            renderer = rendererMap.get(c);
-            if (renderer != null) {
-                return renderer;
+    /**
+     * Removes all previously added appenders from this Category instance.
+     * <p>
+     * This is useful when re-reading configuration information.
+     * </p>
+     */
+    @Override
+    public void removeAllAppenders() {
+        if (aai != null) {
+            final Vector appenders = new Vector();
+            for (final Enumeration iter = aai.getAllAppenders(); iter != null && iter.hasMoreElements();) {
+                appenders.add(iter.nextElement());
             }
-            renderer = searchInterfaces(c);
-            if (renderer != null) {
-                return renderer;
+            aai.removeAllAppenders();
+            for (final Object appender : appenders) {
+                fireRemoveAppenderEvent((Appender) appender);
             }
+            aai = null;
+        }
+    }
+
+    /**
+     * Removes the appender passed as parameter form the list of appenders.
+     *
+     * @param appender The Appender to remove.
+     * @since 0.8.2
+     */
+    @Override
+    public void removeAppender(final Appender appender) {
+        if (appender == null || aai == null) {
+            return;
+        }
+        final boolean wasAttached = aai.isAttached(appender);
+        aai.removeAppender(appender);
+        if (wasAttached) {
+            fireRemoveAppenderEvent(appender);
+        }
+    }
+
+    /**
+     * Removes the appender with the name passed as parameter form the list of appenders.
+     *
+     * @param name The Appender to remove.
+     * @since 0.8.2
+     */
+    @Override
+    public void removeAppender(final String name) {
+        if (name == null || aai == null) {
+            return;
+        }
+        final Appender appender = aai.getAppender(name);
+        aai.removeAppender(name);
+        if (appender != null) {
+            fireRemoveAppenderEvent(appender);
         }
-        return null;
     }
 
     ObjectRenderer searchInterfaces(final Class<?> c) {
@@ -599,4 +654,43 @@ public class Category implements AppenderAttachable {
         return null;
     }
 
+    public void setAdditivity(final boolean additivity) {
+        if (LogManager.isLog4jCorePresent()) {
+            CategoryUtil.setAdditivity(logger, additivity);
+        }
+    }
+
+    /**
+     * Only the Hiearchy class can set the hiearchy of a category. Default package access is MANDATORY here.
+     */
+    final void setHierarchy(final LoggerRepository repository) {
+        this.repository = repository;
+    }
+
+    public void setLevel(final Level level) {
+        setLevel(getLevelStr(level));
+    }
+
+    private void setLevel(final String levelStr) {
+        if (LogManager.isLog4jCorePresent()) {
+            CategoryUtil.setLevel(logger, org.apache.logging.log4j.Level.toLevel(levelStr));
+        }
+    }
+
+    public void setPriority(final Priority priority) {
+        setLevel(getLevelStr(priority));
+    }
+
+    public void setResourceBundle(final ResourceBundle bundle) {
+        this.bundle = bundle;
+    }
+
+    public void warn(final Object message) {
+        maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, null);
+    }
+
+    public void warn(final Object message, final Throwable t) {
+        maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, t);
+    }
+
 }
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java
index a7a059a..8b2f1f9 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java
@@ -16,7 +16,7 @@
  */
 
 // WARNING This class MUST not have references to the Category or
-// WARNING RootCategory classes in its static initiliazation neither
+// WARNING RootCategory classes in its static initialization neither
 // WARNING directly nor indirectly.
 
 package org.apache.log4j;
@@ -29,17 +29,19 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
 import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.legacy.core.ContextUtil;
 import org.apache.log4j.or.ObjectRenderer;
 import org.apache.log4j.or.RendererMap;
 import org.apache.log4j.spi.HierarchyEventListener;
 import org.apache.log4j.spi.LoggerFactory;
+import org.apache.log4j.spi.LoggerRepository;
 import org.apache.log4j.spi.RendererSupport;
 import org.apache.log4j.spi.ThrowableRenderer;
 import org.apache.log4j.spi.ThrowableRendererSupport;
 import org.apache.logging.log4j.core.appender.AsyncAppender;
 import org.apache.logging.log4j.spi.AbstractLoggerAdapter;
 import org.apache.logging.log4j.spi.LoggerContext;
-import org.apache.logging.log4j.util.Strings;
+import org.apache.logging.log4j.util.StackLocatorUtil;
 
 /**
  * This class is specialized in retrieving loggers by name and also maintaining the logger hierarchy.
@@ -58,7 +60,7 @@ import org.apache.logging.log4j.util.Strings;
  * provision node.
  * </p>
  */
-public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableRendererSupport {
+public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport {
 
     private static class PrivateLoggerAdapter extends AbstractLoggerAdapter<Logger> {
 
@@ -170,12 +172,14 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      *
      * <p>
      * You should <em>really</em> know what you are doing before invoking this method.
+     * </p>
      *
      * @since 0.9.0
      */
     public void clear() {
         // System.out.println("\n\nAbout to clear internal hash table.");
         ht.clear();
+        getLoggersMap(getContext()).clear();
     }
 
     @Override
@@ -197,8 +201,15 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      */
     @Override
     public Logger exists(final String name) {
-        final LoggerContext ctx = getContext();
-        if (!ctx.hasLogger(name)) {
+        return exists(name, getContext());
+    }
+
+    Logger exists(final String name, final ClassLoader classLoader) {
+        return exists(name, getContext(classLoader));
+    }
+
+    Logger exists(final String name, final LoggerContext loggerContext) {
+        if (!loggerContext.hasLogger(name)) {
             return null;
         }
         return Logger.getLogger(name);
@@ -227,6 +238,10 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
         }
     }
 
+    LoggerContext getContext(final ClassLoader classLoader) {
+        return LogManager.getContext(classLoader);
+    }
+
     /**
      * @deprecated Please use {@link #getCurrentLoggers} instead.
      */
@@ -241,22 +256,25 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      *
      * <p>
      * The root logger is <em>not</em> included in the returned {@link Enumeration}.
+     * </p>
      */
     @Override
     public Enumeration getCurrentLoggers() {
         // The accumlation in v is necessary because not all elements in
         // ht are Logger objects as there might be some ProvisionNodes
         // as well.
-        final Vector v = new Vector(ht.size());
+//        final Vector v = new Vector(ht.size());
+//
+//        final Enumeration elems = ht.elements();
+//        while (elems.hasMoreElements()) {
+//            final Object o = elems.nextElement();
+//            if (o instanceof Logger) {
+//                v.addElement(o);
+//            }
+//        }
+//        return v.elements();
 
-        final Enumeration elems = ht.elements();
-        while (elems.hasMoreElements()) {
-            final Object o = elems.nextElement();
-            if (o instanceof Logger) {
-                v.addElement(o);
-            }
-        }
-        return v.elements();
+        return LogManager.getCurrentLoggers(StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
@@ -265,6 +283,7 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      * <p>
      * If a logger of that name already exists, then it will be returned. Otherwise, a new logger will be instantiated and
      * then linked with its existing ancestors as well as children.
+     * </p>
      *
      * @param name The name of the logger to retrieve.
      *
@@ -274,9 +293,8 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
         return getInstance(getContext(), name);
     }
 
-    @Override
-    public Logger getLogger(final String name, final ClassLoader classLoader) {
-        return getInstance(LogManager.getContext(classLoader), name);
+    Logger getLogger(final String name, final ClassLoader classLoader) {
+        return getInstance(getContext(classLoader), name);
     }
 
     /**
@@ -285,6 +303,7 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      * <p>
      * If a logger of that name already exists, then it will be returned. Otherwise, a new logger will be instantiated by
      * the <code>factory</code> parameter and linked with its existing ancestors as well as children.
+     * </p>
      *
      * @param name The name of the logger to retrieve.
      * @param factory The factory that will make the new logger instance.
@@ -295,9 +314,8 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
         return getInstance(getContext(), name, factory);
     }
 
-    @Override
-    public Logger getLogger(final String name, final LoggerFactory factory, final ClassLoader classLoader) {
-        return getInstance(LogManager.getContext(classLoader), name, factory);
+    Logger getLogger(final String name, final LoggerFactory factory, final ClassLoader classLoader) {
+        return getInstance(getContext(classLoader), name, factory);
     }
 
     /**
@@ -318,6 +336,10 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
         return getInstance(getContext(), org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME);
     }
 
+    Logger getRootLogger(final ClassLoader classLoader) {
+        return getInstance(getContext(classLoader), org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME);
+    }
+
     /**
      * Gets a {@link Level} representation of the <code>enable</code> state.
      *
@@ -361,6 +383,7 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      *
      * <p>
      * Existing categories are not removed. They are just reset.
+     * </p>
      *
      * <p>
      * This method should be used sparingly and with care as it will block all logging until it is completed.
@@ -370,6 +393,15 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      */
     @Override
     public void resetConfiguration() {
+        resetConfiguration(getContext());
+    }
+
+    void resetConfiguration(final ClassLoader classLoader) {
+        resetConfiguration(getContext(classLoader));
+    }
+
+    void resetConfiguration(final LoggerContext loggerContext) {
+        getLoggersMap(loggerContext).clear();
 
         getRootLogger().setLevel(Level.DEBUG);
         root.setResourceBundle(null);
@@ -413,13 +445,13 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
     /**
      * Enable logging for logging requests with level <code>l</code> or higher. By default all levels are enabled.
      *
-     * @param l The minimum level for which logging requests are sent to their appenders.
+     * @param level The minimum level for which logging requests are sent to their appenders.
      */
     @Override
-    public void setThreshold(final Level l) {
-        if (l != null) {
-            thresholdInt = l.level;
-            threshold = l;
+    public void setThreshold(final Level level) {
+        if (level != null) {
+            thresholdInt = level.level;
+            threshold = level;
         }
     }
 
@@ -428,9 +460,9 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      */
     @Override
     public void setThreshold(final String levelStr) {
-        final Level l = Level.toLevel(levelStr, null);
-        if (l != null) {
-            setThreshold(l);
+        final Level level = Level.toLevel(levelStr, null);
+        if (level != null) {
+            setThreshold(level);
         } else {
             LogLog.warn("Could not convert [" + levelStr + "] to Level.");
         }
@@ -440,8 +472,8 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      * {@inheritDoc}
      */
     @Override
-    public void setThrowableRenderer(final ThrowableRenderer renderer) {
-        throwableRenderer = renderer;
+    public void setThrowableRenderer(final ThrowableRenderer throwableRenderer) {
+        this.throwableRenderer = throwableRenderer;
     }
 
     /**
@@ -461,26 +493,37 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      */
     @Override
     public void shutdown() {
-        final Logger root = getRootLogger();
-
-        // begin by closing nested appenders
-        root.closeNestedAppenders();
-
-        synchronized (ht) {
-            Enumeration cats = this.getCurrentLoggers();
-            while (cats.hasMoreElements()) {
-                final Logger c = (Logger) cats.nextElement();
-                c.closeNestedAppenders();
-            }
-
-            // then, remove all appenders
-            root.removeAllAppenders();
-            cats = this.getCurrentLoggers();
-            while (cats.hasMoreElements()) {
-                final Logger c = (Logger) cats.nextElement();
-                c.removeAllAppenders();
-            }
-        }
+      shutdown(getContext());
+  }
+
+    public void shutdown(final ClassLoader classLoader) {
+      shutdown(org.apache.logging.log4j.LogManager.getContext(classLoader, false));
+  }
+
+    void shutdown(final LoggerContext context) {
+//      final Logger root = getRootLogger();
+//      // begin by closing nested appenders
+//      root.closeNestedAppenders();
+//
+//      synchronized (ht) {
+//          Enumeration cats = this.getCurrentLoggers();
+//          while (cats.hasMoreElements()) {
+//              final Logger c = (Logger) cats.nextElement();
+//              c.closeNestedAppenders();
+//          }
+//
+//          // then, remove all appenders
+//          root.removeAllAppenders();
+//          cats = this.getCurrentLoggers();
+//          while (cats.hasMoreElements()) {
+//              final Logger c = (Logger) cats.nextElement();
+//              c.removeAllAppenders();
+//          }
+//      }
+        getLoggersMap(context).clear();
+          if (LogManager.isLog4jCorePresent()) {
+              ContextUtil.shutdown(context);
+          }
     }
 
     /**
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java b/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java
index 46bff05..b18c8f1 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java
@@ -69,20 +69,30 @@ public final class LogManager {
         LOG4J_CORE_PRESENT = checkLog4jCore();
         //
         // By default we use a DefaultRepositorySelector which always returns 'hierarchy'.
-        Hierarchy hierarchy = new Hierarchy(new RootLogger(Level.DEBUG));
+        final Hierarchy hierarchy = new Hierarchy(new RootLogger(Level.DEBUG));
         repositorySelector = new DefaultRepositorySelector(hierarchy);
     }
 
     private static boolean checkLog4jCore() {
         try {
             return Class.forName("org.apache.logging.log4j.core.LoggerContext") != null;
-        } catch (Exception ex) {
+        } catch (final Throwable ex) {
             return false;
         }
     }
 
+    /**
+     * Tests if a logger for the given name exists.
+     *
+     * @param name logger name to test.
+     * @return whether a logger for the given name exists.
+     */
     public static Logger exists(final String name) {
-        return getLoggerRepository().exists(name);
+        return exists(name, StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    static Logger exists(final String name, final ClassLoader classLoader) {
+        return getHierarchy().exists(name, classLoader);
     }
 
     /**
@@ -98,7 +108,7 @@ public final class LogManager {
 
     /**
      * Gets an enumeration of the current loggers.
-     * 
+     *
      * @return an enumeration of the current loggers.
      */
     @SuppressWarnings("rawtypes")
@@ -116,27 +126,42 @@ public final class LogManager {
         // @formatter:on
     }
 
+    static Hierarchy getHierarchy() {
+        final LoggerRepository loggerRepository = getLoggerRepository();
+        return loggerRepository instanceof Hierarchy ? (Hierarchy) loggerRepository : null;
+    }
+
+    /**
+     * Gets the logger for the given class.
+     */
     public static Logger getLogger(final Class<?> clazz) {
-        // Depth 2 gets the call site of this method.
-        return getLoggerRepository2().getLogger(clazz.getName(), StackLocatorUtil.getCallerClassLoader(2));
+        final Hierarchy hierarchy = getHierarchy();
+        return hierarchy != null ? hierarchy.getLogger(clazz.getName(), StackLocatorUtil.getCallerClassLoader(2))
+            : getLoggerRepository().getLogger(clazz.getName());
     }
 
+    /**
+     * Gets the logger for the given name.
+     */
     public static Logger getLogger(final String name) {
-        // Depth 2 gets the call site of this method.
-        return getLoggerRepository2().getLogger(name, StackLocatorUtil.getCallerClassLoader(2));
+        final Hierarchy hierarchy = getHierarchy();
+        return hierarchy != null ? hierarchy.getLogger(name, StackLocatorUtil.getCallerClassLoader(2)) : getLoggerRepository().getLogger(name);
     }
 
     static Logger getLogger(final String name, final ClassLoader classLoader) {
-        return getLoggerRepository2().getLogger(name, classLoader);
+        final Hierarchy hierarchy = getHierarchy();
+        return hierarchy != null ? hierarchy.getLogger(name, classLoader) : getLoggerRepository().getLogger(name);
     }
 
     public static Logger getLogger(final String name, final LoggerFactory factory) {
-        // Depth 2 gets the call site of this method.
-        return getLoggerRepository2().getLogger(name, factory, StackLocatorUtil.getCallerClassLoader(2));
+        final Hierarchy hierarchy = getHierarchy();
+        return hierarchy != null ? hierarchy.getLogger(name, factory, StackLocatorUtil.getCallerClassLoader(2))
+            : getLoggerRepository().getLogger(name, factory);
     }
 
     static Logger getLogger(final String name, final LoggerFactory factory, final ClassLoader classLoader) {
-        return getLoggerRepository2().getLogger(name, factory, classLoader);
+        final Hierarchy hierarchy = getHierarchy();
+        return hierarchy != null ? hierarchy.getLogger(name, factory, classLoader) : getLoggerRepository().getLogger(name, factory);
     }
 
     public static LoggerRepository getLoggerRepository() {
@@ -146,48 +171,62 @@ public final class LogManager {
         return repositorySelector.getLoggerRepository();
     }
 
-    static LoggerRepository2 getLoggerRepository2() {
-        // TODO Hack
-        return (LoggerRepository2) getLoggerRepository();
+    /**
+     * Gets the root logger.
+     */
+    public static Logger getRootLogger() {
+        return getRootLogger(StackLocatorUtil.getCallerClassLoader(2));
     }
 
-    public static Logger getRootLogger() {
-        return getLoggerRepository2().getRootLogger();
+    static Logger getRootLogger(final ClassLoader classLoader) {
+        final Hierarchy hierarchy = getHierarchy();
+        return hierarchy != null ? hierarchy.getRootLogger(classLoader) : getLoggerRepository().getRootLogger();
     }
 
     static boolean isLog4jCorePresent() {
         return LOG4J_CORE_PRESENT;
     }
 
-    static void reconfigure() {
+    static void reconfigure(final ClassLoader classLoader) {
         if (isLog4jCorePresent()) {
-            ContextUtil.reconfigure(Hierarchy.getContext());
+            ContextUtil.reconfigure(LogManager.getContext(classLoader));
         }
     }
 
-    /**
-     * No-op implementation.
-     */
     public static void resetConfiguration() {
-        // noop
+        resetConfiguration(StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    static void resetConfiguration(final ClassLoader classLoader) {
+        final Hierarchy hierarchy = getHierarchy();
+        if (hierarchy != null) {
+            hierarchy.resetConfiguration(classLoader);
+        } else {
+            getLoggerRepository().resetConfiguration();
+        }
     }
 
-    /**
-     * No-op implementation.
-     * 
-     * @param selector The RepositorySelector.
-     * @param guard prevents calls at the incorrect time.
-     * @throws IllegalArgumentException if a parameter is invalid.
-     */
     public static void setRepositorySelector(final RepositorySelector selector, final Object guard) throws IllegalArgumentException {
-        // noop
+        if (selector == null) {
+            throw new IllegalArgumentException("RepositorySelector must be non-null.");
+        }
+        LogManager.repositorySelector = selector;
     }
 
     /**
-     * No-op implementation.
+     * Shuts down the current configuration.
      */
     public static void shutdown() {
-        // noop
+        shutdown(StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    static void shutdown(final ClassLoader classLoader) {
+        final Hierarchy hierarchy = getHierarchy();
+        if (hierarchy != null) {
+            hierarchy.shutdown(classLoader);
+        } else {
+            getLoggerRepository().shutdown();
+        }
     }
 
 }
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/LoggerRepository2.java b/log4j-1.2-api/src/main/java/org/apache/log4j/LoggerRepository2.java
deleted file mode 100644
index 30c88ee..0000000
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/LoggerRepository2.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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;
-
-import org.apache.log4j.spi.LoggerFactory;
-import org.apache.log4j.spi.LoggerRepository;
-
-/**
- * A LoggerRepository that accounts for the caller's class loader.
- */
-interface LoggerRepository2 extends LoggerRepository {
-
-    Logger getLogger(String name, ClassLoader classLoader);
-
-    Logger getLogger(String name, LoggerFactory factory, ClassLoader classLoader);
-}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java
index 808220a..45a96d4 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java
@@ -16,114 +16,657 @@
  */
 package org.apache.log4j;
 
+import java.io.IOException;
 import java.io.InputStream;
+import java.io.InterruptedIOException;
 import java.net.URL;
+import java.net.URLConnection;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Map;
 import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Vector;
 
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.config.PropertySetter;
+import org.apache.log4j.helpers.FileWatchdog;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.or.RendererMap;
 import org.apache.log4j.spi.Configurator;
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggerFactory;
 import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.OptionHandler;
+import org.apache.log4j.spi.RendererSupport;
+import org.apache.log4j.spi.ThrowableRenderer;
+import org.apache.log4j.spi.ThrowableRendererSupport;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.util.StackLocatorUtil;
 
 /**
- * A configurator for properties.
+ * Configures Log4j from properties.
  */
 public class PropertyConfigurator implements Configurator {
 
+    static class NameValue {
+        String key, value;
+
+        public NameValue(final String key, final String value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return key + "=" + value;
+        }
+    }
+
+    static class PropertyWatchdog extends FileWatchdog {
+
+        private final ClassLoader classLoader;
+
+        PropertyWatchdog(final String fileName, final ClassLoader classLoader) {
+            super(fileName);
+            this.classLoader = classLoader;
+        }
+
+        /**
+         * Call {@link PropertyConfigurator#configure(String)} with the <code>filename</code> to reconfigure log4j.
+         */
+        @Override
+        public void doOnChange() {
+            new PropertyConfigurator().doConfigure(filename, LogManager.getLoggerRepository(), classLoader);
+        }
+    }
+
+    class SortedKeyEnumeration implements Enumeration {
+
+        private final Enumeration e;
+
+        public SortedKeyEnumeration(final Hashtable ht) {
+            final Enumeration f = ht.keys();
+            final Vector keys = new Vector(ht.size());
+            for (int i, last = 0; f.hasMoreElements(); ++last) {
+                final String key = (String) f.nextElement();
+                for (i = 0; i < last; ++i) {
+                    final String s = (String) keys.get(i);
+                    if (key.compareTo(s) <= 0) {
+                        break;
+                    }
+                }
+                keys.add(i, key);
+            }
+            e = keys.elements();
+        }
+
+        @Override
+        public boolean hasMoreElements() {
+            return e.hasMoreElements();
+        }
+
+        @Override
+        public Object nextElement() {
+            return e.nextElement();
+        }
+    }
+
+    static final String CATEGORY_PREFIX = "log4j.category.";
+    static final String LOGGER_PREFIX = "log4j.logger.";
+    static final String FACTORY_PREFIX = "log4j.factory";
+    static final String ADDITIVITY_PREFIX = "log4j.additivity.";
+    static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
+    static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
+    static final String APPENDER_PREFIX = "log4j.appender.";
+    static final String RENDERER_PREFIX = "log4j.renderer.";
+    static final String THRESHOLD_PREFIX = "log4j.threshold";
+
+    private static final String THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer";
+    private static final String LOGGER_REF = "logger-ref";
+    private static final String ROOT_REF = "root-ref";
+    private static final String APPENDER_REF_TAG = "appender-ref";
+
+    /**
+     * Key for specifying the {@link org.apache.log4j.spi.LoggerFactory LoggerFactory}. Currently set to
+     * "<code>log4j.loggerFactory</code>".
+     */
+    public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory";
+
+    /**
+     * If property set to true, then hierarchy will be reset before configuration.
+     */
+    private static final String RESET_KEY = "log4j.reset";
+
+    static final private String INTERNAL_ROOT_NAME = "root";
+
     /**
      * Reads configuration options from an InputStream.
      *
      * @param inputStream The input stream
      */
     public static void configure(final InputStream inputStream) {
+        new PropertyConfigurator().doConfigure(inputStream, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
-     * Read configuration options from <code>properties</code>.
+     * Reads configuration options from <code>properties</code>.
      *
      * See {@link #doConfigure(String, LoggerRepository)} for the expected format.
      *
      * @param properties The properties
      */
     public static void configure(final Properties properties) {
+        new PropertyConfigurator().doConfigure(properties, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
-     * Read configuration options from configuration file.
+     * Reads configuration options from configuration file.
      *
-     * @param configFileName The configuration file.
+     * @param fileName The configuration file.
      */
-    public static void configure(final String configFileName) {
+    public static void configure(final String fileName) {
+        new PropertyConfigurator().doConfigure(fileName, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
-     * Read configuration options from url <code>configURL</code>.
+     * Reads configuration options from url <code>configURL</code>.
      *
      * @param configURL The configuration URL
      */
     public static void configure(final URL configURL) {
+        new PropertyConfigurator().doConfigure(configURL, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
-     * Like {@link #configureAndWatch(String, long)} except that the
-     * default delay as defined by FileWatchdog.DEFAULT_DELAY is
-     * used.
+     * Like {@link #configureAndWatch(String, long)} except that the default delay as defined by FileWatchdog.DEFAULT_DELAY
+     * is used.
      *
      * @param configFilename A file in key=value format.
      */
     public static void configureAndWatch(final String configFilename) {
+        configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY, StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
-     * Read the configuration file <code>configFilename</code> if it
-     * exists. Moreover, a thread will be created that will periodically
-     * check if <code>configFilename</code> has been created or
-     * modified. The period is determined by the <code>delay</code>
-     * argument. If a change or file creation is detected, then
-     * <code>configFilename</code> is read to configure log4j.
+     * Reads the configuration file <code>configFilename</code> if it exists. Moreover, a thread will be created that will
+     * periodically check if <code>configFilename</code> has been created or modified. The period is determined by the
+     * <code>delay</code> argument. If a change or file creation is detected, then <code>configFilename</code> is read to
+     * configure log4j.
      *
      * @param configFilename A file in key=value format.
-     * @param delay The delay in milliseconds to wait between each check.
+     * @param delayMillis The delay in milliseconds to wait between each check.
      */
-    public static void configureAndWatch(final String configFilename, final long delay) {
+    public static void configureAndWatch(final String configFilename, final long delayMillis) {
+        configureAndWatch(configFilename, delayMillis, StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    static void configureAndWatch(final String configFilename, final long delay, final ClassLoader classLoader) {
+        final PropertyWatchdog watchdog = new PropertyWatchdog(configFilename, classLoader);
+        watchdog.setDelay(delay);
+        watchdog.start();
+    }
+
+    private static Configuration reconfigure(final Configuration configuration) {
+        org.apache.logging.log4j.core.config.Configurator.reconfigure(configuration);
+        return configuration;
     }
 
     /**
-     * Read configuration options from an InputStream.
+     * Used internally to keep track of configured appenders.
+     */
+    protected Hashtable registry = new Hashtable(11);
+
+    private LoggerRepository repository;
+
+    protected LoggerFactory loggerFactory = new DefaultCategoryFactory();
+
+    /**
+     * Checks the provided <code>Properties</code> object for a {@link org.apache.log4j.spi.LoggerFactory LoggerFactory}
+     * entry specified by {@link #LOGGER_FACTORY_KEY}. If such an entry exists, an attempt is made to create an instance
+     * using the default constructor. This instance is used for subsequent Category creations within this configurator.
+     *
+     * @see #parseCatsAndRenderers
+     */
+    protected void configureLoggerFactory(final Properties properties) {
+        final String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY, properties);
+        if (factoryClassName != null) {
+            LogLog.debug("Setting category factory to [" + factoryClassName + "].");
+            loggerFactory = (LoggerFactory) OptionConverter.instantiateByClassName(factoryClassName, LoggerFactory.class, loggerFactory);
+            PropertySetter.setProperties(loggerFactory, properties, FACTORY_PREFIX + ".");
+        }
+    }
+
+    void configureRootCategory(final Properties properties, final LoggerRepository loggerRepository) {
+        String effectiveFrefix = ROOT_LOGGER_PREFIX;
+        String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, properties);
+
+        if (value == null) {
+            value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, properties);
+            effectiveFrefix = ROOT_CATEGORY_PREFIX;
+        }
+
+        if (value == null) {
+            LogLog.debug("Could not find root logger information. Is this OK?");
+        } else {
+            final Logger root = loggerRepository.getRootLogger();
+            synchronized (root) {
+                parseCategory(properties, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
+            }
+        }
+    }
+
+    /**
+     * Reads configuration options from an InputStream.
      *
      * @param inputStream The input stream
-     * @param hierarchy The hierarchy
+     * @param loggerRepository The hierarchy
      */
     @Override
-    public void doConfigure(final InputStream inputStream, final LoggerRepository hierarchy) {
+    public void doConfigure(final InputStream inputStream, final LoggerRepository loggerRepository) {
+        doConfigure(inputStream, loggerRepository, StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    Configuration doConfigure(final InputStream inputStream, final LoggerRepository loggerRepository, final ClassLoader classLoader) {
+        return doConfigure(loadProperties(inputStream), loggerRepository, classLoader);
     }
 
     /**
-     * Read configuration options from <code>properties</code>.
+     * Reads configuration options from <code>properties</code>.
      *
      * See {@link #doConfigure(String, LoggerRepository)} for the expected format.
      *
      * @param properties The properties
-     * @param hierarchy The hierarchy
+     * @param loggerRepository The hierarchy
      */
-    public void doConfigure(final Properties properties, final LoggerRepository hierarchy) {
+    public void doConfigure(final Properties properties, final LoggerRepository loggerRepository) {
+        doConfigure(properties, loggerRepository, StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
-     * Read configuration options from configuration file.
+     * Reads configuration options from <code>properties</code>.
+     *
+     * See {@link #doConfigure(String, LoggerRepository)} for the expected format.
      *
-     * @param configFileName The configuration file
-     * @param hierarchy The hierarchy
+     * @param properties The properties
+     * @param loggerRepository The hierarchy
      */
-    public void doConfigure(final String configFileName, final LoggerRepository hierarchy) {
+    Configuration doConfigure(final Properties properties, final LoggerRepository loggerRepository, final ClassLoader classLoader) {
+        final PropertiesConfiguration configuration = new PropertiesConfiguration(LogManager.getContext(classLoader), properties);
+        configuration.doConfigure();
+
+        repository = loggerRepository;
+//      String value = properties.getProperty(LogLog.DEBUG_KEY);
+//      if (value == null) {
+//          value = properties.getProperty("log4j.configDebug");
+//          if (value != null) {
+//              LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
+//          }
+//      }
+//
+//      if (value != null) {
+//          LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
+//      }
+//
+//      //
+//      // if log4j.reset=true then
+//      // reset hierarchy
+//      final String reset = properties.getProperty(RESET_KEY);
+//      if (reset != null && OptionConverter.toBoolean(reset, false)) {
+//          hierarchy.resetConfiguration();
+//      }
+//
+//      final String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX, properties);
+//      if (thresholdStr != null) {
+//          hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr, (Level) Level.ALL));
+//          LogLog.debug("Hierarchy threshold set to [" + hierarchy.getThreshold() + "].");
+//      }
+//
+//      configureRootCategory(properties, hierarchy);
+//      configureLoggerFactory(properties);
+//      parseCatsAndRenderers(properties, hierarchy);
+//
+        // We don't want to hold references to appenders preventing their
+        // garbage collection.
+        registry.clear();
 
+        return reconfigure(configuration);
+    }
+
+    /**
+     * Reads configuration options from configuration file.
+     *
+     * @param fileName The configuration file
+     * @param loggerRepository The hierarchy
+     */
+    public void doConfigure(final String fileName, final LoggerRepository loggerRepository) {
+        doConfigure(fileName, loggerRepository, StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    /**
+     * Reads configuration options from configuration file.
+     *
+     * @param fileName The configuration file
+     * @param loggerRepository The hierarchy
+     */
+    Configuration doConfigure(final String fileName, final LoggerRepository loggerRepository, final ClassLoader classLoader) {
+        try (InputStream inputStream = Files.newInputStream(Paths.get(fileName))) {
+            return doConfigure(inputStream, loggerRepository, classLoader);
+        } catch (final Exception e) {
+            if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+            LogLog.error("Could not read configuration file [" + fileName + "].", e);
+            LogLog.error("Ignoring configuration file [" + fileName + "].");
+            return null;
+        }
     }
 
     /**
      * Read configuration options from url <code>configURL</code>.
      *
-     * @param configURL The configuration URL
-     * @param hierarchy The hierarchy
+     * @param url The configuration URL
+     * @param loggerRepository The hierarchy
      */
     @Override
-    public void doConfigure(final URL configURL, final LoggerRepository hierarchy) {
+    public void doConfigure(final URL url, final LoggerRepository loggerRepository) {
+        doConfigure(url, loggerRepository, StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    Configuration doConfigure(final URL url, final LoggerRepository loggerRepository, final ClassLoader classLoader) {
+        LogLog.debug("Reading configuration from URL " + url);
+        try {
+            final URLConnection urlConnection = url.openConnection();
+            urlConnection.setUseCaches(false);
+            try (InputStream inputStream = urlConnection.getInputStream()) {
+                return doConfigure(inputStream, loggerRepository, classLoader);
+            }
+        } catch (final IOException e) {
+            LogLog.error("Could not read configuration file from URL [" + url + "].", e);
+            LogLog.error("Ignoring configuration file [" + url + "].");
+            return null;
+        }
+
+    }
+
+    private Properties loadProperties(final InputStream inputStream) {
+        final Properties loaded = new Properties();
+        try {
+            loaded.load(inputStream);
+        } catch (final IOException | IllegalArgumentException e) {
+            if (e instanceof InterruptedIOException) {
+                Thread.currentThread().interrupt();
+            }
+            LogLog.error("Could not read configuration file from InputStream [" + inputStream + "].", e);
+            LogLog.error("Ignoring configuration InputStream [" + inputStream + "].");
+            return null;
+        }
+        return loaded;
+    }
+
+    /**
+     * Parse the additivity option for a non-root category.
+     */
+    void parseAdditivityForLogger(final Properties properties, final Logger logger, final String loggerName) {
+        final String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName, properties);
+        LogLog.debug("Handling " + ADDITIVITY_PREFIX + loggerName + "=[" + value + "]");
+        // touch additivity only if necessary
+        if ((value != null) && (!value.equals(""))) {
+            final boolean additivity = OptionConverter.toBoolean(value, true);
+            LogLog.debug("Setting additivity for \"" + loggerName + "\" to " + additivity);
+            logger.setAdditivity(additivity);
+        }
+    }
+
+    Appender parseAppender(final Properties properties, final String appenderName) {
+        Appender appender = registryGet(appenderName);
+        if ((appender != null)) {
+            LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
+            return appender;
+        }
+        // Appender was not previously initialized.
+        final String prefix = APPENDER_PREFIX + appenderName;
+        final String layoutPrefix = prefix + ".layout";
+
+        appender = (Appender) OptionConverter.instantiateByKey(properties, prefix, org.apache.log4j.Appender.class, null);
+        if (appender == null) {
+            LogLog.error("Could not instantiate appender named \"" + appenderName + "\".");
+            return null;
+        }
+        appender.setName(appenderName);
+
+        if (appender instanceof OptionHandler) {
+            if (appender.requiresLayout()) {
+                final Layout layout = (Layout) OptionConverter.instantiateByKey(properties, layoutPrefix, Layout.class, null);
+                if (layout != null) {
+                    appender.setLayout(layout);
+                    LogLog.debug("Parsing layout options for \"" + appenderName + "\".");
+                    // configureOptionHandler(layout, layoutPrefix + ".", props);
+                    PropertySetter.setProperties(layout, properties, layoutPrefix + ".");
+                    LogLog.debug("End of parsing for \"" + appenderName + "\".");
+                }
+            }
+            final String errorHandlerPrefix = prefix + ".errorhandler";
+            final String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, properties);
+            if (errorHandlerClass != null) {
+                final ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(properties, errorHandlerPrefix, ErrorHandler.class, null);
+                if (eh != null) {
+                    appender.setErrorHandler(eh);
+                    LogLog.debug("Parsing errorhandler options for \"" + appenderName + "\".");
+                    parseErrorHandler(eh, errorHandlerPrefix, properties, repository);
+                    final Properties edited = new Properties();
+                    final String[] keys = new String[] {errorHandlerPrefix + "." + ROOT_REF, errorHandlerPrefix + "." + LOGGER_REF,
+                        errorHandlerPrefix + "." + APPENDER_REF_TAG};
+                    for (final Object element : properties.entrySet()) {
+                        final Map.Entry entry = (Map.Entry) element;
+                        int i = 0;
+                        for (; i < keys.length; i++) {
+                            if (keys[i].equals(entry.getKey())) {
+                                break;
+                            }
+                        }
+                        if (i == keys.length) {
+                            edited.put(entry.getKey(), entry.getValue());
+                        }
+                    }
+                    PropertySetter.setProperties(eh, edited, errorHandlerPrefix + ".");
+                    LogLog.debug("End of errorhandler parsing for \"" + appenderName + "\".");
+                }
+
+            }
+            // configureOptionHandler((OptionHandler) appender, prefix + ".", props);
+            PropertySetter.setProperties(appender, properties, prefix + ".");
+            LogLog.debug("Parsed \"" + appenderName + "\" options.");
+        }
+        parseAppenderFilters(properties, appenderName, appender);
+        registryPut(appender);
+        return appender;
+    }
+
+    void parseAppenderFilters(final Properties properties, final String appenderName, final Appender appender) {
+        // extract filters and filter options from props into a hashtable mapping
+        // the property name defining the filter class to a list of pre-parsed
+        // name-value pairs associated to that filter
+        final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter.";
+        final int fIdx = filterPrefix.length();
+        final Hashtable filters = new Hashtable();
+        final Enumeration e = properties.keys();
+        String name = "";
+        while (e.hasMoreElements()) {
+            final String key = (String) e.nextElement();
+            if (key.startsWith(filterPrefix)) {
+                final int dotIdx = key.indexOf('.', fIdx);
+                String filterKey = key;
+                if (dotIdx != -1) {
+                    filterKey = key.substring(0, dotIdx);
+                    name = key.substring(dotIdx + 1);
+                }
+                Vector filterOpts = (Vector) filters.get(filterKey);
+                if (filterOpts == null) {
+                    filterOpts = new Vector();
+                    filters.put(filterKey, filterOpts);
+                }
+                if (dotIdx != -1) {
+                    final String value = OptionConverter.findAndSubst(key, properties);
+                    filterOpts.add(new NameValue(name, value));
+                }
+            }
+        }
+
+        // sort filters by IDs, insantiate filters, set filter options,
+        // add filters to the appender
+        final Enumeration g = new SortedKeyEnumeration(filters);
+        while (g.hasMoreElements()) {
+            final String key = (String) g.nextElement();
+            final String clazz = properties.getProperty(key);
+            if (clazz != null) {
+                LogLog.debug("Filter key: [" + key + "] class: [" + properties.getProperty(key) + "] props: " + filters.get(key));
+                final Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz, Filter.class, null);
+                if (filter != null) {
+                    final PropertySetter propSetter = new PropertySetter(filter);
+                    final Vector v = (Vector) filters.get(key);
+                    final Enumeration filterProps = v.elements();
+                    while (filterProps.hasMoreElements()) {
+                        final NameValue kv = (NameValue) filterProps.nextElement();
+                        propSetter.setProperty(kv.key, kv.value);
+                    }
+                    propSetter.activate();
+                    LogLog.debug("Adding filter of type [" + filter.getClass() + "] to appender named [" + appender.getName() + "].");
+                    appender.addFilter(filter);
+                }
+            } else {
+                LogLog.warn("Missing class definition for filter: [" + key + "]");
+            }
+        }
+    }
+
+    /**
+     * This method must work for the root category as well.
+     */
+    void parseCategory(final Properties properties, final Logger logger, final String optionKey, final String loggerName, final String value) {
+
+        LogLog.debug("Parsing for [" + loggerName + "] with value=[" + value + "].");
+        // We must skip over ',' but not white space
+        final StringTokenizer st = new StringTokenizer(value, ",");
+
+        // If value is not in the form ", appender.." or "", then we should set
+        // the level of the loggeregory.
+
+        if (!(value.startsWith(",") || value.equals(""))) {
+
+            // just to be on the safe side...
+            if (!st.hasMoreTokens()) {
+                return;
+            }
+
+            final String levelStr = st.nextToken();
+            LogLog.debug("Level token is [" + levelStr + "].");
+
+            // If the level value is inherited, set category level value to
+            // null. We also check that the user has not specified inherited for the
+            // root category.
+            if (INHERITED.equalsIgnoreCase(levelStr) || NULL.equalsIgnoreCase(levelStr)) {
+                if (loggerName.equals(INTERNAL_ROOT_NAME)) {
+                    LogLog.warn("The root logger cannot be set to null.");
+                } else {
+                    logger.setLevel(null);
+                }
+            } else {
+                logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG));
+            }
+            LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
+        }
+
+        // Begin by removing all existing appenders.
+        logger.removeAllAppenders();
+
+        Appender appender;
+        String appenderName;
+        while (st.hasMoreTokens()) {
+            appenderName = st.nextToken().trim();
+            if (appenderName == null || appenderName.equals(",")) {
+                continue;
+            }
+            LogLog.debug("Parsing appender named \"" + appenderName + "\".");
+            appender = parseAppender(properties, appenderName);
+            if (appender != null) {
+                logger.addAppender(appender);
+            }
+        }
+    }
+
+    /**
+     * Parse non-root elements, such non-root categories and renderers.
+     */
+    protected void parseCatsAndRenderers(final Properties properties, final LoggerRepository loggerRepository) {
+        final Enumeration enumeration = properties.propertyNames();
+        while (enumeration.hasMoreElements()) {
+            final String key = (String) enumeration.nextElement();
+            if (key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
+                String loggerName = null;
+                if (key.startsWith(CATEGORY_PREFIX)) {
+                    loggerName = key.substring(CATEGORY_PREFIX.length());
+                } else if (key.startsWith(LOGGER_PREFIX)) {
+                    loggerName = key.substring(LOGGER_PREFIX.length());
+                }
+                final String value = OptionConverter.findAndSubst(key, properties);
+                final Logger logger = loggerRepository.getLogger(loggerName, loggerFactory);
+                synchronized (logger) {
+                    parseCategory(properties, logger, key, loggerName, value);
+                    parseAdditivityForLogger(properties, logger, loggerName);
+                }
+            } else if (key.startsWith(RENDERER_PREFIX)) {
+                final String renderedClass = key.substring(RENDERER_PREFIX.length());
+                final String renderingClass = OptionConverter.findAndSubst(key, properties);
+                if (loggerRepository instanceof RendererSupport) {
+                    RendererMap.addRenderer((RendererSupport) loggerRepository, renderedClass, renderingClass);
+                }
+            } else if (key.equals(THROWABLE_RENDERER_PREFIX)) {
+                if (loggerRepository instanceof ThrowableRendererSupport) {
+                    final ThrowableRenderer tr = (ThrowableRenderer) OptionConverter.instantiateByKey(properties, THROWABLE_RENDERER_PREFIX,
+                        org.apache.log4j.spi.ThrowableRenderer.class, null);
+                    if (tr == null) {
+                        LogLog.error("Could not instantiate throwableRenderer.");
+                    } else {
+                        final PropertySetter setter = new PropertySetter(tr);
+                        setter.setProperties(properties, THROWABLE_RENDERER_PREFIX + ".");
+                        ((ThrowableRendererSupport) loggerRepository).setThrowableRenderer(tr);
+
+                    }
+                }
+            }
+        }
+    }
+
+    private void parseErrorHandler(final ErrorHandler errorHandler, final String errorHandlerPrefix, final Properties props,
+        final LoggerRepository loggerRepository) {
+        final boolean rootRef = OptionConverter.toBoolean(OptionConverter.findAndSubst(errorHandlerPrefix + ROOT_REF, props), false);
+        if (rootRef) {
+            errorHandler.setLogger(loggerRepository.getRootLogger());
+        }
+        final String loggerName = OptionConverter.findAndSubst(errorHandlerPrefix + LOGGER_REF, props);
+        if (loggerName != null) {
+            final Logger logger = (loggerFactory == null) ? loggerRepository.getLogger(loggerName) : loggerRepository.getLogger(loggerName, loggerFactory);
+            errorHandler.setLogger(logger);
+        }
+        final String appenderName = OptionConverter.findAndSubst(errorHandlerPrefix + APPENDER_REF_TAG, props);
+        if (appenderName != null) {
+            final Appender backup = parseAppender(props, appenderName);
+            if (backup != null) {
+                errorHandler.setBackupAppender(backup);
+            }
+        }
+    }
+
+    Appender registryGet(final String name) {
+        return (Appender) registry.get(name);
+    }
+
+    void registryPut(final Appender appender) {
+        registry.put(appender.getName(), appender);
     }
 }
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java
index 8be58d4..a8d8614 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java
@@ -56,15 +56,21 @@ public class PropertiesConfiguration extends Log4j1Configuration {
     private static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
     private static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
     private static final String APPENDER_PREFIX = "log4j.appender.";
-    private static final String LOGGER_REF	= "logger-ref";
-    private static final String ROOT_REF		= "root-ref";
+    private static final String LOGGER_REF = "logger-ref";
+    private static final String ROOT_REF = "root-ref";
     private static final String APPENDER_REF_TAG = "appender-ref";
-    public static final long DEFAULT_DELAY = 60000;
-    public static final String DEBUG_KEY="log4j.debug";
+
+    /**
+     * If property set to true, then hierarchy will be reset before configuration.
+     */
+    private static final String RESET_KEY = "log4j.reset";
+
+    public static final String DEBUG_KEY = "log4j.debug";
 
     private static final String INTERNAL_ROOT_NAME = "root";
 
     private final Map<String, Appender> registry = new HashMap<>();
+    private Properties properties;
 
     /**
      * Constructs a new instance.
@@ -73,20 +79,44 @@ public class PropertiesConfiguration extends Log4j1Configuration {
      * @param source The ConfigurationSource.
      * @param monitorIntervalSeconds The monitoring interval in seconds.
      */
-    public PropertiesConfiguration(final LoggerContext loggerContext, final ConfigurationSource source,
-            final int monitorIntervalSeconds) {
+    public PropertiesConfiguration(final LoggerContext loggerContext, final ConfigurationSource source, final int monitorIntervalSeconds) {
         super(loggerContext, source, monitorIntervalSeconds);
     }
 
+    /**
+     * Constructs a new instance.
+     *
+     * @param loggerContext The LoggerContext.
+     * @param properties The ConfigurationSource, may be null.
+     */
+    public PropertiesConfiguration(final LoggerContext loggerContext, final Properties properties) {
+        super(loggerContext, ConfigurationSource.NULL_SOURCE, 0);
+        this.properties = properties;
+    }
+
+    /**
+     * Constructs a new instance.
+     *
+     * @param loggerContext The LoggerContext.
+     * @param properties The ConfigurationSource.
+     */
+    public PropertiesConfiguration(org.apache.logging.log4j.spi.LoggerContext loggerContext, Properties properties) {
+        this((LoggerContext) loggerContext, properties);
+    }
+
     @Override
     public void doConfigure() {
-        final InputStream inputStream = getConfigurationSource().getInputStream();
-        final Properties properties = new Properties();
-        try {
-            properties.load(inputStream);
-        } catch (final Exception e) {
-            LOGGER.error("Could not read configuration file [{}].", getConfigurationSource().toString(), e);
-            return;
+        if (properties == null) {
+            properties = new Properties();
+            final InputStream inputStream = getConfigurationSource().getInputStream();
+            if (inputStream != null) {
+                try {
+                    properties.load(inputStream);
+                } catch (final Exception e) {
+                    LOGGER.error("Could not read configuration file [{}].", getConfigurationSource().toString(), e);
+                    return;
+                }
+            }
         }
         // If we reach here, then the config file is alright.
         doConfigure(properties);
@@ -108,27 +138,26 @@ public class PropertiesConfiguration extends Log4j1Configuration {
     }
 
     /**
-     * Reads a configuration from a file. <b>The existing configuration is
-     * not cleared nor reset.</b> If you require a different behavior,
-     * then call {@link  LogManager#resetConfiguration
-     * resetConfiguration} method before calling
+     * Reads a configuration from a file. <b>The existing configuration is not cleared nor reset.</b> If you require a
+     * different behavior, then call {@link LogManager#resetConfiguration resetConfiguration} method before calling
      * <code>doConfigure</code>.
      *
-     * <p>The configuration file consists of statements in the format
-     * <code>key=value</code>. The syntax of different configuration
-     * elements are discussed below.
+     * <p>
+     * The configuration file consists of statements in the format <code>key=value</code>. The syntax of different
+     * configuration elements are discussed below.
      *
-     * <p>The level value can consist of the string values OFF, FATAL,
-     * ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
-     * custom level value can be specified in the form
-     * level#classname. By default the repository-wide threshold is set
-     * to the lowest possible value, namely the level <code>ALL</code>.
+     * <p>
+     * The level value can consist of the string values OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em>
+     * value. A custom level value can be specified in the form level#classname. By default the repository-wide threshold is
+     * set to the lowest possible value, namely the level <code>ALL</code>.
      * </p>
      *
      *
      * <h3>Appender configuration</h3>
      *
-     * <p>Appender configuration syntax is:
+     * <p>
+     * Appender configuration syntax is:
+     * 
      * <pre>
      * # For appender named <i>appenderName</i>, set its class.
      * # Note: The appender name can contain dots.
@@ -140,8 +169,8 @@ public class PropertiesConfiguration extends Log4j1Configuration {
      * log4j.appender.appenderName.optionN=valueN
      * </pre>
      * <p>
-     * For each named appender you can configure its {@link Layout}. The
-     * syntax for configuring an appender's layout is:
+     * For each named appender you can configure its {@link Layout}. The syntax for configuring an appender's layout is:
+     * 
      * <pre>
      * log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
      * log4j.appender.appenderName.layout.option1=value1
@@ -150,18 +179,19 @@ public class PropertiesConfiguration extends Log4j1Configuration {
      * </pre>
      * <p>
      * The syntax for adding {@link Filter}s to an appender is:
+     * 
      * <pre>
      * log4j.appender.appenderName.filter.ID=fully.qualified.name.of.filter.class
      * log4j.appender.appenderName.filter.ID.option1=value1
      * ...
      * log4j.appender.appenderName.filter.ID.optionN=valueN
      * </pre>
-     * The first line defines the class name of the filter identified by ID;
-     * subsequent lines with the same ID specify filter option - value
-     * pairs. Multiple filters are added to the appender in the lexicographic
-     * order of IDs.
+     * 
+     * The first line defines the class name of the filter identified by ID; subsequent lines with the same ID specify
+     * filter option - value pairs. Multiple filters are added to the appender in the lexicographic order of IDs.
      * <p>
      * The syntax for adding an {@link ErrorHandler} to an appender is:
+     * 
      * <pre>
      * log4j.appender.appenderName.errorhandler=fully.qualified.name.of.errorhandler.class
      * log4j.appender.appenderName.errorhandler.appender-ref=appenderName
@@ -172,111 +202,97 @@ public class PropertiesConfiguration extends Log4j1Configuration {
      *
      * <h3>Configuring loggers</h3>
      *
-     * <p>The syntax for configuring the root logger is:
+     * <p>
+     * The syntax for configuring the root logger is:
+     * 
      * <pre>
      * log4j.rootLogger=[level], appenderName, appenderName, ...
      * </pre>
      *
-     * <p>This syntax means that an optional <em>level</em> can be
-     * supplied followed by appender names separated by commas.
+     * <p>
+     * This syntax means that an optional <em>level</em> can be supplied followed by appender names separated by commas.
      *
-     * <p>The level value can consist of the string values OFF, FATAL,
-     * ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
-     * custom level value can be specified in the form
-     * <code>level#classname</code>.
+     * <p>
+     * The level value can consist of the string values OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em>
+     * value. A custom level value can be specified in the form <code>level#classname</code>.
      *
-     * <p>If a level value is specified, then the root level is set
-     * to the corresponding level.  If no level value is specified,
+     * <p>
+     * If a level value is specified, then the root level is set to the corresponding level. If no level value is specified,
      * then the root level remains untouched.
      *
-     * <p>The root logger can be assigned multiple appenders.
+     * <p>
+     * The root logger can be assigned multiple appenders.
      *
-     * <p>Each <i>appenderName</i> (separated by commas) will be added to
-     * the root logger. The named appender is defined using the
-     * appender syntax defined above.
+     * <p>
+     * Each <i>appenderName</i> (separated by commas) will be added to the root logger. The named appender is defined using
+     * the appender syntax defined above.
      *
-     * <p>For non-root categories the syntax is almost the same:
+     * <p>
+     * For non-root categories the syntax is almost the same:
+     * 
      * <pre>
      * log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...
      * </pre>
      *
-     * <p>The meaning of the optional level value is discussed above
-     * in relation to the root logger. In addition however, the value
-     * INHERITED can be specified meaning that the named logger should
-     * inherit its level from the logger hierarchy.
+     * <p>
+     * The meaning of the optional level value is discussed above in relation to the root logger. In addition however, the
+     * value INHERITED can be specified meaning that the named logger should inherit its level from the logger hierarchy.
      *
-     * <p>If no level value is supplied, then the level of the
-     * named logger remains untouched.
+     * <p>
+     * If no level value is supplied, then the level of the named logger remains untouched.
      *
-     * <p>By default categories inherit their level from the
-     * hierarchy. However, if you set the level of a logger and later
-     * decide that that logger should inherit its level, then you should
-     * specify INHERITED as the value for the level value. NULL is a
-     * synonym for INHERITED.
+     * <p>
+     * By default categories inherit their level from the hierarchy. However, if you set the level of a logger and later
+     * decide that that logger should inherit its level, then you should specify INHERITED as the value for the level value.
+     * NULL is a synonym for INHERITED.
      *
-     * <p>Similar to the root logger syntax, each <i>appenderName</i>
-     * (separated by commas) will be attached to the named logger.
+     * <p>
+     * Similar to the root logger syntax, each <i>appenderName</i> (separated by commas) will be attached to the named
+     * logger.
      *
-     * <p>See the <a href="../../../../manual.html#additivity">appender
-     * additivity rule</a> in the user manual for the meaning of the
-     * <code>additivity</code> flag.
+     * <p>
+     * See the <a href="../../../../manual.html#additivity">appender additivity rule</a> in the user manual for the meaning
+     * of the <code>additivity</code> flag.
      *
      *
-     * # Set options for appender named "A1".
-     * # Appender "A1" will be a SyslogAppender
+     * # Set options for appender named "A1". # Appender "A1" will be a SyslogAppender
      * log4j.appender.A1=org.apache.log4j.net.SyslogAppender
      *
-     * # The syslog daemon resides on www.abc.net
-     * log4j.appender.A1.SyslogHost=www.abc.net
-     *
-     * # A1's layout is a PatternLayout, using the conversion pattern
-     * # <b>%r %-5p %c{2} %M.%L %x - %m\n</b>. Thus, the log output will
-     * # include # the relative time since the start of the application in
-     * # milliseconds, followed by the level of the log request,
-     * # followed by the two rightmost components of the logger name,
-     * # followed by the callers method name, followed by the line number,
-     * # the nested diagnostic context and finally the message itself.
-     * # Refer to the documentation of {@link PatternLayout} for further information
-     * # on the syntax of the ConversionPattern key.
-     * log4j.appender.A1.layout=org.apache.log4j.PatternLayout
-     * log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2} %M.%L %x - %m\n
-     *
-     * # Set options for appender named "A2"
-     * # A2 should be a RollingFileAppender, with maximum file size of 10 MB
-     * # using at most one backup file. A2's layout is TTCC, using the
-     * # ISO8061 date format with context printing enabled.
-     * log4j.appender.A2=org.apache.log4j.RollingFileAppender
-     * log4j.appender.A2.MaxFileSize=10MB
-     * log4j.appender.A2.MaxBackupIndex=1
-     * log4j.appender.A2.layout=org.apache.log4j.TTCCLayout
-     * log4j.appender.A2.layout.ContextPrinting=enabled
-     * log4j.appender.A2.layout.DateFormat=ISO8601
-     *
-     * # Root logger set to DEBUG using the A2 appender defined above.
-     * log4j.rootLogger=DEBUG, A2
-     *
-     * # Logger definitions:
-     * # The SECURITY logger inherits is level from root. However, it's output
-     * # will go to A1 appender defined above. It's additivity is non-cumulative.
-     * log4j.logger.SECURITY=INHERIT, A1
+     * # The syslog daemon resides on www.abc.net log4j.appender.A1.SyslogHost=www.abc.net
+     *
+     * # A1's layout is a PatternLayout, using the conversion pattern # <b>%r %-5p %c{2} %M.%L %x - %m\n</b>. Thus, the log
+     * output will # include # the relative time since the start of the application in # milliseconds, followed by the level
+     * of the log request, # followed by the two rightmost components of the logger name, # followed by the callers method
+     * name, followed by the line number, # the nested diagnostic context and finally the message itself. # Refer to the
+     * documentation of {@link PatternLayout} for further information # on the syntax of the ConversionPattern key.
+     * log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2}
+     * %M.%L %x - %m\n
+     *
+     * # Set options for appender named "A2" # A2 should be a RollingFileAppender, with maximum file size of 10 MB # using
+     * at most one backup file. A2's layout is TTCC, using the # ISO8061 date format with context printing enabled.
+     * log4j.appender.A2=org.apache.log4j.RollingFileAppender log4j.appender.A2.MaxFileSize=10MB
+     * log4j.appender.A2.MaxBackupIndex=1 log4j.appender.A2.layout=org.apache.log4j.TTCCLayout
+     * log4j.appender.A2.layout.ContextPrinting=enabled log4j.appender.A2.layout.DateFormat=ISO8601
+     *
+     * # Root logger set to DEBUG using the A2 appender defined above. log4j.rootLogger=DEBUG, A2
+     *
+     * # Logger definitions: # The SECURITY logger inherits is level from root. However, it's output # will go to A1
+     * appender defined above. It's additivity is non-cumulative. log4j.logger.SECURITY=INHERIT, A1
      * log4j.additivity.SECURITY=false
      *
-     * # Only warnings or above will be logged for the logger "SECURITY.access".
-     * # Output will go to A1.
+     * # Only warnings or above will be logged for the logger "SECURITY.access". # Output will go to A1.
      * log4j.logger.SECURITY.access=WARN
      *
      *
-     * # The logger "class.of.the.day" inherits its level from the
-     * # logger hierarchy.  Output will go to the appender's of the root
-     * # logger, A2 in this case.
-     * log4j.logger.class.of.the.day=INHERIT
+     * # The logger "class.of.the.day" inherits its level from the # logger hierarchy. Output will go to the appender's of
+     * the root # logger, A2 in this case. log4j.logger.class.of.the.day=INHERIT
      * </pre>
      *
-     * <p>Refer to the <b>setOption</b> method in each Appender and
-     * Layout for class specific options.
+     * <p>
+     * Refer to the <b>setOption</b> method in each Appender and Layout for class specific options.
      *
-     * <p>Use the <code>#</code> or <code>!</code> characters at the
-     * beginning of a line for comments.
+     * <p>
+     * Use the <code>#</code> or <code>!</code> characters at the beginning of a line for comments.
      *
      * <p>
      */
@@ -297,6 +313,12 @@ public class PropertiesConfiguration extends Log4j1Configuration {
         final StatusConfiguration statusConfig = new StatusConfiguration().withStatus(status);
         statusConfig.initialize();
 
+        // if log4j.reset=true then reset hierarchy
+        final String reset = properties.getProperty(RESET_KEY);
+        if (reset != null && OptionConverter.toBoolean(reset, false)) {
+            LogManager.resetConfiguration();
+        }
+
         configureRoot(properties);
         parseLoggers(properties);
 
@@ -368,7 +390,7 @@ public class PropertiesConfiguration extends Log4j1Configuration {
     /**
      * This method must work for the root category as well.
      */
-    private void parseLogger(final Properties props, final LoggerConfig logger, final String optionKey, final String loggerName, final String value) {
+    private void parseLogger(final Properties props, final LoggerConfig loggerConfig, final String optionKey, final String loggerName, final String value) {
 
         LOGGER.debug("Parsing for [{}] with value=[{}].", loggerName, value);
         // We must skip over ',' but not white space
@@ -386,9 +408,9 @@ public class PropertiesConfiguration extends Log4j1Configuration {
             final String levelStr = st.nextToken();
             LOGGER.debug("Level token is [{}].", levelStr);
 
-            final org.apache.logging.log4j.Level level = levelStr == null ? org.apache.logging.log4j.Level.ERROR :
-                    OptionConverter.convertLevel(levelStr, org.apache.logging.log4j.Level.DEBUG);
-            logger.setLevel(level);
+            final org.apache.logging.log4j.Level level = levelStr == null ? org.apache.logging.log4j.Level.ERROR
+                : OptionConverter.convertLevel(levelStr, org.apache.logging.log4j.Level.DEBUG);
+            loggerConfig.setLevel(level);
             LOGGER.debug("Logger {} level set to {}", loggerName, level);
         }
 
@@ -402,9 +424,8 @@ public class PropertiesConfiguration extends Log4j1Configuration {
             LOGGER.debug("Parsing appender named \"{}\".", appenderName);
             appender = parseAppender(props, appenderName);
             if (appender != null) {
-                LOGGER.debug("Adding appender named [{}] to loggerConfig [{}].", appenderName,
-                        logger.getName());
-                logger.addAppender(getAppender(appenderName), null, null);
+                LOGGER.debug("Adding appender named [{}] to loggerConfig [{}].", appenderName, loggerConfig.getName());
+                loggerConfig.addAppender(getAppender(appenderName), null, null);
             } else {
                 LOGGER.debug("Appender named [{}] not found.", appenderName);
             }
@@ -436,8 +457,8 @@ public class PropertiesConfiguration extends Log4j1Configuration {
         return appender;
     }
 
-    private Appender buildAppender(final String appenderName, final String className, final String prefix,
-            final String layoutPrefix, final String filterPrefix, final Properties props) {
+    private Appender buildAppender(final String appenderName, final String className, final String prefix, final String layoutPrefix, final String filterPrefix,
+        final Properties props) {
         final Appender appender = newInstanceOf(className, "Appender");
         if (appender == null) {
             return null;
@@ -453,7 +474,7 @@ public class PropertiesConfiguration extends Log4j1Configuration {
             }
         }
         appender.addFilter(parseAppenderFilters(props, filterPrefix, appenderName));
-        final String[] keys = new String[] { layoutPrefix };
+        final String[] keys = new String[] {layoutPrefix};
         addProperties(appender, keys, props, prefix);
         if (appender instanceof AppenderWrapper) {
             addAppender(((AppenderWrapper) appender).getAppender());
@@ -487,14 +508,14 @@ public class PropertiesConfiguration extends Log4j1Configuration {
         return layout;
     }
 
-    public ErrorHandler parseErrorHandler(final Properties props, final String errorHandlerPrefix,
-            final String errorHandlerClass, final Appender appender) {
+    public ErrorHandler parseErrorHandler(final Properties props, final String errorHandlerPrefix, final String errorHandlerClass, final Appender appender) {
         final ErrorHandler eh = newInstanceOf(errorHandlerClass, "ErrorHandler");
         final String[] keys = new String[] {
-                errorHandlerPrefix + "." + ROOT_REF,
-                errorHandlerPrefix + "." + LOGGER_REF,
-                errorHandlerPrefix + "." + APPENDER_REF_TAG
-        };
+            // @formatter:off
+            errorHandlerPrefix + "." + ROOT_REF, 
+            errorHandlerPrefix + "." + LOGGER_REF,
+            errorHandlerPrefix + "." + APPENDER_REF_TAG};
+            // @formatter:on
         addProperties(eh, keys, props, errorHandlerPrefix);
         return eh;
     }
@@ -515,7 +536,6 @@ public class PropertiesConfiguration extends Log4j1Configuration {
         PropertySetter.setProperties(obj, edited, prefix + ".");
     }
 
-
     public Filter parseAppenderFilters(final Properties props, final String filterPrefix, final String appenderName) {
         // extract filters and filter options from props into a hashtable mapping
         // the property name defining the filter class to a list of pre-parsed
@@ -577,13 +597,11 @@ public class PropertiesConfiguration extends Log4j1Configuration {
         return filter;
     }
 
-
     private static <T> T newInstanceOf(final String className, final String type) {
         try {
             return LoaderUtil.newInstanceOf(className);
         } catch (ReflectiveOperationException ex) {
-            LOGGER.error("Unable to create {} {} due to {}:{}", type,  className,
-                    ex.getClass().getSimpleName(), ex.getMessage());
+            LOGGER.error("Unable to create {} {} due to {}:{}", type, className, ex.getClass().getSimpleName(), ex.getMessage());
             return null;
         }
     }
diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfiguratorTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfiguratorTest.java
new file mode 100644
index 0000000..d14dcf9
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfiguratorTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+import org.apache.log4j.varia.NullAppender;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test {@link BasicConfigurator}.
+ */
+public class BasicConfiguratorTest {
+
+    @Test
+    public void testConfigure() {
+        // TODO More...
+        BasicConfigurator.configure();
+    }
+
+    @Test
+    public void testResetConfiguration() {
+        // TODO More...
+        BasicConfigurator.resetConfiguration();
+    }
+
+    @Test
+    public void testConfigureAppender() {
+        BasicConfigurator.configure(null);
+        // TODO More...
+    }
+
+    @Test
+    public void testConfigureConsoleAppender() {
+        // TODO What to do? Map to Log4j 2 Appender deeper in the code?
+        BasicConfigurator.configure(new ConsoleAppender());
+    }
+
+    @Test
+    public void testConfigureNullAppender() {
+        // The NullAppender name is null and we do not want an NPE when the name is used as a key in a ConcurrentHashMap.
+        BasicConfigurator.configure(NullAppender.getNullAppender());
+    }
+}
diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/PropertyConfiguratorTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/PropertyConfiguratorTest.java
new file mode 100644
index 0000000..26812ec
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/PropertyConfiguratorTest.java
@@ -0,0 +1,427 @@
+/*
+ * 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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Properties;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.OptionHandler;
+import org.apache.log4j.spi.RootLogger;
+import org.apache.log4j.spi.ThrowableRenderer;
+import org.apache.log4j.spi.ThrowableRendererSupport;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link PropertyConfigurator}.
+ */
+public class PropertyConfiguratorTest {
+
+    /**
+     * Mock definition of FilterBasedTriggeringPolicy from extras companion.
+     */
+    public static final class FilterBasedTriggeringPolicy extends TriggeringPolicy {
+        private Filter filter;
+
+        public FilterBasedTriggeringPolicy() {
+        }
+
+        public Filter getFilter() {
+            return filter;
+
+        }
+
+        public void setFilter(final Filter val) {
+            filter = val;
+        }
+    }
+
+    /**
+     * Mock definition of FixedWindowRollingPolicy from extras companion.
+     */
+    public static final class FixedWindowRollingPolicy extends RollingPolicy {
+        private String activeFileName;
+        private String fileNamePattern;
+        private int minIndex;
+
+        public FixedWindowRollingPolicy() {
+            minIndex = -1;
+        }
+
+        public String getActiveFileName() {
+            return activeFileName;
+        }
+
+        public String getFileNamePattern() {
+            return fileNamePattern;
+        }
+
+        public int getMinIndex() {
+            return minIndex;
+        }
+
+        public void setActiveFileName(final String val) {
+            activeFileName = val;
+        }
+
+        public void setFileNamePattern(final String val) {
+            fileNamePattern = val;
+        }
+
+        public void setMinIndex(final int val) {
+            minIndex = val;
+        }
+    }
+
+    /**
+     * Mock ThrowableRenderer for testThrowableRenderer. See bug 45721.
+     */
+    public static class MockThrowableRenderer implements ThrowableRenderer, OptionHandler {
+        private boolean activated = false;
+        private boolean showVersion = true;
+
+        public MockThrowableRenderer() {
+        }
+
+        @Override
+        public void activateOptions() {
+            activated = true;
+        }
+
+        @Override
+        public String[] doRender(final Throwable t) {
+            return new String[0];
+        }
+
+        public boolean getShowVersion() {
+            return showVersion;
+        }
+
+        public boolean isActivated() {
+            return activated;
+        }
+
+        public void setShowVersion(final boolean v) {
+            showVersion = v;
+        }
+    }
+
+    /**
+     * Mock definition of org.apache.log4j.rolling.RollingFileAppender from extras companion.
+     */
+    public static final class RollingFileAppender extends AppenderSkeleton {
+        private RollingPolicy rollingPolicy;
+        private TriggeringPolicy triggeringPolicy;
+        private boolean append;
+
+        public RollingFileAppender() {
+
+        }
+
+        @Override
+        public void append(final LoggingEvent event) {
+        }
+
+        @Override
+        public void close() {
+        }
+
+        public boolean getAppend() {
+            return append;
+        }
+
+        public RollingPolicy getRollingPolicy() {
+            return rollingPolicy;
+        }
+
+        public TriggeringPolicy getTriggeringPolicy() {
+            return triggeringPolicy;
+        }
+
+        @Override
+        public boolean requiresLayout() {
+            return true;
+        }
+
+        public void setAppend(final boolean val) {
+            append = val;
+        }
+
+        public void setRollingPolicy(final RollingPolicy policy) {
+            rollingPolicy = policy;
+        }
+
+        public void setTriggeringPolicy(final TriggeringPolicy policy) {
+            triggeringPolicy = policy;
+        }
+    }
+
+    /**
+     * Mock definition of org.apache.log4j.rolling.RollingPolicy from extras companion.
+     */
+    public static class RollingPolicy implements OptionHandler {
+        private boolean activated = false;
+
+        public RollingPolicy() {
+
+        }
+
+        @Override
+        public void activateOptions() {
+            activated = true;
+        }
+
+        public final boolean isActivated() {
+            return activated;
+        }
+
+    }
+
+    /**
+     * Mock definition of TriggeringPolicy from extras companion.
+     */
+    public static class TriggeringPolicy implements OptionHandler {
+        private boolean activated = false;
+
+        public TriggeringPolicy() {
+
+        }
+
+        @Override
+        public void activateOptions() {
+            activated = true;
+        }
+
+        public final boolean isActivated() {
+            return activated;
+        }
+
+    }
+
+    private static final String FILTER1_PROPERTIES = "target/test-classes/log4j1-1.2.17/input/filter1.properties";
+
+    private static final String CAT_A_NAME = "categoryA";
+
+    private static final String CAT_B_NAME = "categoryB";
+
+    private static final String CAT_C_NAME = "categoryC";
+
+    /**
+     * Test for bug 40944. Did not catch IllegalArgumentException on Properties.load and close input stream.
+     *
+     * @throws IOException if IOException creating properties file.
+     */
+    @Test
+    public void testBadUnicodeEscape() throws IOException {
+        final String fileName = "target/badescape.properties";
+        try (FileWriter writer = new FileWriter(fileName)) {
+            writer.write("log4j.rootLogger=\\uXX41");
+        }
+        PropertyConfigurator.configure(fileName);
+        final File file = new File(fileName);
+        assertTrue(file.delete());
+        assertFalse(file.exists());
+    }
+
+    /**
+     * Tests configuring Log4J from an InputStream.
+     *
+     * @since 1.2.17
+     */
+    @Test
+    public void testInputStream() throws IOException {
+        final Path file = Paths.get(FILTER1_PROPERTIES);
+        assertTrue(Files.exists(file));
+        try (InputStream inputStream = Files.newInputStream(file)) {
+            PropertyConfigurator.configure(inputStream);
+        }
+        this.validateNested();
+        LogManager.resetConfiguration();
+    }
+
+    /**
+     * Test for bug 47465. configure(URL) did not close opened JarURLConnection.
+     *
+     * @throws IOException if IOException creating properties jar.
+     */
+    @Test
+    public void testJarURL() throws IOException {
+        final File dir = new File("output");
+        dir.mkdirs();
+        final File file = new File("output/properties.jar");
+        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(file))) {
+            zos.putNextEntry(new ZipEntry(LogManager.DEFAULT_CONFIGURATION_FILE));
+            zos.write("log4j.rootLogger=debug".getBytes());
+            zos.closeEntry();
+        }
+        final URL url = new URL("jar:" + file.toURI().toURL() + "!/" + LogManager.DEFAULT_CONFIGURATION_FILE);
+        PropertyConfigurator.configure(url);
+        assertTrue(file.delete());
+        assertFalse(file.exists());
+    }
+
+    @Test
+    public void testLocalVsGlobal() {
+        LoggerRepository repos1, repos2;
+        final Logger catA = Logger.getLogger(CAT_A_NAME);
+        final Logger catB = Logger.getLogger(CAT_B_NAME);
+        final Logger catC = Logger.getLogger(CAT_C_NAME);
+
+        final Properties globalSettings = new Properties();
+        globalSettings.put("log4j.logger." + CAT_A_NAME, Level.WARN.toString());
+        globalSettings.put("log4j.logger." + CAT_B_NAME, Level.WARN.toString());
+        globalSettings.put("log4j.logger." + CAT_C_NAME, Level.DEBUG.toString());
+        PropertyConfigurator.configure(globalSettings);
+        assertEquals(Level.WARN, catA.getLevel());
+        assertEquals(Level.WARN, catB.getLevel());
+        assertEquals(Level.DEBUG, catC.getLevel());
+
+        assertEquals(Level.WARN, catA.getLoggerRepository().getLogger(CAT_A_NAME).getLevel());
+        assertEquals(Level.WARN, catB.getLoggerRepository().getLogger(CAT_B_NAME).getLevel());
+        assertEquals(Level.DEBUG, catC.getLoggerRepository().getLogger(CAT_C_NAME).getLevel());
+
+        final Properties repos1Settings = new Properties();
+        repos1Settings.put("log4j.logger." + CAT_A_NAME, Level.DEBUG.toString());
+        repos1Settings.put("log4j.logger." + CAT_B_NAME, Level.INFO.toString());
+        repos1 = new Hierarchy(new RootLogger(Level.OFF));
+        new PropertyConfigurator().doConfigure(repos1Settings, repos1);
+        assertEquals(Level.DEBUG, repos1.getLogger(CAT_A_NAME).getLevel());
+        assertEquals(Level.INFO, repos1.getLogger(CAT_B_NAME).getLevel());
+
+        final Properties repos2Settings = new Properties();
+        repos2Settings.put("log4j.logger." + CAT_A_NAME, Level.INFO.toString());
+        repos2Settings.put("log4j.logger." + CAT_B_NAME, Level.DEBUG.toString());
+        repos2 = new Hierarchy(new RootLogger(Level.OFF));
+        new PropertyConfigurator().doConfigure(repos2Settings, repos2);
+        assertEquals(Level.INFO, repos2.getLogger(CAT_A_NAME).getLevel());
+        assertEquals(Level.DEBUG, repos2.getLogger(CAT_B_NAME).getLevel());
+    }
+
+    /**
+     * Tests processing of nested objects, see bug 36384.
+     */
+    public void testNested() {
+        try {
+            PropertyConfigurator.configure(FILTER1_PROPERTIES);
+            this.validateNested();
+        } finally {
+            LogManager.resetConfiguration();
+        }
+    }
+
+    /**
+     * Test processing of log4j.reset property, see bug 17531.
+     */
+    @Test
+    public void testReset() {
+        final VectorAppender appender = new VectorAppender();
+        appender.setName("A1");
+        Logger.getRootLogger().addAppender(appender);
+        final Properties properties = new Properties();
+        properties.put("log4j.reset", "true");
+        PropertyConfigurator.configure(properties);
+        assertNull(Logger.getRootLogger().getAppender("A1"));
+        LogManager.resetConfiguration();
+    }
+
+    /**
+     * Test of log4j.throwableRenderer support. See bug 45721.
+     */
+    public void testThrowableRenderer() {
+        final Properties properties = new Properties();
+        properties.put("log4j.throwableRenderer", "org.apache.log4j.PropertyConfiguratorTest$MockThrowableRenderer");
+        properties.put("log4j.throwableRenderer.showVersion", "false");
+        PropertyConfigurator.configure(properties);
+        final ThrowableRendererSupport repo = (ThrowableRendererSupport) LogManager.getLoggerRepository();
+        final MockThrowableRenderer renderer = (MockThrowableRenderer) repo.getThrowableRenderer();
+        LogManager.resetConfiguration();
+//        assertNotNull(renderer);
+//        assertEquals(true, renderer.isActivated());
+//        assertEquals(false, renderer.getShowVersion());
+    }
+
+    /**
+     * Test for bug 40944. configure(URL) never closed opened stream.
+     *
+     * @throws IOException if IOException creating properties file.
+     */
+    @Test
+    public void testURL() throws IOException {
+        final File file = new File("target/unclosed.properties");
+        try (FileWriter writer = new FileWriter(file)) {
+            writer.write("log4j.rootLogger=debug");
+        }
+        final URL url = file.toURI().toURL();
+        PropertyConfigurator.configure(url);
+        assertTrue(file.delete());
+        assertFalse(file.exists());
+    }
+
+    /**
+     * Test for bug 40944. configure(URL) did not catch IllegalArgumentException and did not close stream.
+     *
+     * @throws IOException if IOException creating properties file.
+     */
+    @Test
+    public void testURLBadEscape() throws IOException {
+        final File file = new File("target/urlbadescape.properties");
+        try (FileWriter writer = new FileWriter(file)) {
+            writer.write("log4j.rootLogger=\\uXX41");
+        }
+        final URL url = file.toURI().toURL();
+        PropertyConfigurator.configure(url);
+        assertTrue(file.delete());
+        assertFalse(file.exists());
+    }
+
+    void validateNested() {
+        final Logger logger = Logger.getLogger("org.apache.log4j.PropertyConfiguratorTest");
+        final String appenderName = "ROLLING";
+        // Appender OK
+        final Appender appender = logger.getAppender(appenderName);
+        assertNotNull(appender);
+        // Down-cast?
+//        final RollingFileAppender rfa = (RollingFileAppender) appender;
+//        assertNotNull(appenderName, rfa);
+//        final FixedWindowRollingPolicy rollingPolicy = (FixedWindowRollingPolicy) rfa.getRollingPolicy();
+//        assertEquals("filterBase-test1.log", rollingPolicy.getActiveFileName());
+//        assertEquals("filterBased-test1.%i", rollingPolicy.getFileNamePattern());
+//        assertEquals(0, rollingPolicy.getMinIndex());
+//        assertTrue(rollingPolicy.isActivated());
+//        final FilterBasedTriggeringPolicy triggeringPolicy = (FilterBasedTriggeringPolicy) rfa.getTriggeringPolicy();
+//        final LevelRangeFilter filter = (LevelRangeFilter) triggeringPolicy.getFilter();
+//        assertTrue(Level.INFO.equals(filter.getLevelMin()));
+    }
+}

[logging-log4j2] 01/02: Add classes for BC.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit c959ef9de38cc697c311f3efaaaac31fa302a7a0
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Fri Jan 14 13:32:17 2022 -0500

    Add classes for BC.
---
 .../main/java/org/apache/log4j/FileAppender.java   | 306 +++++++++++++++++++++
 .../java/org/apache/log4j/RollingFileAppender.java | 253 +++++++++++++++++
 2 files changed, 559 insertions(+)

diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/FileAppender.java b/log4j-1.2-api/src/main/java/org/apache/log4j/FileAppender.java
new file mode 100644
index 0000000..40f46a9
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/FileAppender.java
@@ -0,0 +1,306 @@
+/*
+ * 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;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.Writer;
+
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.QuietWriter;
+import org.apache.log4j.spi.ErrorCode;
+
+/**
+ * FileAppender appends log events to a file.
+ * <p>
+ * Support for <code>java.io.Writer</code> and console appending has been deprecated and then removed. See the
+ * replacement solutions: {@link WriterAppender} and {@link ConsoleAppender}.
+ * </p>
+ */
+public class FileAppender extends WriterAppender {
+
+    /**
+     * Controls file truncatation. The default value for this variable is <code>true</code>, meaning that by default a
+     * <code>FileAppender</code> will append to an existing file and not truncate it.
+     * <p>
+     * This option is meaningful only if the FileAppender opens the file.
+     * </p>
+     */
+    protected boolean fileAppend = true;
+
+    /**
+     * The name of the log file.
+     */
+    protected String fileName = null;
+
+    /**
+     * Do we do bufferedIO?
+     */
+    protected boolean bufferedIO = false;
+
+    /**
+     * Determines the size of IO buffer be. Default is 8K.
+     */
+    protected int bufferSize = 8 * 1024;
+
+    /**
+     * The default constructor does not do anything.
+     */
+    public FileAppender() {
+    }
+
+    /**
+     * Constructs a FileAppender and open the file designated by <code>filename</code>. The opened filename will become the
+     * output destination for this appender.
+     * <p>
+     * The file will be appended to.
+     * </p>
+     */
+    public FileAppender(Layout layout, String filename) throws IOException {
+        this(layout, filename, true);
+    }
+
+    /**
+     * Constructs a FileAppender and open the file designated by <code>filename</code>. The opened filename will become the
+     * output destination for this appender.
+     * <p>
+     * If the <code>append</code> parameter is true, the file will be appended to. Otherwise, the file designated by
+     * <code>filename</code> will be truncated before being opened.
+     * </p>
+     */
+    public FileAppender(Layout layout, String filename, boolean append) throws IOException {
+        this.layout = layout;
+        this.setFile(filename, append, false, bufferSize);
+    }
+
+    /**
+     * Constructs a <code>FileAppender</code> and open the file designated by <code>filename</code>. The opened filename
+     * will become the output destination for this appender.
+     * <p>
+     * If the <code>append</code> parameter is true, the file will be appended to. Otherwise, the file designated by
+     * <code>filename</code> will be truncated before being opened.
+     * </p>
+     * <p>
+     * If the <code>bufferedIO</code> parameter is <code>true</code>, then buffered IO will be used to write to the output
+     * file.
+     * </p>
+     */
+    public FileAppender(Layout layout, String filename, boolean append, boolean bufferedIO, int bufferSize) throws IOException {
+        this.layout = layout;
+        this.setFile(filename, append, bufferedIO, bufferSize);
+    }
+
+    /**
+     * If the value of <b>File</b> is not <code>null</code>, then {@link #setFile} is called with the values of <b>File</b>
+     * and <b>Append</b> properties.
+     * 
+     * @since 0.8.1
+     */
+    public void activateOptions() {
+        if (fileName != null) {
+            try {
+                setFile(fileName, fileAppend, bufferedIO, bufferSize);
+            } catch (java.io.IOException e) {
+                errorHandler.error("setFile(" + fileName + "," + fileAppend + ") call failed.", e, ErrorCode.FILE_OPEN_FAILURE);
+            }
+        } else {
+            // LogLog.error("File option not set for appender ["+name+"].");
+            LogLog.warn("File option not set for appender [" + name + "].");
+            LogLog.warn("Are you using FileAppender instead of ConsoleAppender?");
+        }
+    }
+
+    /**
+     * Closes the previously opened file.
+     */
+    protected void closeFile() {
+        if (this.qw != null) {
+            try {
+                this.qw.close();
+            } catch (java.io.IOException e) {
+                if (e instanceof InterruptedIOException) {
+                    Thread.currentThread().interrupt();
+                }
+                // Exceptionally, it does not make sense to delegate to an
+                // ErrorHandler. Since a closed appender is basically dead.
+                LogLog.error("Could not close " + qw, e);
+            }
+        }
+    }
+
+    /**
+     * Returns the value of the <b>Append</b> option.
+     */
+    public boolean getAppend() {
+        return fileAppend;
+    }
+
+    /**
+     * Get the value of the <b>BufferedIO</b> option.
+     * 
+     * <p>
+     * BufferedIO will significatnly increase performance on heavily loaded systems.
+     * </p>
+     */
+    public boolean getBufferedIO() {
+        return this.bufferedIO;
+    }
+
+    /**
+     * Get the size of the IO buffer.
+     */
+    public int getBufferSize() {
+        return this.bufferSize;
+    }
+
+    /** Returns the value of the <b>File</b> option. */
+    public String getFile() {
+        return fileName;
+    }
+
+    /**
+     * Close any previously opened file and call the parent's <code>reset</code>.
+     */
+    protected void reset() {
+        closeFile();
+        this.fileName = null;
+        super.reset();
+    }
+
+    /**
+     * The <b>Append</b> option takes a boolean value. It is set to <code>true</code> by default. If true, then
+     * <code>File</code> will be opened in append mode by {@link #setFile setFile} (see above). Otherwise, {@link #setFile
+     * setFile} will open <code>File</code> in truncate mode.
+     * 
+     * <p>
+     * Note: Actual opening of the file is made when {@link #activateOptions} is called, not when the options are set.
+     * </p>
+     */
+    public void setAppend(boolean flag) {
+        fileAppend = flag;
+    }
+
+    /**
+     * The <b>BufferedIO</b> option takes a boolean value. It is set to <code>false</code> by default. If true, then
+     * <code>File</code> will be opened and the resulting {@link java.io.Writer} wrapped around a {@link BufferedWriter}.
+     * 
+     * BufferedIO will significatnly increase performance on heavily loaded systems.
+     * 
+     */
+    public void setBufferedIO(boolean bufferedIO) {
+        this.bufferedIO = bufferedIO;
+        if (bufferedIO) {
+            immediateFlush = false;
+        }
+    }
+
+    /**
+     * Set the size of the IO buffer.
+     */
+    public void setBufferSize(int bufferSize) {
+        this.bufferSize = bufferSize;
+    }
+
+    /**
+     * The <b>File</b> property takes a string value which should be the name of the file to append to.
+     * <p>
+     * <font color="#DD0044"><b>Note that the special values "System.out" or "System.err" are no longer honored.</b></font>
+     * </p>
+     * <p>
+     * Note: Actual opening of the file is made when {@link #activateOptions} is called, not when the options are set.
+     * </p>
+     */
+    public void setFile(String file) {
+        // Trim spaces from both ends. The users probably does not want
+        // trailing spaces in file names.
+        String val = file.trim();
+        fileName = val;
+    }
+
+    /**
+     * Sets and <i>opens</i> the file where the log output will go. The specified file must be writable.
+     * <p>
+     * If there was already an opened file, then the previous file is closed first.
+     * </p>
+     * <p>
+     * <b>Do not use this method directly. To configure a FileAppender or one of its subclasses, set its properties one by
+     * one and then call activateOptions.</b>
+     * </p>
+     * 
+     * @param fileName The path to the log file.
+     * @param append If true will append to fileName. Otherwise will truncate fileName.
+     */
+    public synchronized void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize) throws IOException {
+        LogLog.debug("setFile called: " + fileName + ", " + append);
+
+        // It does not make sense to have immediate flush and bufferedIO.
+        if (bufferedIO) {
+            setImmediateFlush(false);
+        }
+
+        reset();
+        FileOutputStream ostream = null;
+        try {
+            //
+            // attempt to create file
+            //
+            ostream = new FileOutputStream(fileName, append);
+        } catch (FileNotFoundException ex) {
+            //
+            // if parent directory does not exist then
+            // attempt to create it and try to create file
+            // see bug 9150
+            //
+            String parentName = new File(fileName).getParent();
+            if (parentName != null) {
+                File parentDir = new File(parentName);
+                if (!parentDir.exists() && parentDir.mkdirs()) {
+                    ostream = new FileOutputStream(fileName, append);
+                } else {
+                    throw ex;
+                }
+            } else {
+                throw ex;
+            }
+        }
+        Writer fw = createWriter(ostream);
+        if (bufferedIO) {
+            fw = new BufferedWriter(fw, bufferSize);
+        }
+        this.setQWForFiles(fw);
+        this.fileName = fileName;
+        this.fileAppend = append;
+        this.bufferedIO = bufferedIO;
+        this.bufferSize = bufferSize;
+        writeHeader();
+        LogLog.debug("setFile ended");
+    }
+
+    /**
+     * Sets the quiet writer being used.
+     * 
+     * This method is overriden by {@link RollingFileAppender}.
+     */
+    protected void setQWForFiles(Writer writer) {
+        this.qw = new QuietWriter(writer, errorHandler);
+    }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/RollingFileAppender.java b/log4j-1.2-api/src/main/java/org/apache/log4j/RollingFileAppender.java
new file mode 100644
index 0000000..964c519
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/RollingFileAppender.java
@@ -0,0 +1,253 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.io.File;
+import java.io.InterruptedIOException;
+
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.CountingQuietWriter;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * RollingFileAppender extends FileAppender to backup the log files when they reach a certain size.
+ * 
+ * The log4j extras companion includes alternatives which should be considered for new deployments and which are
+ * discussed in the documentation for org.apache.log4j.rolling.RollingFileAppender.
+ */
+public class RollingFileAppender extends FileAppender {
+
+    /**
+     * The default maximum file size is 10MB.
+     */
+    protected long maxFileSize = 10 * 1024 * 1024;
+
+    /**
+     * There is one backup file by default.
+     */
+    protected int maxBackupIndex = 1;
+
+    private long nextRollover = 0;
+
+    /**
+     * The default constructor simply calls its {@link FileAppender#FileAppender parents constructor}.
+     */
+    public RollingFileAppender() {
+        super();
+    }
+
+    /**
+     * Constructs a RollingFileAppender and open the file designated by <code>filename</code>. The opened filename will
+     * become the ouput destination for this appender.
+     * 
+     * <p>
+     * If the <code>append</code> parameter is true, the file will be appended to. Otherwise, the file desginated by
+     * <code>filename</code> will be truncated before being opened.
+     * </p>
+     */
+    public RollingFileAppender(Layout layout, String filename, boolean append) throws IOException {
+        super(layout, filename, append);
+    }
+
+    /**
+     * Constructs a FileAppender and open the file designated by <code>filename</code>. The opened filename will become the
+     * output destination for this appender.
+     * 
+     * <p>
+     * The file will be appended to.
+     * </p>
+     */
+    public RollingFileAppender(Layout layout, String filename) throws IOException {
+        super(layout, filename);
+    }
+
+    /**
+     * Gets the value of the <b>MaxBackupIndex</b> option.
+     */
+    public int getMaxBackupIndex() {
+        return maxBackupIndex;
+    }
+
+    /**
+     * Gets the maximum size that the output file is allowed to reach before being rolled over to backup files.
+     * 
+     * @since 1.1
+     */
+    public long getMaximumFileSize() {
+        return maxFileSize;
+    }
+
+    /**
+     * Implements the usual roll over behaviour.
+     * <p>
+     * If <code>MaxBackupIndex</code> is positive, then files {<code>File.1</code>, ...,
+     * <code>File.MaxBackupIndex -1</code>} are renamed to {<code>File.2</code>, ..., <code>File.MaxBackupIndex</code>}.
+     * Moreover, <code>File</code> is renamed <code>File.1</code> and closed. A new <code>File</code> is created to receive
+     * further log output.
+     * </p>
+     * <p>
+     * If <code>MaxBackupIndex</code> is equal to zero, then the <code>File</code> is truncated with no backup files
+     * created.
+     * </p>
+     */
+    public // synchronization not necessary since doAppend is alreasy synched
+    void rollOver() {
+        File target;
+        File file;
+
+        if (qw != null) {
+            long size = ((CountingQuietWriter) qw).getCount();
+            LogLog.debug("rolling over count=" + size);
+            // if operation fails, do not roll again until
+            // maxFileSize more bytes are written
+            nextRollover = size + maxFileSize;
+        }
+        LogLog.debug("maxBackupIndex=" + maxBackupIndex);
+
+        boolean renameSucceeded = true;
+        // If maxBackups <= 0, then there is no file renaming to be done.
+        if (maxBackupIndex > 0) {
+            // Delete the oldest file, to keep Windows happy.
+            file = new File(fileName + '.' + maxBackupIndex);
+            if (file.exists())
+                renameSucceeded = file.delete();
+
+            // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2}
+            for (int i = maxBackupIndex - 1; i >= 1 && renameSucceeded; i--) {
+                file = new File(fileName + "." + i);
+                if (file.exists()) {
+                    target = new File(fileName + '.' + (i + 1));
+                    LogLog.debug("Renaming file " + file + " to " + target);
+                    renameSucceeded = file.renameTo(target);
+                }
+            }
+
+            if (renameSucceeded) {
+                // Rename fileName to fileName.1
+                target = new File(fileName + "." + 1);
+
+                this.closeFile(); // keep windows happy.
+
+                file = new File(fileName);
+                LogLog.debug("Renaming file " + file + " to " + target);
+                renameSucceeded = file.renameTo(target);
+                //
+                // if file rename failed, reopen file with append = true
+                //
+                if (!renameSucceeded) {
+                    try {
+                        this.setFile(fileName, true, bufferedIO, bufferSize);
+                    } catch (IOException e) {
+                        if (e instanceof InterruptedIOException) {
+                            Thread.currentThread().interrupt();
+                        }
+                        LogLog.error("setFile(" + fileName + ", true) call failed.", e);
+                    }
+                }
+            }
+        }
+
+        //
+        // if all renames were successful, then
+        //
+        if (renameSucceeded) {
+            try {
+                // This will also close the file. This is OK since multiple
+                // close operations are safe.
+                this.setFile(fileName, false, bufferedIO, bufferSize);
+                nextRollover = 0;
+            } catch (IOException e) {
+                if (e instanceof InterruptedIOException) {
+                    Thread.currentThread().interrupt();
+                }
+                LogLog.error("setFile(" + fileName + ", false) call failed.", e);
+            }
+        }
+    }
+
+    public synchronized void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize) throws IOException {
+        super.setFile(fileName, append, this.bufferedIO, this.bufferSize);
+        if (append) {
+            File f = new File(fileName);
+            ((CountingQuietWriter) qw).setCount(f.length());
+        }
+    }
+
+    /**
+     * Sets the maximum number of backup files to keep around.
+     * 
+     * <p>
+     * The <b>MaxBackupIndex</b> option determines how many backup files are kept before the oldest is erased. This option
+     * takes a positive integer value. If set to zero, then there will be no backup files and the log file will be truncated
+     * when it reaches <code>MaxFileSize</code>.
+     * </p>
+     */
+    public void setMaxBackupIndex(int maxBackups) {
+        this.maxBackupIndex = maxBackups;
+    }
+
+    /**
+     * Sets the maximum size that the output file is allowed to reach before being rolled over to backup files.
+     * 
+     * <p>
+     * This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter taking
+     * a <code>long</code> argument from the setter taking a <code>String</code> argument by the JavaBeans
+     * {@link java.beans.Introspector Introspector}.
+     * </p>
+     * 
+     * @see #setMaxFileSize(String)
+     */
+    public void setMaximumFileSize(long maxFileSize) {
+        this.maxFileSize = maxFileSize;
+    }
+
+    /**
+     * Sets the maximum size that the output file is allowed to reach before being rolled over to backup files.
+     * 
+     * <p>
+     * In configuration files, the <b>MaxFileSize</b> option takes an long integer in the range 0 - 2^63. You can specify
+     * the value with the suffixes "KB", "MB" or "GB" so that the integer is interpreted being expressed respectively in
+     * kilobytes, megabytes or gigabytes. For example, the value "10KB" will be interpreted as 10240.
+     * </p>
+     */
+    public void setMaxFileSize(String value) {
+        maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1);
+    }
+
+    protected void setQWForFiles(Writer writer) {
+        this.qw = new CountingQuietWriter(writer, errorHandler);
+    }
+
+    /**
+     * This method differentiates RollingFileAppender from its super class.
+     * 
+     * @since 0.9.0
+     */
+    protected void subAppend(LoggingEvent event) {
+        super.subAppend(event);
+        if (fileName != null && qw != null) {
+            long size = ((CountingQuietWriter) qw).getCount();
+            if (size >= maxFileSize && size >= nextRollover) {
+                rollOver();
+            }
+        }
+    }
+}