You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rg...@apache.org on 2019/11/21 04:27:32 UTC

[logging-log4j2] branch release-2.x updated: LOG4J2-63 Add AsyncAppender

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

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


View the commit online:
https://github.com/apache/logging-log4j2/commit/8193ab7004696bbb64038bf1b3400a614bbc7991

The following commit(s) were added to refs/heads/release-2.x by this push:
     new 8193ab7  LOG4J2-63 Add AsyncAppender
8193ab7 is described below

commit 8193ab7004696bbb64038bf1b3400a614bbc7991
Author: Ralph Goers <rg...@apache.org>
AuthorDate: Wed Nov 20 21:27:09 2019 -0700

    LOG4J2-63 Add AsyncAppender
---
 .../org/apache/log4j/builders/AbstractBuilder.java |  64 +++++++-
 .../builders/appender/AsyncAppenderBuilder.java    | 170 +++++++++++++++++++++
 .../builders/appender/ConsoleAppenderBuilder.java  |  80 ++++++----
 .../appender/DailyRollingFileAppenderBuilder.java  |  30 ++--
 .../builders/appender/FileAppenderBuilder.java     |  32 ++--
 .../appender/RollingFileAppenderBuilder.java       |  27 ++--
 .../apache/log4j/config/Log4j1Configuration.java   |   2 +
 .../org/apache/log4j/xml/XmlConfiguration.java     |  11 +-
 .../org/apache/log4j/config/AsyncAppenderTest.java | 103 +++++++++++++
 .../src/test/resources/log4j1-async.properties     |  21 +++
 log4j-1.2-api/src/test/resources/log4j1-async.xml  |  35 +++++
 11 files changed, 503 insertions(+), 72 deletions(-)

diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/AbstractBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/AbstractBuilder.java
index d4e1b37..0448ed2 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/AbstractBuilder.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/AbstractBuilder.java
@@ -16,13 +16,26 @@
  */
 package org.apache.log4j.builders;
 
+import org.apache.log4j.bridge.FilterAdapter;
+import org.apache.log4j.bridge.FilterWrapper;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.spi.Filter;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.filter.CompositeFilter;
+import org.apache.logging.log4j.core.filter.ThresholdFilter;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Properties;
 
 /**
- * Class Description goes here.
+ * Base class for Log4j 1 component builders.
  */
 public abstract class AbstractBuilder {
 
+    private static Logger LOGGER = StatusLogger.getLogger();
     protected static final String FILE_PARAM = "File";
     protected static final String APPEND_PARAM = "Append";
     protected static final String BUFFERED_IO_PARAM = "BufferedIO";
@@ -55,4 +68,53 @@ public abstract class AbstractBuilder {
     public boolean getBooleanProperty(String key) {
         return Boolean.parseBoolean(props.getProperty(prefix + key, Boolean.FALSE.toString()));
     }
+
+    public int getIntegerProperty(String key, int defaultValue) {
+        String value = props.getProperty(key);
+        try {
+            if (value != null) {
+                return Integer.parseInt(value);
+            }
+        } catch (Exception ex) {
+            LOGGER.warn("Error converting value {} of {} to an integer: {}", value, key, ex.getMessage());
+        }
+        return defaultValue;
+    }
+
+    public Properties getProperties() {
+        return props;
+    }
+
+
+    protected org.apache.logging.log4j.core.Filter buildFilters(String level, Filter filter) {
+        if (level != null && filter != null) {
+            List<org.apache.logging.log4j.core.Filter> filterList = new ArrayList<>();
+            org.apache.logging.log4j.core.Filter thresholdFilter =
+                    ThresholdFilter.createFilter(OptionConverter.convertLevel(level, Level.TRACE),
+                            org.apache.logging.log4j.core.Filter.Result.NEUTRAL,
+                            org.apache.logging.log4j.core.Filter.Result.DENY);
+            filterList.add(thresholdFilter);
+            Filter f = filter;
+            while (f != null) {
+                if (filter instanceof FilterWrapper) {
+                    filterList.add(((FilterWrapper) f).getFilter());
+                } else {
+                    filterList.add(new FilterAdapter(f));
+                }
+                f = f.next;
+            }
+            return CompositeFilter.createFilters(filterList.toArray(new org.apache.logging.log4j.core.Filter[0]));
+        } else if (level != null) {
+            return ThresholdFilter.createFilter(OptionConverter.convertLevel(level, Level.TRACE),
+                    org.apache.logging.log4j.core.Filter.Result.NEUTRAL,
+                    org.apache.logging.log4j.core.Filter.Result.DENY);
+        } else if (filter != null) {
+            if (filter instanceof FilterWrapper) {
+                return ((FilterWrapper) filter).getFilter();
+            } else {
+                return new FilterAdapter(filter);
+            }
+        }
+        return null;
+    }
 }
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/AsyncAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/AsyncAppenderBuilder.java
new file mode 100644
index 0000000..e14537c
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/AsyncAppenderBuilder.java
@@ -0,0 +1,170 @@
+/*
+ * 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.builders.appender;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.bridge.AppenderAdapter;
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.builders.AbstractBuilder;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.config.Log4j1Configuration;
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.appender.AsyncAppender;
+import org.apache.logging.log4j.core.config.AppenderRef;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfiguration.NAME_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG;
+import static org.apache.log4j.xml.XmlConfiguration.REF_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.VALUE_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.forEachElement;
+import static org.apache.log4j.config.Log4j1Configuration.APPENDER_REF_TAG;
+import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM;
+
+
+/**
+ * Build an Asynch Appender
+ */
+@Plugin(name = "org.apache.log4j.AsyncAppender", category = CATEGORY)
+public class AsyncAppenderBuilder extends AbstractBuilder implements AppenderBuilder {
+
+    private static final Logger LOGGER = StatusLogger.getLogger();
+    private static final String BLOCKING_PARAM = "Blocking";
+    private static final String INCLUDE_LOCATION_PARAM = "IncludeLocation";
+
+    public AsyncAppenderBuilder() {
+    }
+
+    public AsyncAppenderBuilder(String prefix, Properties props) {
+        super(prefix, props);
+    }
+
+    @Override
+    public Appender parseAppender(final Element appenderElement, final XmlConfiguration config) {
+        String name = appenderElement.getAttribute(NAME_ATTR);
+        Holder<List<String>> appenderRefs = new Holder<>(new ArrayList<>());
+        Holder<Boolean> blocking = new BooleanHolder();
+        Holder<Boolean> includeLocation = new BooleanHolder();
+        Holder<String> level = new Holder<>("trace");
+        Holder<Integer> bufferSize = new Holder<>(1024);
+        forEachElement(appenderElement.getChildNodes(), (currentElement) -> {
+            switch (currentElement.getTagName()) {
+                case APPENDER_REF_TAG:
+                    Appender appender = config.findAppenderByReference(currentElement);
+                    if (appender != null) {
+                        appenderRefs.get().add(appender.getName());
+                    }
+                    break;
+                case PARAM_TAG: {
+                    switch (currentElement.getAttribute(NAME_ATTR)) {
+                        case BUFFER_SIZE_PARAM: {
+                            String value = currentElement.getAttribute(VALUE_ATTR);
+                            if (value == null) {
+                                LOGGER.warn("No value supplied for BufferSize parameter. Defaulting to 1024.");
+                            } else {
+                                bufferSize.set(Integer.parseInt(value));
+                            }
+                            break;
+                        }
+                        case BLOCKING_PARAM: {
+                            String value = currentElement.getAttribute(VALUE_ATTR);
+                            if (value == null) {
+                                LOGGER.warn("No value supplied for Blocking parameter. Defaulting to false.");
+                            } else {
+                                blocking.set(Boolean.parseBoolean(value));
+                            }
+                            break;
+                        }
+                        case INCLUDE_LOCATION_PARAM: {
+                            String value = currentElement.getAttribute(VALUE_ATTR);
+                            if (value == null) {
+                                LOGGER.warn("No value supplied for IncludeLocation parameter. Defaulting to false.");
+                            } else {
+                                includeLocation.set(Boolean.parseBoolean(value));
+                            }
+                            break;
+                        }
+                        case THRESHOLD_PARAM: {
+                            String value = currentElement.getAttribute(VALUE_ATTR);
+                            if (value == null) {
+                                LOGGER.warn("No value supplied for Threshold parameter, ignoring.");
+                            } else {
+                                level.set(value);
+                            }
+                            break;
+                        }
+                    }
+                    break;
+                }
+            }
+        });
+        return createAppender(name, level.get(), appenderRefs.get().toArray(new String[0]), blocking.get(),
+                bufferSize.get(), includeLocation.get(), config);
+    }
+
+    @Override
+    public Appender parseAppender(final String name, final String layoutPrefix,
+            final String filterPrefix, final Properties props, final PropertiesConfiguration configuration) {
+        String appenderRef = getProperty(APPENDER_REF_TAG);
+        boolean blocking = getBooleanProperty(BLOCKING_PARAM);
+        boolean includeLocation = getBooleanProperty(INCLUDE_LOCATION_PARAM);
+        String level = getProperty(THRESHOLD_PARAM);
+        int bufferSize = getIntegerProperty(BUFFER_SIZE_PARAM, 1024);
+        if (appenderRef == null) {
+            LOGGER.warn("No appender references configured for AsyncAppender {}", name);
+            return null;
+        }
+        Appender appender = configuration.parseAppender(props, appenderRef);
+        if (appender == null) {
+            LOGGER.warn("Cannot locate Appender {}", appenderRef);
+            return null;
+        }
+        return createAppender(name, level, new String[] {appenderRef}, blocking, bufferSize, includeLocation,
+                configuration);
+    }
+
+    private <T extends Log4j1Configuration> Appender createAppender(String name, String level,
+            String[] appenderRefs, boolean blocking, int bufferSize, boolean includeLocation,
+            T configuration) {
+        org.apache.logging.log4j.Level logLevel = OptionConverter.convertLevel(level,
+                org.apache.logging.log4j.Level.TRACE);
+        AppenderRef[] refs = new AppenderRef[appenderRefs.length];
+        int index = 0;
+        for (String appenderRef : appenderRefs) {
+            refs[index++] = AppenderRef.createAppenderRef(appenderRef, logLevel, null);
+        }
+        return new AppenderWrapper(AsyncAppender.newBuilder()
+                .setName(name)
+                .setAppenderRefs(refs)
+                .setBlocking(blocking)
+                .setBufferSize(bufferSize)
+                .setIncludeLocation(includeLocation)
+                .setConfiguration(configuration)
+                .build());
+    }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/ConsoleAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/ConsoleAppenderBuilder.java
index 8c754bd..398ca6d 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/ConsoleAppenderBuilder.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/ConsoleAppenderBuilder.java
@@ -19,8 +19,6 @@ package org.apache.log4j.builders.appender;
 import org.apache.log4j.Appender;
 import org.apache.log4j.Layout;
 import org.apache.log4j.bridge.AppenderWrapper;
-import org.apache.log4j.bridge.FilterAdapter;
-import org.apache.log4j.bridge.FilterWrapper;
 import org.apache.log4j.bridge.LayoutAdapter;
 import org.apache.log4j.bridge.LayoutWrapper;
 import org.apache.log4j.builders.AbstractBuilder;
@@ -35,9 +33,12 @@ import org.apache.logging.log4j.core.config.plugins.Plugin;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.w3c.dom.Element;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Properties;
 
 import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM;
 import static org.apache.log4j.xml.XmlConfiguration.FILTER_TAG;
 import static org.apache.log4j.xml.XmlConfiguration.LAYOUT_TAG;
 import static org.apache.log4j.xml.XmlConfiguration.NAME_ATTR;
@@ -45,7 +46,6 @@ import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG;
 import static org.apache.log4j.xml.XmlConfiguration.VALUE_ATTR;
 import static org.apache.log4j.xml.XmlConfiguration.forEachElement;
 
-
 /**
  * Build a Console Appender
  */
@@ -69,39 +69,63 @@ public class ConsoleAppenderBuilder extends AbstractBuilder implements AppenderB
         String name = appenderElement.getAttribute(NAME_ATTR);
         Holder<String> target = new Holder<>(SYSTEM_OUT);
         Holder<Layout> layout = new Holder<>();
-        Holder<Filter> filter = new Holder<>();
+        Holder<List<Filter>> filters = new Holder<>(new ArrayList<>());
+        Holder<String> level = new Holder<>();
         forEachElement(appenderElement.getChildNodes(), (currentElement) -> {
             switch (currentElement.getTagName()) {
                 case LAYOUT_TAG:
                     layout.set(config.parseLayout(currentElement));
                     break;
                 case FILTER_TAG:
-                    filter.set(config.parseFilters(currentElement));
+                    filters.get().add(config.parseFilters(currentElement));
                     break;
                 case PARAM_TAG: {
-                    if (currentElement.getAttribute(NAME_ATTR).equalsIgnoreCase(TARGET)) {
-                        String value = currentElement.getAttribute(VALUE_ATTR);
-                        if (value == null) {
-                            LOGGER.warn("No value supplied for target parameter. Defaulting to System.out.");
-                        } else {
-                            switch (value) {
-                                case SYSTEM_OUT:
-                                    target.set(SYSTEM_OUT);
-                                    break;
-                                case SYSTEM_ERR:
-                                    target.set(SYSTEM_ERR);
-                                    break;
-                                default:
-                                    LOGGER.warn("Invalid value \"{}\" for target parameter. Using default of System.out",
-                                            value);
+                    switch (currentElement.getAttribute(NAME_ATTR)) {
+                        case TARGET: {
+                            String value = currentElement.getAttribute(VALUE_ATTR);
+                            if (value == null) {
+                                LOGGER.warn("No value supplied for target parameter. Defaulting to System.out.");
+                            } else {
+                                switch (value) {
+                                    case SYSTEM_OUT:
+                                        target.set(SYSTEM_OUT);
+                                        break;
+                                    case SYSTEM_ERR:
+                                        target.set(SYSTEM_ERR);
+                                        break;
+                                    default:
+                                        LOGGER.warn("Invalid value \"{}\" for target parameter. Using default of System.out",
+                                                value);
+                                }
+                            }
+                            break;
+                        }
+                        case THRESHOLD_PARAM: {
+                            String value = currentElement.getAttribute(VALUE_ATTR);
+                            if (value == null) {
+                                LOGGER.warn("No value supplied for Threshold parameter, ignoring.");
+                            } else {
+                                level.set(value);
                             }
+                            break;
                         }
                     }
                     break;
                 }
             }
         });
-        return createAppender(name, layout.get(), filter.get(), target.get(), config);
+        Filter head = null;
+        Filter current = null;
+        for (Filter f : filters.get()) {
+            if (head == null) {
+                head = f;
+                current = f;
+            } else {
+                current.next = f;
+                current = f;
+            }
+        }
+        return createAppender(name, layout.get(), head, level.get(), target.get(), config);
     }
 
     @Override
@@ -109,27 +133,21 @@ public class ConsoleAppenderBuilder extends AbstractBuilder implements AppenderB
             final String filterPrefix, final Properties props, final PropertiesConfiguration configuration) {
         Layout layout = configuration.parseLayout(layoutPrefix, name, props);
         Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name);
+        String level = getProperty(THRESHOLD_PARAM);
         String target = getProperty(TARGET);
-        return createAppender(name, layout, filter, target, configuration);
+        return createAppender(name, layout, filter, level, target, configuration);
     }
 
     private <T extends Log4j1Configuration> Appender createAppender(String name, Layout layout, Filter filter,
-            String target, T configuration) {
+            String level, String target, T configuration) {
         org.apache.logging.log4j.core.Layout<?> consoleLayout = null;
-        org.apache.logging.log4j.core.Filter consoleFilter = null;
 
         if (layout instanceof LayoutWrapper) {
             consoleLayout = ((LayoutWrapper) layout).getLayout();
         } else if (layout != null) {
             consoleLayout = new LayoutAdapter(layout);
         }
-        if (filter != null) {
-            if (filter instanceof FilterWrapper) {
-                consoleFilter = ((FilterWrapper) filter).getFilter();
-            } else {
-                consoleFilter = new FilterAdapter(filter);
-            }
-        }
+        org.apache.logging.log4j.core.Filter consoleFilter = buildFilters(level, filter);
         ConsoleAppender.Target consoleTarget = SYSTEM_ERR.equals(target)
                 ? ConsoleAppender.Target.SYSTEM_ERR : ConsoleAppender.Target.SYSTEM_OUT;
         return new AppenderWrapper(ConsoleAppender.newBuilder()
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/DailyRollingFileAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/DailyRollingFileAppenderBuilder.java
index ae50e45..a612d42 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/DailyRollingFileAppenderBuilder.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/DailyRollingFileAppenderBuilder.java
@@ -43,6 +43,7 @@ import org.w3c.dom.Element;
 import java.util.Properties;
 
 import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM;
 import static org.apache.log4j.xml.XmlConfiguration.FILTER_TAG;
 import static org.apache.log4j.xml.XmlConfiguration.LAYOUT_TAG;
 import static org.apache.log4j.xml.XmlConfiguration.NAME_ATTR;
@@ -73,6 +74,7 @@ public class DailyRollingFileAppenderBuilder extends AbstractBuilder implements
         Holder<Layout> layout = new Holder<>();
         Holder<Filter> filter = new Holder<>();
         Holder<String> fileName = new Holder<>();
+        Holder<String> level = new Holder<>();
         Holder<Boolean> immediateFlush = new BooleanHolder();
         Holder<Boolean> append = new BooleanHolder();
         Holder<Boolean> bufferedIo = new BooleanHolder();
@@ -108,7 +110,7 @@ public class DailyRollingFileAppenderBuilder extends AbstractBuilder implements
                             }
                             break;
                         }
-                        case BUFFER_SIZE_PARAM:
+                        case BUFFER_SIZE_PARAM: {
                             String size = currentElement.getAttribute(VALUE_ATTR);
                             if (size != null) {
                                 bufferSize.set(Integer.parseInt(size));
@@ -116,13 +118,23 @@ public class DailyRollingFileAppenderBuilder extends AbstractBuilder implements
                                 LOGGER.warn("No value provide for bufferSize parameter");
                             }
                             break;
+                        }
+                        case THRESHOLD_PARAM: {
+                            String value = currentElement.getAttribute(VALUE_ATTR);
+                            if (value == null) {
+                                LOGGER.warn("No value supplied for Threshold parameter, ignoring.");
+                            } else {
+                                level.set(value);
+                            }
+                            break;
+                        }
                     }
                     break;
                 }
             }
         });
         return createAppender(name, layout.get(), filter.get(), fileName.get(), append.get(), immediateFlush.get(),
-                bufferedIo.get(), bufferSize.get(), config);
+                level.get(), bufferedIo.get(), bufferSize.get(), config);
     }
 
     @Override
@@ -131,20 +143,20 @@ public class DailyRollingFileAppenderBuilder extends AbstractBuilder implements
         Layout layout = configuration.parseLayout(layoutPrefix, name, props);
         Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name);
         String fileName = getProperty(FILE_PARAM);
+        String level = getProperty(THRESHOLD_PARAM);
         boolean append = getBooleanProperty(APPEND_PARAM);
         boolean immediateFlush = false;
         boolean bufferedIo = getBooleanProperty(BUFFERED_IO_PARAM);
         int bufferSize = Integer.parseInt(getProperty(BUFFER_SIZE_PARAM, "8192"));
         return createAppender(name, layout, filter, fileName, append, immediateFlush,
-                bufferedIo, bufferSize, configuration);
+                level, bufferedIo, bufferSize, configuration);
     }
 
     private <T extends Log4j1Configuration> Appender createAppender(final String name, final Layout layout,
             final Filter filter, final String fileName, final boolean append, boolean immediateFlush,
-            final boolean bufferedIo, final int bufferSize, final T configuration) {
+            final String level, final boolean bufferedIo, final int bufferSize, final T configuration) {
 
         org.apache.logging.log4j.core.Layout<?> fileLayout = null;
-        org.apache.logging.log4j.core.Filter fileFilter = null;
         if (bufferedIo) {
             immediateFlush = true;
         }
@@ -153,13 +165,7 @@ public class DailyRollingFileAppenderBuilder extends AbstractBuilder implements
         } else if (layout != null) {
             fileLayout = new LayoutAdapter(layout);
         }
-        if (filter != null) {
-            if (filter instanceof FilterWrapper) {
-                fileFilter = ((FilterWrapper) filter).getFilter();
-            } else {
-                fileFilter = new FilterAdapter(filter);
-            }
-        }
+        org.apache.logging.log4j.core.Filter fileFilter = buildFilters(level, filter);
         if (fileName == null) {
             LOGGER.warn("Unable to create File Appender, no file name provided");
             return null;
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/FileAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/FileAppenderBuilder.java
index 76f2788..3b4fefd 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/FileAppenderBuilder.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/FileAppenderBuilder.java
@@ -39,6 +39,7 @@ import org.w3c.dom.Element;
 import java.util.Properties;
 
 import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM;
 import static org.apache.log4j.xml.XmlConfiguration.FILTER_TAG;
 import static org.apache.log4j.xml.XmlConfiguration.LAYOUT_TAG;
 import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG;
@@ -67,6 +68,7 @@ public class FileAppenderBuilder extends AbstractBuilder implements AppenderBuil
         Holder<Layout> layout = new Holder<>();
         Holder<Filter> filter = new Holder<>();
         Holder<String> fileName = new Holder<>();
+        Holder<String> level = new Holder<>();
         Holder<Boolean> immediateFlush = new BooleanHolder();
         Holder<Boolean> append = new BooleanHolder();
         Holder<Boolean> bufferedIo = new BooleanHolder();
@@ -102,7 +104,7 @@ public class FileAppenderBuilder extends AbstractBuilder implements AppenderBuil
                             }
                             break;
                         }
-                        case BUFFER_SIZE_PARAM:
+                        case BUFFER_SIZE_PARAM: {
                             String size = currentElement.getAttribute(VALUE_ATTR);
                             if (size != null) {
                                 bufferSize.set(Integer.parseInt(size));
@@ -110,14 +112,24 @@ public class FileAppenderBuilder extends AbstractBuilder implements AppenderBuil
                                 LOGGER.warn("No value provide for bufferSize parameter");
                             }
                             break;
+                        }
+                        case THRESHOLD_PARAM: {
+                            String value = currentElement.getAttribute(VALUE_ATTR);
+                            if (value == null) {
+                                LOGGER.warn("No value supplied for Threshold parameter, ignoring.");
+                            } else {
+                                level.set(value);
+                            }
+                            break;
+                        }
                     }
                     break;
                 }
             }
         });
 
-        return createAppender(name, config, layout.get(), filter.get(), fileName.get(), immediateFlush.get(),
-                append.get(), bufferedIo.get(), bufferSize.get());
+        return createAppender(name, config, layout.get(), filter.get(), fileName.get(), level.get(),
+                immediateFlush.get(), append.get(), bufferedIo.get(), bufferSize.get());
     }
 
 
@@ -126,20 +138,20 @@ public class FileAppenderBuilder extends AbstractBuilder implements AppenderBuil
             final Properties props, final PropertiesConfiguration configuration) {
         Layout layout = configuration.parseLayout(layoutPrefix, name, props);
         Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name);
+        String level = getProperty(THRESHOLD_PARAM);
         String fileName = getProperty(FILE_PARAM);
         boolean append = getBooleanProperty(APPEND_PARAM);
         boolean immediateFlush = false;
         boolean bufferedIo = getBooleanProperty(BUFFERED_IO_PARAM);
         int bufferSize = Integer.parseInt(getProperty(BUFFER_SIZE_PARAM, "8192"));
-        return createAppender(name, configuration, layout, filter, fileName, immediateFlush,
+        return createAppender(name, configuration, layout, filter, fileName, level, immediateFlush,
                 append, bufferedIo, bufferSize);
     }
 
     private Appender createAppender(final String name, final Log4j1Configuration configuration, final Layout layout,
-            final Filter filter, final String fileName, boolean immediateFlush, final boolean append,
+            final Filter filter, final String fileName, String level, boolean immediateFlush, final boolean append,
             final boolean bufferedIo, final int bufferSize) {
         org.apache.logging.log4j.core.Layout<?> fileLayout = null;
-        org.apache.logging.log4j.core.Filter fileFilter = null;
         if (bufferedIo) {
             immediateFlush = true;
         }
@@ -148,13 +160,7 @@ public class FileAppenderBuilder extends AbstractBuilder implements AppenderBuil
         } else if (layout != null) {
             fileLayout = new LayoutAdapter(layout);
         }
-        if (filter != null) {
-            if (filter instanceof FilterWrapper) {
-                fileFilter = ((FilterWrapper) filter).getFilter();
-            } else {
-                fileFilter = new FilterAdapter(filter);
-            }
-        }
+        org.apache.logging.log4j.core.Filter fileFilter = buildFilters(level, filter);
         if (fileName == null) {
             LOGGER.warn("Unable to create File Appender, no file name provided");
             return null;
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RollingFileAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RollingFileAppenderBuilder.java
index d6e33c5..0746be6 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RollingFileAppenderBuilder.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RollingFileAppenderBuilder.java
@@ -45,6 +45,7 @@ import org.w3c.dom.Element;
 import java.util.Properties;
 
 import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM;
 import static org.apache.log4j.xml.XmlConfiguration.FILTER_TAG;
 import static org.apache.log4j.xml.XmlConfiguration.LAYOUT_TAG;
 import static org.apache.log4j.xml.XmlConfiguration.NAME_ATTR;
@@ -80,6 +81,7 @@ public class RollingFileAppenderBuilder extends AbstractBuilder implements Appen
         Holder<Integer> bufferSize = new Holder<>(8192);
         Holder<String> maxSize = new Holder<>();
         Holder<String> maxBackups = new Holder<>();
+        Holder<String> level = new Holder<>();
         forEachElement(appenderElement.getChildNodes(), (currentElement) -> {
             switch (currentElement.getTagName()) {
                 case LAYOUT_TAG:
@@ -138,13 +140,22 @@ public class RollingFileAppenderBuilder extends AbstractBuilder implements Appen
                             }
                             break;
                         }
+                        case THRESHOLD_PARAM: {
+                            String value = currentElement.getAttribute(VALUE_ATTR);
+                            if (value == null) {
+                                LOGGER.warn("No value supplied for Threshold parameter, ignoring.");
+                            } else {
+                                level.set(value);
+                            }
+                            break;
+                        }
                     }
                     break;
                 }
             }
         });
         return createAppender(name, config, layout.get(), filter.get(), bufferedIo.get(), immediateFlush.get(),
-                fileName.get(), maxSize.get(), maxBackups.get());
+                fileName.get(), level.get(), maxSize.get(), maxBackups.get());
     }
 
 
@@ -154,19 +165,19 @@ public class RollingFileAppenderBuilder extends AbstractBuilder implements Appen
         Layout layout = configuration.parseLayout(layoutPrefix, name, props);
         Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name);
         String fileName = getProperty(FILE_PARAM);
+        String level = getProperty(THRESHOLD_PARAM);
         boolean immediateFlush = false;
         boolean bufferedIo = getBooleanProperty(BUFFERED_IO_PARAM);
         String maxSize = getProperty(MAX_SIZE_PARAM);
         String maxBackups = getProperty(MAX_BACKUP_INDEX);
-        return createAppender(name, configuration, layout, filter, bufferedIo, immediateFlush, fileName, maxSize,
+        return createAppender(name, configuration, layout, filter, bufferedIo, immediateFlush, fileName, level, maxSize,
                 maxBackups);
     }
 
     private Appender createAppender(final String name, final Log4j1Configuration config, final Layout layout,
             final Filter filter, final boolean bufferedIo, boolean immediateFlush, final String fileName,
-            final String maxSize, final String maxBackups) {
+            final String level, final String maxSize, final String maxBackups) {
         org.apache.logging.log4j.core.Layout<?> fileLayout = null;
-        org.apache.logging.log4j.core.Filter fileFilter = null;
         if (bufferedIo) {
             immediateFlush = true;
         }
@@ -175,13 +186,7 @@ public class RollingFileAppenderBuilder extends AbstractBuilder implements Appen
         } else if (layout != null) {
             fileLayout = new LayoutAdapter(layout);
         }
-        if (filter != null) {
-            if (filter instanceof FilterWrapper) {
-                fileFilter = ((FilterWrapper) filter).getFilter();
-            } else {
-                fileFilter = new FilterAdapter(filter);
-            }
-        }
+        org.apache.logging.log4j.core.Filter fileFilter = buildFilters(level, filter);
         if (fileName == null) {
             LOGGER.warn("Unable to create File Appender, no file name provided");
             return null;
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java
index ffa45d8..ceafd07 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java
@@ -29,6 +29,8 @@ import org.apache.logging.log4j.core.config.Reconfigurable;
 public class Log4j1Configuration extends AbstractConfiguration implements Reconfigurable {
 
     public static final String MONITOR_INTERVAL = "log4j1.monitorInterval";
+    public static final String APPENDER_REF_TAG = "appender-ref";
+    public static final String THRESHOLD_PARAM = "Threshold";
 
     public static final String INHERITED = "inherited";
 
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfiguration.java b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfiguration.java
index a82b773..6ce22c8 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfiguration.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfiguration.java
@@ -65,7 +65,6 @@ public class XmlConfiguration extends Log4j1Configuration {
     private static final String OLD_CONFIGURATION_TAG = "configuration";
     private static final String RENDERER_TAG = "renderer";
     private static final String APPENDER_TAG = "appender";
-    private static final String APPENDER_REF_TAG = "appender-ref";
     public  static final String PARAM_TAG = "param";
     public static final String LAYOUT_TAG = "layout";
     private static final String CATEGORY = "category";
@@ -80,7 +79,7 @@ public class XmlConfiguration extends Log4j1Configuration {
     private static final String PRIORITY_TAG = "priority";
     public static final String FILTER_TAG = "filter";
     private static final String ERROR_HANDLER_TAG = "errorHandler";
-    private static final String REF_ATTR = "ref";
+    public static final String REF_ATTR = "ref";
     private static final String ADDITIVITY_ATTR = "additivity";
     private static final String CONFIG_DEBUG_ATTR = "configDebug";
     private static final String INTERNAL_DEBUG_ATTR = "debug";
@@ -114,6 +113,10 @@ public class XmlConfiguration extends Log4j1Configuration {
         manager = new BuilderManager();
     }
 
+    public void addAppenderIfAbsent(Appender appender) {
+        appenderMap.putIfAbsent(appender.getName(), appender);
+    }
+
     /**
      * Configure log4j by reading in a log4j.dtd compliant XML
      * configuration file.
@@ -328,7 +331,7 @@ public class XmlConfiguration extends Log4j1Configuration {
     /**
      * Used internally to parse appenders by IDREF element.
      */
-    private Appender findAppenderByReference(Element appenderRef) {
+    public Appender findAppenderByReference(Element appenderRef) {
         String appenderName = subst(appenderRef.getAttribute(REF_ATTR));
         Document doc = appenderRef.getOwnerDocument();
         return findAppenderByName(doc, appenderName);
@@ -337,7 +340,7 @@ public class XmlConfiguration extends Log4j1Configuration {
     /**
      * Used internally to parse an appender element.
      */
-    private Appender parseAppender(Element appenderElement) {
+    public Appender parseAppender(Element appenderElement) {
         String className = subst(appenderElement.getAttribute(CLASS_ATTR));
         LOGGER.debug("Class name: [" + className + ']');
         Appender appender = manager.parseAppender(className, appenderElement, this);
diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/AsyncAppenderTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AsyncAppenderTest.java
new file mode 100644
index 0000000..bac1338
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AsyncAppenderTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.config;
+
+import org.apache.log4j.ListAppender;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.bridge.AppenderAdapter;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.spi.LoggerContextFactory;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test configuration from XML.
+ */
+public class AsyncAppenderTest {
+
+    @Test
+    public void testAsyncXml() throws Exception {
+        LoggerContext loggerContext = configure("target/test-classes/log4j1-async.xml");
+        Logger logger = LogManager.getLogger("test");
+        logger.debug("This is a test of the root logger");
+        Thread.sleep(50);
+        Configuration configuration = loggerContext.getConfiguration();
+        Map<String, Appender> appenders = configuration.getAppenders();
+        ListAppender messageAppender = null;
+        for (Map.Entry<String, Appender> entry : appenders.entrySet()) {
+            if (entry.getKey().equals("list")) {
+                messageAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender();
+            }
+        }
+        assertNotNull("No Message Appender", messageAppender);
+        List<String> messages = messageAppender.getMessages();
+        assertTrue("No messages", messages != null && messages.size() > 0);
+    }
+
+    @Test
+    public void testAsyncProperties() throws Exception {
+        LoggerContext loggerContext = configure("target/test-classes/log4j1-async.properties");
+        Logger logger = LogManager.getLogger("test");
+        logger.debug("This is a test of the root logger");
+        Thread.sleep(50);
+        Configuration configuration = loggerContext.getConfiguration();
+        Map<String, Appender> appenders = configuration.getAppenders();
+        ListAppender messageAppender = null;
+        for (Map.Entry<String, Appender> entry : appenders.entrySet()) {
+            if (entry.getKey().equals("list")) {
+                messageAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender();
+            }
+        }
+        assertNotNull("No Message Appender", messageAppender);
+        List<String> messages = messageAppender.getMessages();
+        assertTrue("No messages", messages != null && messages.size() > 0);
+    }
+
+
+    private LoggerContext configure(String configLocation) throws Exception {
+        File file = new File(configLocation);
+        InputStream is = new FileInputStream(file);
+        ConfigurationSource source = new ConfigurationSource(is, file);
+        LoggerContextFactory factory = org.apache.logging.log4j.LogManager.getFactory();
+        LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);
+        Configuration configuration;
+        if (configLocation.endsWith(".xml")) {
+            configuration = new XmlConfigurationFactory().getConfiguration(context, source);
+        } else {
+            configuration = new PropertiesConfigurationFactory().getConfiguration(context, source);
+        }
+        assertNotNull("No configuration created", configuration);
+        Configurator.reconfigure(configuration);
+        return context;
+    }
+
+}
diff --git a/log4j-1.2-api/src/test/resources/log4j1-async.properties b/log4j-1.2-api/src/test/resources/log4j1-async.properties
new file mode 100644
index 0000000..8e80b46
--- /dev/null
+++ b/log4j-1.2-api/src/test/resources/log4j1-async.properties
@@ -0,0 +1,21 @@
+# 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.
+
+log4j.appender.list=org.apache.log4j.ListAppender
+log4j.appender.list.layout=org.apache.log4j.PatternLayout
+log4j.appender.list.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
+log4j.appender.async=org.apache.log4j.AsyncAppender
+log4j.appender.async.appender-ref=list
+log4j.rootLogger=trace, async
\ No newline at end of file
diff --git a/log4j-1.2-api/src/test/resources/log4j1-async.xml b/log4j-1.2-api/src/test/resources/log4j1-async.xml
new file mode 100644
index 0000000..a0cb7f6
--- /dev/null
+++ b/log4j-1.2-api/src/test/resources/log4j1-async.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  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.
+-->
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+  <appender name="list" class="org.apache.log4j.ListAppender">
+    <layout class="org.apache.log4j.PatternLayout">
+      <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" />
+    </layout>
+  </appender>
+  <appender name="async" class="org.apache.log4j.AsyncAppender">
+    <appender-ref ref="list"/>
+  </appender>
+
+  <root>
+    <priority value ="trace" />
+    <appender-ref ref="async" />
+  </root>
+
+</log4j:configuration>
\ No newline at end of file