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


The following commit(s) were added to refs/heads/release-2.x by this push:
     new d87a307  Issues with multiple Log4j 1.x filters (#753)
d87a307 is described below

commit d87a3073af1e87281e961c4bd5a9b733aa912488
Author: ppkarwasz <[email protected]>
AuthorDate: Sat Feb 19 17:05:20 2022 +0100

    Issues with multiple Log4j 1.x filters (#753)
    
    * Fix issues with multiple Log4j 1.x filters
    
    This PR fixes a couple of issues concerning filters in the Log4j 1.x
    bridge:
    
     - there is an endless loop in `FilterAdapter#filter` that hangs the
    program if more than one filter is configured,
     - filters are executed multiple times per log message.
    
    The second problem comes from chains of Log4j 1.x filters: a filter
    chain is split into a `CompositeFilter` of `FilterAdapter`s (one for
    each filter). Each `FilterAdapter` executes its own filter and all those
    that come later in the chain.
    
    In order to preserve the behavior of `FilterAdapter` the following
    restrictions have been applied to the generated filters:
    
     1. no chains of Log4j 1.x filters are generated by the configuration; a
    list of Log4j 1.x filters is represented as a wrapped `CompositeFilter`
    of `FilterAdapter`s,
     2. the factories don't generate any `FilterWrapper`s of
    `FilterAdapter`s nor `FilterAdapter`s of `FilterWrapper`s.
    
    * Import and whitespace fixes
    
    * More unused import fixes
    
    * Adds regression test for CompositeFilter
---
 .../java/org/apache/log4j/AppenderSkeleton.java    |   5 +-
 .../org/apache/log4j/PropertyConfigurator.java     |   5 +-
 .../org/apache/log4j/bridge/AppenderAdapter.java   |  16 +--
 .../org/apache/log4j/bridge/AppenderWrapper.java   |   6 +-
 .../org/apache/log4j/bridge/FilterAdapter.java     |  43 ++++++-
 .../org/apache/log4j/builders/AbstractBuilder.java |  31 ++----
 .../builders/appender/AsyncAppenderBuilder.java    |  20 +++-
 .../builders/appender/ConsoleAppenderBuilder.java  |  16 +--
 .../appender/DailyRollingFileAppenderBuilder.java  |   2 +-
 .../builders/appender/FileAppenderBuilder.java     |   2 +-
 .../builders/appender/RewriteAppenderBuilder.java  |   2 +-
 .../appender/RollingFileAppenderBuilder.java       |   2 +-
 .../builders/appender/SocketAppenderBuilder.java   |  16 +--
 .../builders/appender/SyslogAppenderBuilder.java   |   2 +-
 .../log4j/config/PropertiesConfiguration.java      |  11 +-
 .../src/main/java/org/apache/log4j/spi/Filter.java |  12 --
 .../org/apache/log4j/xml/XmlConfiguration.java     |  55 +++++++--
 .../log4j/builders/Log4j2ListAppenderBuilder.java  |  98 ++++++++++++++++
 .../config/AbstractLog4j1ConfigurationTest.java    | 124 ++++++++++++++++++++-
 .../log4j/config/PropertiesConfigurationTest.java  |  12 +-
 .../org/apache/log4j/config/StartsWithFilter.java  |  38 +++++++
 .../apache/log4j/config/XmlConfigurationTest.java  |   6 +
 .../resources/log4j-multipleFilters.properties     |  69 ++++++++++++
 .../src/test/resources/log4j-multipleFilters.xml   |  92 +++++++++++++++
 .../logging/log4j/core/filter/CompositeFilter.java |   4 +-
 .../log4j/core/filter/CompositeFilterTest.java     |  49 ++++++++
 26 files changed, 613 insertions(+), 125 deletions(-)

diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/AppenderSkeleton.java 
b/log4j-1.2-api/src/main/java/org/apache/log4j/AppenderSkeleton.java
index 5bdabc0..7fbd92a 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/AppenderSkeleton.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/AppenderSkeleton.java
@@ -106,12 +106,9 @@ public abstract class AppenderSkeleton implements 
Appender, OptionHandler {
         return ((threshold == null) || priority.isGreaterOrEqual(threshold));
     }
 
-    /**
-     * This method is never going to be called in Log4j 2 so there isn't much 
point in having any code in it.
-     * @param event The LoggingEvent.
-     */
     @Override
     public void doAppend(final LoggingEvent event) {
+        // Threshold checks and filtering is performed by the AppenderWrapper.
         append(event);
     }
 
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 d522d57..483096e 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
@@ -30,6 +30,7 @@ import java.util.Properties;
 import java.util.StringTokenizer;
 import java.util.Vector;
 
+import org.apache.log4j.bridge.FilterAdapter;
 import org.apache.log4j.config.PropertiesConfiguration;
 import org.apache.log4j.config.PropertySetter;
 import org.apache.log4j.helpers.FileWatchdog;
@@ -520,6 +521,7 @@ public class PropertyConfigurator implements Configurator {
         // sort filters by IDs, insantiate filters, set filter options,
         // add filters to the appender
         final Enumeration g = new SortedKeyEnumeration(filters);
+        Filter head = null;
         while (g.hasMoreElements()) {
             final String key = (String) g.nextElement();
             final String clazz = properties.getProperty(key);
@@ -536,12 +538,13 @@ public class PropertyConfigurator implements Configurator 
{
                     }
                     propSetter.activate();
                     LogLog.debug("Adding filter of type [" + filter.getClass() 
+ "] to appender named [" + appender.getName() + "].");
-                    appender.addFilter(filter);
+                    head = FilterAdapter.addFilter(head, filter);
                 }
             } else {
                 LogLog.warn("Missing class definition for filter: [" + key + 
"]");
             }
         }
+        appender.addFilter(head);
     }
 
     /**
diff --git 
a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderAdapter.java 
b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderAdapter.java
index b2163c6..3334535 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderAdapter.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderAdapter.java
@@ -26,7 +26,6 @@ import org.apache.logging.log4j.core.Layout;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.appender.AbstractAppender;
 import org.apache.logging.log4j.core.config.Property;
-import org.apache.logging.log4j.core.filter.CompositeFilter;
 
 /**
  * Binds a Log4j 1.x Appender to Log4j 2.
@@ -42,20 +41,7 @@ public class AppenderAdapter {
      */
     public AppenderAdapter(Appender appender) {
         this.appender = appender;
-        org.apache.logging.log4j.core.Filter appenderFilter = null;
-        if (appender.getFilter() != null) {
-            if (appender.getFilter().getNext() != null) {
-                org.apache.log4j.spi.Filter filter = appender.getFilter();
-                List<org.apache.logging.log4j.core.Filter> filters = new 
ArrayList<>();
-                while (filter != null) {
-                    filters.add(new FilterAdapter(filter));
-                    filter = filter.getNext();
-                }
-                appenderFilter = 
CompositeFilter.createFilters(filters.toArray(Filter.EMPTY_ARRAY));
-            } else {
-                appenderFilter = new FilterAdapter(appender.getFilter());
-            }
-        }
+        final org.apache.logging.log4j.core.Filter appenderFilter = 
FilterAdapter.convertFilter(appender.getFilter());
         this.adapter = new Adapter(appender.getName(), appenderFilter, null, 
true, null);
     }
 
diff --git 
a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderWrapper.java 
b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderWrapper.java
index d26c878..3d28fd7 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderWrapper.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderWrapper.java
@@ -55,11 +55,7 @@ public class AppenderWrapper implements Appender {
     @Override
     public void addFilter(Filter newFilter) {
         if (appender instanceof AbstractFilterable) {
-            if (newFilter instanceof FilterWrapper) {
-                ((AbstractFilterable) appender).addFilter(((FilterWrapper) 
newFilter).getFilter());
-            } else {
-                ((AbstractFilterable) appender).addFilter(new 
FilterAdapter(newFilter));
-            }
+            ((AbstractFilterable) 
appender).addFilter(FilterAdapter.convertFilter(newFilter));
         } else {
             LOGGER.warn("Unable to add filter to appender {}, it does not 
support filters", appender.getName());
         }
diff --git 
a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/FilterAdapter.java 
b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/FilterAdapter.java
index adab346..e10478c 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/FilterAdapter.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/FilterAdapter.java
@@ -20,6 +20,7 @@ import org.apache.log4j.spi.Filter;
 import org.apache.log4j.spi.LoggingEvent;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.filter.AbstractFilter;
+import org.apache.logging.log4j.core.filter.CompositeFilter;
 
 /**
  * Binds a Log4j 1.x Filter with Log4j 2.
@@ -28,6 +29,44 @@ public class FilterAdapter extends AbstractFilter {
 
     private final Filter filter;
 
+    /**
+     * Converts a Log4j 1.x filter into a Log4j 2.x filter.
+     * 
+     * @param filter
+     *            a Log4j 1.x filter
+     * @return a Log4j 2.x filter
+     */
+    public static org.apache.logging.log4j.core.Filter convertFilter(Filter 
filter) {
+        if (filter instanceof FilterWrapper && filter.getNext() == null) {
+            return ((FilterWrapper) filter).getFilter();
+        } else if (filter != null) {
+            return new FilterAdapter(filter);
+        }
+        return null;
+    }
+
+    /**
+     * Appends one filter to another using Log4j 2.x concatenation utilities.
+     * @param first
+     * @param second
+     * @return
+     */
+    public static Filter addFilter(Filter first, Filter second) {
+        if (first == null) {
+            return second;
+        }
+        if (second == null) {
+            return first;
+        }
+        final CompositeFilter composite;
+        if (first instanceof FilterWrapper && ((FilterWrapper) 
first).getFilter() instanceof CompositeFilter) {
+            composite = (CompositeFilter) ((FilterWrapper) first).getFilter();
+        } else {
+            composite = CompositeFilter.createFilters(new 
org.apache.logging.log4j.core.Filter[] {convertFilter(first)});
+        }
+        return new FilterWrapper(composite.addFilter(convertFilter(second)));
+    }
+
     public FilterAdapter(Filter filter) {
         this.filter = filter;
     }
@@ -37,14 +76,14 @@ public class FilterAdapter extends AbstractFilter {
         LoggingEvent loggingEvent = new LogEventAdapter(event);
         Filter next = filter;
         while (next != null) {
-            switch (filter.decide(loggingEvent)) {
+            switch (next.decide(loggingEvent)) {
                 case Filter.ACCEPT:
                     return Result.ACCEPT;
                 case Filter.DENY:
                     return Result.DENY;
                 default:
             }
-            next = filter.getNext();
+            next = next.getNext();
         }
         return Result.NEUTRAL;
     }
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 6ed0304..cefb2bd 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
@@ -74,32 +74,17 @@ public abstract class AbstractBuilder implements Builder {
         props.entrySet().forEach(e -> 
this.properties.put(toBeanKey(e.getKey().toString()), e.getValue()));
     }
 
-    protected org.apache.logging.log4j.core.Filter buildFilters(final String 
level, final Filter filter) {
-        if (level != null && filter != null) {
-            final List<org.apache.logging.log4j.core.Filter> filterList = new 
ArrayList<>();
+    protected static org.apache.logging.log4j.core.Filter buildFilters(final 
String level, final Filter filter) {
+        Filter head = null;
+        if (level != null) {
             final 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.getNext();
-            }
-            return 
CompositeFilter.createFilters(filterList.toArray(org.apache.logging.log4j.core.Filter.EMPTY_ARRAY));
-        } 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();
-            }
-            return new FilterAdapter(filter);
+            head = new FilterWrapper(thresholdFilter);
+        }
+        if (filter != null) {
+            head = FilterAdapter.addFilter(head, filter);
         }
-        return null;
+        return FilterAdapter.convertFilter(head);
     }
 
     private String capitalize(final String value) {
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
index d70a55d..87f602c 100644
--- 
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
@@ -19,6 +19,7 @@ package org.apache.log4j.builders.appender;
 import static org.apache.log4j.builders.BuilderManager.CATEGORY;
 import static org.apache.log4j.config.Log4j1Configuration.APPENDER_REF_TAG;
 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.PARAM_TAG;
 import static org.apache.log4j.xml.XmlConfiguration.forEachElement;
 
@@ -31,13 +32,16 @@ import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.log4j.Appender;
 import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.bridge.FilterAdapter;
 import org.apache.log4j.builders.AbstractBuilder;
 import org.apache.log4j.config.Log4j1Configuration;
 import org.apache.log4j.config.PropertiesConfiguration;
 import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.spi.Filter;
 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.appender.AsyncAppender.Builder;
 import org.apache.logging.log4j.core.config.AppenderRef;
 import org.apache.logging.log4j.core.config.plugins.Plugin;
 import org.apache.logging.log4j.status.StatusLogger;
@@ -70,6 +74,7 @@ public class AsyncAppenderBuilder extends AbstractBuilder 
implements AppenderBui
         final AtomicBoolean includeLocation = new AtomicBoolean();
         final AtomicReference<String> level = new AtomicReference<>("trace");
         final AtomicInteger bufferSize = new AtomicInteger(1024);
+        final AtomicReference<Filter> filter = new AtomicReference<>();
         forEachElement(appenderElement.getChildNodes(), currentElement -> {
             switch (currentElement.getTagName()) {
                 case APPENDER_REF_TAG:
@@ -78,6 +83,9 @@ public class AsyncAppenderBuilder extends AbstractBuilder 
implements AppenderBui
                         appenderRefs.get().add(appender.getName());
                     }
                     break;
+                case FILTER_TAG:
+                    config.addFilter(filter, currentElement);
+                    break;
                 case PARAM_TAG: {
                     switch (getNameAttributeKey(currentElement)) {
                         case BUFFER_SIZE_PARAM:
@@ -98,13 +106,14 @@ public class AsyncAppenderBuilder extends AbstractBuilder 
implements AppenderBui
             }
         });
         return createAppender(name, level.get(), 
appenderRefs.get().toArray(Strings.EMPTY_ARRAY), blocking.get(),
-                bufferSize.get(), includeLocation.get(), config);
+                bufferSize.get(), includeLocation.get(), filter.get(), config);
     }
 
     @Override
     public Appender parseAppender(final String name, final String 
appenderPrefix, final String layoutPrefix,
             final String filterPrefix, final Properties props, final 
PropertiesConfiguration configuration) {
         final String appenderRef = getProperty(APPENDER_REF_TAG);
+        final Filter filter = configuration.parseAppenderFilters(props, 
filterPrefix, name);
         final boolean blocking = getBooleanProperty(BLOCKING_PARAM);
         final boolean includeLocation = 
getBooleanProperty(INCLUDE_LOCATION_PARAM);
         final String level = getProperty(THRESHOLD_PARAM);
@@ -118,13 +127,13 @@ public class AsyncAppenderBuilder extends AbstractBuilder 
implements AppenderBui
             LOGGER.warn("Cannot locate Appender {}", appenderRef);
             return null;
         }
-        return createAppender(name, level, new String[] {appenderRef}, 
blocking, bufferSize, includeLocation,
+        return createAppender(name, level, new String[]{appenderRef}, 
blocking, bufferSize, includeLocation, filter,
                 configuration);
     }
 
     private <T extends Log4j1Configuration> Appender createAppender(final 
String name, final String level,
             final String[] appenderRefs, final boolean blocking, final int 
bufferSize, final boolean includeLocation,
-            final T configuration) {
+            final Filter filter, final T configuration) {
         final org.apache.logging.log4j.Level logLevel = 
OptionConverter.convertLevel(level,
                 org.apache.logging.log4j.Level.TRACE);
         final AppenderRef[] refs = new AppenderRef[appenderRefs.length];
@@ -132,8 +141,9 @@ public class AsyncAppenderBuilder extends AbstractBuilder 
implements AppenderBui
         for (final String appenderRef : appenderRefs) {
             refs[index++] = AppenderRef.createAppenderRef(appenderRef, 
logLevel, null);
         }
-        return new AppenderWrapper(AsyncAppender.newBuilder()
-                .setName(name)
+        Builder builder = AsyncAppender.newBuilder();
+        builder.setFilter(FilterAdapter.convertFilter(filter));
+        return new AppenderWrapper(builder.setName(name)
                 .setAppenderRefs(refs)
                 .setBlocking(blocking)
                 .setBufferSize(bufferSize)
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 0abee05..e745416 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
@@ -70,7 +70,7 @@ public class ConsoleAppenderBuilder extends AbstractBuilder 
implements AppenderB
         final String name = getNameAttribute(appenderElement);
         final AtomicReference<String> target = new 
AtomicReference<>(SYSTEM_OUT);
         final AtomicReference<Layout> layout = new AtomicReference<>();
-        final AtomicReference<List<Filter>> filters = new 
AtomicReference<>(new ArrayList<>());
+        final AtomicReference<Filter> filter = new AtomicReference<>();
         final AtomicReference<String> level = new AtomicReference<>();
         final AtomicBoolean follow = new AtomicBoolean();
         final AtomicBoolean immediateFlush = new AtomicBoolean(true);
@@ -80,7 +80,7 @@ public class ConsoleAppenderBuilder extends AbstractBuilder 
implements AppenderB
                     layout.set(config.parseLayout(currentElement));
                     break;
                 case FILTER_TAG:
-                    filters.get().add(config.parseFilters(currentElement));
+                    config.addFilter(filter, currentElement);
                     break;
                 case PARAM_TAG: {
                     switch (getNameAttributeKey(currentElement)) {
@@ -115,17 +115,7 @@ public class ConsoleAppenderBuilder extends 
AbstractBuilder implements AppenderB
                 }
             }
         });
-        Filter head = null;
-        Filter current = null;
-        for (final Filter f : filters.get()) {
-            if (head == null) {
-                head = f;
-            } else {
-                current.next = f;
-            }
-            current = f;
-        }
-        return createAppender(name, layout.get(), head, level.get(), 
target.get(), immediateFlush.get(), follow.get(), config);
+        return createAppender(name, layout.get(), filter.get(), level.get(), 
target.get(), immediateFlush.get(), follow.get(), config);
     }
 
     @Override
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 ae43088..7c0575b 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
@@ -85,7 +85,7 @@ public class DailyRollingFileAppenderBuilder extends 
AbstractBuilder implements
                     layout.set(config.parseLayout(currentElement));
                     break;
                 case FILTER_TAG:
-                    filter.set(config.parseFilters(currentElement));
+                    config.addFilter(filter, currentElement);
                     break;
                 case PARAM_TAG:
                     switch (getNameAttributeKey(currentElement)) {
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 8903cb6..e461324 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
@@ -76,7 +76,7 @@ public class FileAppenderBuilder extends AbstractBuilder 
implements AppenderBuil
                     layout.set(config.parseLayout(currentElement));
                     break;
                 case FILTER_TAG:
-                    filter.set(config.parseFilters(currentElement));
+                    config.addFilter(filter, currentElement);
                     break;
                 case PARAM_TAG:
                     switch (getNameAttributeKey(currentElement)) {
diff --git 
a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RewriteAppenderBuilder.java
 
b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RewriteAppenderBuilder.java
index 0cc8557..28102be 100644
--- 
a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RewriteAppenderBuilder.java
+++ 
b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RewriteAppenderBuilder.java
@@ -86,7 +86,7 @@ public class RewriteAppenderBuilder extends AbstractBuilder 
implements AppenderB
                     }
                     break;
                 case FILTER_TAG:
-                    filter.set(config.parseFilters(currentElement));
+                    config.addFilter(filter, currentElement);
                     break;
                 case PARAM_TAG:
                     if 
(getNameAttributeKey(currentElement).equalsIgnoreCase(THRESHOLD_PARAM)) {
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 604747e..4c168d9 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
@@ -85,7 +85,7 @@ public class RollingFileAppenderBuilder extends 
AbstractBuilder implements Appen
                     layout.set(config.parseLayout(currentElement));
                     break;
                 case FILTER_TAG:
-                    filter.set(config.parseFilters(currentElement));
+                    config.addFilter(filter, currentElement);
                     break;
                 case PARAM_TAG:
                     switch (getNameAttributeKey(currentElement)) {
diff --git 
a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SocketAppenderBuilder.java
 
b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SocketAppenderBuilder.java
index 82c74ae..a48a08a 100644
--- 
a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SocketAppenderBuilder.java
+++ 
b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SocketAppenderBuilder.java
@@ -101,7 +101,7 @@ public class SocketAppenderBuilder extends AbstractBuilder 
implements AppenderBu
         final AtomicInteger port = new AtomicInteger(DEFAULT_PORT);
         final AtomicInteger reconnectDelay = new 
AtomicInteger(DEFAULT_RECONNECTION_DELAY);
         final AtomicReference<Layout> layout = new AtomicReference<>();
-        final AtomicReference<List<Filter>> filters = new 
AtomicReference<>(new ArrayList<>());
+        final AtomicReference<Filter> filter = new AtomicReference<>();
         final AtomicReference<String> level = new AtomicReference<>();
         final AtomicBoolean immediateFlush = new AtomicBoolean(true);
         forEachElement(appenderElement.getChildNodes(), currentElement -> {
@@ -110,7 +110,7 @@ public class SocketAppenderBuilder extends AbstractBuilder 
implements AppenderBu
                 layout.set(config.parseLayout(currentElement));
                 break;
             case FILTER_TAG:
-                filters.get().add(config.parseFilters(currentElement));
+                config.addFilter(filter, currentElement);
                 break;
             case PARAM_TAG:
                 switch (getNameAttributeKey(currentElement)) {
@@ -133,17 +133,7 @@ public class SocketAppenderBuilder extends AbstractBuilder 
implements AppenderBu
                 break;
             }
         });
-        Filter head = null;
-        Filter current = null;
-        for (final Filter f : filters.get()) {
-            if (head == null) {
-                head = f;
-            } else {
-                current.next = f;
-            }
-            current = f;
-        }
-        return createAppender(name, host.get(), port.get(), layout.get(), 
head, level.get(), immediateFlush.get(), reconnectDelay.get(), config);
+        return createAppender(name, host.get(), port.get(), layout.get(), 
filter.get(), level.get(), immediateFlush.get(), reconnectDelay.get(), config);
     }
 
     @Override
diff --git 
a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java
 
b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java
index 83fe788..7d7947c 100644
--- 
a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java
+++ 
b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java
@@ -84,7 +84,7 @@ public class SyslogAppenderBuilder extends AbstractBuilder 
implements AppenderBu
                     layout.set(config.parseLayout(currentElement));
                     break;
                 case FILTER_TAG:
-                    filter.set(config.parseFilters(currentElement));
+                    config.addFilter(filter, currentElement);
                     break;
                 case PARAM_TAG:
                     switch (getNameAttributeKey(currentElement)) {
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 0b77118..06b8854 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
@@ -35,6 +35,7 @@ import org.apache.log4j.LogManager;
 import org.apache.log4j.PatternLayout;
 import org.apache.log4j.bridge.AppenderAdapter;
 import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.bridge.FilterAdapter;
 import org.apache.log4j.helpers.OptionConverter;
 import org.apache.log4j.spi.ErrorHandler;
 import org.apache.log4j.spi.Filter;
@@ -555,7 +556,6 @@ public class PropertiesConfiguration extends 
Log4j1Configuration {
         }
 
         Filter head = null;
-        Filter next = null;
         for (final Map.Entry<String, List<NameValue>> entry : 
filters.entrySet()) {
             final String clazz = props.getProperty(entry.getKey());
             Filter filter = null;
@@ -566,14 +566,7 @@ public class PropertiesConfiguration extends 
Log4j1Configuration {
                     filter = buildFilter(clazz, appenderName, 
entry.getValue());
                 }
             }
-            if (filter != null) {
-                if (head == null) {
-                    head = filter;
-                } else {
-                    next.setNext(filter);
-                }
-                next = filter;
-            }
+            head = FilterAdapter.addFilter(head, filter);
         }
         return head;
     }
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/Filter.java 
b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/Filter.java
index 17a15d6..91917ab 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/Filter.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/Filter.java
@@ -16,8 +16,6 @@
  */
 package org.apache.log4j.spi;
 
-import org.apache.log4j.bridge.FilterAdapter;
-
 /**
  * @since 0.9.0
  */
@@ -32,16 +30,6 @@ public abstract class Filter {
         }
         isCorePresent = temp;
     }
-    
-    // TODO Unused?
-    private final FilterAdapter adapter;
-
-    /**
-     * Constructs a new instance.
-     */
-    public Filter() {
-        this.adapter = isCorePresent ? new FilterAdapter(this) : null;
-    }
 
     /**
      * The log event must be dropped immediately without consulting
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 cdf4e14..3821188 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
@@ -22,6 +22,7 @@ import java.lang.reflect.Method;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 
 import javax.xml.parsers.DocumentBuilder;
@@ -33,6 +34,7 @@ import org.apache.log4j.Layout;
 import org.apache.log4j.Level;
 import org.apache.log4j.bridge.AppenderAdapter;
 import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.bridge.FilterAdapter;
 import org.apache.log4j.config.Log4j1Configuration;
 import org.apache.log4j.config.PropertySetter;
 import org.apache.log4j.helpers.OptionConverter;
@@ -371,6 +373,7 @@ public class XmlConfiguration extends Log4j1Configuration {
             PropertySetter propSetter = new PropertySetter(appender);
 
             appender.setName(subst(appenderElement.getAttribute(NAME_ATTR)));
+            final AtomicReference<Filter> filterChain = new 
AtomicReference<>();
             forEachElement(appenderElement.getChildNodes(), currentElement -> {
                 // Parse appender parameters
                 switch (currentElement.getTagName()) {
@@ -381,12 +384,7 @@ public class XmlConfiguration extends Log4j1Configuration {
                         appender.setLayout(parseLayout(currentElement));
                         break;
                     case FILTER_TAG:
-                        Filter filter = parseFilters(currentElement);
-                        if (filter != null) {
-                            LOGGER.debug("Adding filter of type [{}] to 
appender named [{}]",
-                                    filter.getClass(), appender.getName());
-                            appender.addFilter(filter);
-                        }
+                        addFilter(filterChain, currentElement);
                         break;
                     case ERROR_HANDLER_TAG:
                         parseErrorHandler(currentElement, appender);
@@ -413,6 +411,10 @@ public class XmlConfiguration extends Log4j1Configuration {
                         }
                 }
             });
+            final Filter head = filterChain.get();
+            if (head != null) {
+                appender.addFilter(head);
+            }
             propSetter.activate();
             return appender;
         } catch (ConsumerException ex) {
@@ -494,23 +496,52 @@ public class XmlConfiguration extends Log4j1Configuration 
{
     /**
      * Used internally to parse a filter element.
      */
+    public void addFilter(final AtomicReference<Filter> ref, final Element 
filterElement) {
+        final Filter value = parseFilters(filterElement);
+        ref.accumulateAndGet(value, FilterAdapter::addFilter);
+    }
+
+    /**
+     * Used internally to parse a filter element.
+     */
     public Filter parseFilters(Element filterElement) {
         String className = subst(filterElement.getAttribute(CLASS_ATTR));
         LOGGER.debug("Class name: [" + className + ']');
         Filter filter = manager.parseFilter(className, filterElement, this);
         if (filter == null) {
+            filter = buildFilter(className, filterElement);
+        }
+        return filter;
+    }
+
+    private Filter buildFilter(final String className, final Element 
filterElement) {
+        try {
+            Filter filter = LoaderUtil.newInstanceOf(className);
             PropertySetter propSetter = new PropertySetter(filter);
+
             forEachElement(filterElement.getChildNodes(), currentElement -> {
-                String tagName = currentElement.getTagName();
-                if (tagName.equals(PARAM_TAG)) {
-                    setParameter(currentElement, propSetter);
-                } else {
-                    quietParseUnrecognizedElement(filter, currentElement, 
props);
+                // Parse appender parameters
+                switch (currentElement.getTagName()) {
+                    case PARAM_TAG :
+                        setParameter(currentElement, propSetter);
+                        break;
                 }
             });
             propSetter.activate();
+            return filter;
+        } catch (ConsumerException ex) {
+            Throwable t = ex.getCause();
+            if (t instanceof InterruptedException || t instanceof 
InterruptedIOException) {
+                Thread.currentThread().interrupt();
+            }
+            LOGGER.error("Could not create an Filter. Reported error 
follows.", t);
+        } catch (Exception oops) {
+            if (oops instanceof InterruptedException || oops instanceof 
InterruptedIOException) {
+                Thread.currentThread().interrupt();
+            }
+            LOGGER.error("Could not create an Filter. Reported error 
follows.", oops);
         }
-        return filter;
+        return null;
     }
 
     /**
diff --git 
a/log4j-1.2-api/src/test/java/org/apache/log4j/builders/Log4j2ListAppenderBuilder.java
 
b/log4j-1.2-api/src/test/java/org/apache/log4j/builders/Log4j2ListAppenderBuilder.java
new file mode 100644
index 0000000..825a083
--- /dev/null
+++ 
b/log4j-1.2-api/src/test/java/org/apache/log4j/builders/Log4j2ListAppenderBuilder.java
@@ -0,0 +1,98 @@
+/*
+ * 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;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+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;
+import static org.apache.log4j.xml.XmlConfiguration.forEachElement;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.bridge.LayoutAdapter;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.appender.AppenderBuilder;
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.w3c.dom.Element;
+
+/**
+ * Builder for the native Log4j 2.x list appender to be used in the tests.
+ */
+@Plugin(name = "org.apache.logging.log4j.test.appender.ListAppender", category 
= CATEGORY)
+public class Log4j2ListAppenderBuilder extends AbstractBuilder implements 
AppenderBuilder {
+
+    public Log4j2ListAppenderBuilder() {
+    }
+
+    public Log4j2ListAppenderBuilder(final String prefix, final Properties 
props) {
+        super(prefix, props);
+    }
+
+    @Override
+    public Appender parseAppender(Element element, XmlConfiguration 
configuration) {
+        final String name = getNameAttribute(element);
+        final AtomicReference<Layout> layout = new AtomicReference<>();
+        final AtomicReference<Filter> filter = new AtomicReference<>();
+        forEachElement(element.getChildNodes(), currentElement -> {
+            switch (currentElement.getTagName()) {
+                case LAYOUT_TAG :
+                    layout.set(configuration.parseLayout(currentElement));
+                    break;
+                case FILTER_TAG :
+                    configuration.addFilter(filter, currentElement);
+                    break;
+                default :
+            }
+        });
+        return createAppender(name, layout.get(), filter.get());
+    }
+
+    @Override
+    public Appender parseAppender(String name, String appenderPrefix, String 
layoutPrefix, String filterPrefix,
+            Properties props, PropertiesConfiguration configuration) {
+        final Layout layout = configuration.parseLayout(layoutPrefix, name, 
props);
+        final Filter filter = configuration.parseAppenderFilters(props, 
filterPrefix, name);
+        return createAppender(name, layout, filter);
+    }
+
+    private Appender createAppender(String name, Layout layout, Filter filter) 
{
+        final org.apache.logging.log4j.core.Layout<?> log4j2Layout;
+        if (layout instanceof LayoutWrapper) {
+            log4j2Layout = ((LayoutWrapper) layout).getLayout();
+        } else {
+            log4j2Layout = layout != null ? new LayoutAdapter(layout) : null;
+        }
+        return new AppenderWrapper(
+                ListAppender.newBuilder()
+                        .setName(name)
+                        .setLayout(log4j2Layout)
+                        .setFilter(AbstractBuilder.buildFilters(null, filter))
+                        .build());
+    }
+}
diff --git 
a/log4j-1.2-api/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationTest.java
 
b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationTest.java
index bb3e0e3..675c287 100644
--- 
a/log4j-1.2-api/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationTest.java
+++ 
b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationTest.java
@@ -17,7 +17,9 @@
 package org.apache.log4j.config;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
@@ -33,9 +35,16 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.log4j.ListAppender;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.bridge.FilterAdapter;
+import org.apache.log4j.bridge.FilterWrapper;
+import org.apache.log4j.bridge.AppenderAdapter.Adapter;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.core.Appender;
 import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.appender.ConsoleAppender;
 import org.apache.logging.log4j.core.appender.ConsoleAppender.Target;
 import org.apache.logging.log4j.core.appender.FileAppender;
@@ -49,15 +58,28 @@ import 
org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy;
 import 
org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy;
 import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
 import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.Configurator;
 import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.logging.log4j.core.filter.CompositeFilter;
+import org.apache.logging.log4j.core.filter.Filterable;
 import org.apache.logging.log4j.core.layout.HtmlLayout;
 import org.apache.logging.log4j.core.layout.PatternLayout;
 import org.apache.logging.log4j.core.util.CloseShieldOutputStream;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
 
 public abstract class AbstractLog4j1ConfigurationTest {
 
+    @Rule
+    public TemporaryFolder tempFolder = 
TemporaryFolder.builder().assureDeletion().build();
+
     abstract Configuration getConfiguration(String configResourcePrefix) 
throws URISyntaxException, IOException;
 
+    private LoggerContext configure(String configResourcePrefix) throws 
URISyntaxException, IOException {
+        Configurator.reconfigure(getConfiguration(configResourcePrefix));
+        return (LoggerContext) 
org.apache.logging.log4j.LogManager.getContext(false);
+    }
+
     public void testConsoleCapitalization() throws Exception {
         final Configuration config = 
getConfiguration("config-1.2/log4j-capitalization");
         final Appender capitalized = config.getAppender("ConsoleCapitalized");
@@ -307,7 +329,7 @@ public abstract class AbstractLog4j1ConfigurationTest {
             final Field appendField = 
FileOutputStream.class.getDeclaredField("append");
             appendField.setAccessible(true);
             return (Boolean) appendField.get(os);
-        } catch (NoSuchFieldError e) {
+        } catch (NoSuchFieldError | NoSuchFieldException e) {
             // Java 11
             final Field appendField = 
FileDescriptor.class.getDeclaredField("append");
             appendField.setAccessible(true);
@@ -385,4 +407,104 @@ public abstract class AbstractLog4j1ConfigurationTest {
         config.start();
         config.stop();
     }
+
+    /**
+     * Checks a hierarchy of filters.
+     * 
+     * @param filter
+     * @return the number of filters
+     */
+    private int checkFilters(final org.apache.logging.log4j.core.Filter 
filter) {
+        int count = 0;
+        if (filter instanceof CompositeFilter) {
+            for (final org.apache.logging.log4j.core.Filter part : 
((CompositeFilter) filter).getFiltersArray()) {
+                count += checkFilters(part);
+            }
+        } else if (filter instanceof FilterAdapter) {
+            // Don't create adapters from wrappers
+            assertFalse("found FilterAdapter of a FilterWrapper", 
((FilterAdapter) filter).getFilter() instanceof FilterWrapper);
+            count += checkFilters(((FilterAdapter) filter).getFilter());
+        } else {
+            count++;
+        }
+        return count;
+    }
+
+    /**
+     * Checks a hierarchy of filters.
+     * 
+     * @param filter
+     * @return the number of filters
+     */
+    private int checkFilters(final org.apache.log4j.spi.Filter filter) {
+        int count = 0;
+        if (filter instanceof FilterWrapper) {
+            // Don't create wrappers from adapters
+            assertFalse("found FilterWrapper of a FilterAdapter", 
((FilterWrapper) filter).getFilter() instanceof FilterAdapter);
+            count += checkFilters(((FilterWrapper) filter).getFilter());
+        } else {
+            count++;
+        }
+        // We prefer a:
+        // CompositeFilter of native Log4j 2.x filters
+        // over a:
+        // FilterAdapter of a chain of FilterWrappers.
+        assertNull("found chain of Log4j 1.x filters", filter.getNext());
+        return count;
+    }
+
+    public void testMultipleFilters() throws Exception {
+        final File folder = tempFolder.newFolder();
+        System.setProperty("test.tmpDir", folder.getCanonicalPath());
+        try (LoggerContext loggerContext = configure("log4j-multipleFilters")) 
{
+            final Configuration configuration = 
loggerContext.getConfiguration();
+            assertNotNull(configuration);
+            // Check only number of filters.
+            final Filterable console = configuration.getAppender("CONSOLE");
+            assertNotNull(console);
+            assertEquals(4, checkFilters(console.getFilter()));
+            final Filterable file = configuration.getAppender("FILE");
+            assertNotNull(file);
+            assertEquals(4, checkFilters(file.getFilter()));
+            final Filterable rfa = configuration.getAppender("RFA");
+            assertNotNull(rfa);
+            assertEquals(4, checkFilters(rfa.getFilter()));
+            final Filterable drfa = configuration.getAppender("DRFA");
+            assertNotNull(drfa);
+            assertEquals(4, checkFilters(drfa.getFilter()));
+            // List appenders
+            final Appender appender = configuration.getAppender("LIST");
+            assertNotNull(appender);
+            assertEquals(3, checkFilters(((Filterable)appender).getFilter()));
+            final ListAppender legacyAppender = (ListAppender) ((Adapter) 
appender).getAppender();
+            final org.apache.logging.log4j.test.appender.ListAppender 
nativeAppender = configuration.getAppender("LIST2");
+            assertEquals(3, 
checkFilters(((Filterable)nativeAppender).getFilter()));
+            final Logger logger = 
LogManager.getLogger(PropertiesConfigurationTest.class);
+            int expected = 0;
+            // message blocked by Threshold
+            logger.trace("NEUTRAL message");
+            assertEquals(expected, legacyAppender.getEvents().size());
+            assertEquals(expected, nativeAppender.getEvents().size());
+            // message blocked by DenyAll filter
+            logger.warn("NEUTRAL message");
+            assertEquals(expected, legacyAppender.getEvents().size());
+            assertEquals(expected, nativeAppender.getEvents().size());
+            // message accepted by level filter
+            logger.info("NEUTRAL message");
+            expected++;
+            assertEquals(expected, legacyAppender.getEvents().size());
+            assertEquals(expected, nativeAppender.getEvents().size());
+            // message accepted by "StartsWith" filter
+            logger.warn("ACCEPT message");
+            expected++;
+            assertEquals(expected, legacyAppender.getEvents().size());
+            assertEquals(expected, nativeAppender.getEvents().size());
+            // message blocked by "StartsWith" filter
+            logger.info("DENY message");
+            assertEquals(expected, legacyAppender.getEvents().size());
+            assertEquals(expected, nativeAppender.getEvents().size());
+        } finally {
+            System.clearProperty("test.tmpDir");
+        }
+    }
 }
diff --git 
a/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationTest.java
 
b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationTest.java
index ad8b8d1..06288f7 100644
--- 
a/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationTest.java
+++ 
b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationTest.java
@@ -32,7 +32,6 @@ import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
 import org.apache.log4j.bridge.AppenderAdapter;
 import org.apache.log4j.bridge.FilterAdapter;
-import org.apache.log4j.bridge.FilterWrapper;
 import org.apache.log4j.spi.LoggingEvent;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.core.Appender;
@@ -118,9 +117,9 @@ public class PropertiesConfigurationTest extends 
AbstractLog4j1ConfigurationTest
             final Filterable filterable = (Filterable) appender;
             final CompositeFilter filter = (CompositeFilter) 
filterable.getFilter();
             final org.apache.logging.log4j.core.Filter[] filters = 
filter.getFiltersArray();
-            final LevelRangeFilter customFilterReal = (LevelRangeFilter) 
((FilterWrapper) ((FilterAdapter) filters[0]).getFilter()).getFilter();
+            final LevelRangeFilter customFilterReal = (LevelRangeFilter) 
filters[0];
             assertEquals(Level.ALL, customFilterReal.getMinLevel());
-            final LevelRangeFilter defaultFilter = (LevelRangeFilter) 
((FilterWrapper) ((FilterAdapter) filters[1]).getFilter()).getFilter();
+            final LevelRangeFilter defaultFilter = (LevelRangeFilter) 
filters[1];
             assertEquals(Level.TRACE, defaultFilter.getMinLevel());
       }
     }
@@ -184,6 +183,7 @@ public class PropertiesConfigurationTest extends 
AbstractLog4j1ConfigurationTest
         }
     }
 
+
     @Override
     @Test
     public void testConsoleEnhancedPatternLayout() throws Exception {
@@ -267,4 +267,10 @@ public class PropertiesConfigurationTest extends 
AbstractLog4j1ConfigurationTest
     public void testDefaultValues() throws Exception {
         super.testDefaultValues();
     }
+
+    @Override
+    @Test
+    public void testMultipleFilters() throws Exception {
+        super.testMultipleFilters();
+    }
 }
diff --git 
a/log4j-1.2-api/src/test/java/org/apache/log4j/config/StartsWithFilter.java 
b/log4j-1.2-api/src/test/java/org/apache/log4j/config/StartsWithFilter.java
new file mode 100644
index 0000000..db16d84
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/StartsWithFilter.java
@@ -0,0 +1,38 @@
+/*
+ * 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.spi.Filter;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * A Filter used in tests.
+ */
+public class StartsWithFilter extends Filter {
+
+    @Override
+    public int decide(LoggingEvent event) {
+        String message = String.valueOf(event.getMessage());
+        if (message.startsWith("DENY")) {
+            return DENY;
+        } else if (message.startsWith("ACCEPT")) {
+            return ACCEPT;
+        }
+        return NEUTRAL;
+    }
+
+}
diff --git 
a/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationTest.java 
b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationTest.java
index d5d07cf..219880e 100644
--- 
a/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationTest.java
+++ 
b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationTest.java
@@ -166,4 +166,10 @@ public class XmlConfigurationTest extends 
AbstractLog4j1ConfigurationTest {
         super.testDefaultValues();
     }
 
+    @Override
+    @Test
+    public void testMultipleFilters() throws Exception {
+        super.testMultipleFilters();
+    }
+
 }
diff --git a/log4j-1.2-api/src/test/resources/log4j-multipleFilters.properties 
b/log4j-1.2-api/src/test/resources/log4j-multipleFilters.properties
new file mode 100644
index 0000000..177d60c
--- /dev/null
+++ b/log4j-1.2-api/src/test/resources/log4j-multipleFilters.properties
@@ -0,0 +1,69 @@
+#
+# 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.Threshold=DEBUG
+log4j.appender.LIST.filter.1=org.apache.log4j.config.StartsWithFilter
+log4j.appender.LIST.filter.2=org.apache.log4j.varia.LevelMatchFilter
+log4j.appender.LIST.filter.2.LevelToMatch=INFO
+log4j.appender.LIST.filter.2.AcceptOnMatch=true
+log4j.appender.LIST.filter.3=org.apache.log4j.varia.DenyAllFilter
+
+log4j.appender.LIST2=org.apache.logging.log4j.test.appender.ListAppender
+log4j.appender.LIST2.Threshold=DEBUG
+log4j.appender.LIST2.filter.1=org.apache.log4j.config.StartsWithFilter
+log4j.appender.LIST2.filter.2=org.apache.log4j.varia.LevelMatchFilter
+log4j.appender.LIST2.filter.2.LevelToMatch=INFO
+log4j.appender.LIST2.filter.2.AcceptOnMatch=true
+log4j.appender.LIST2.filter.3=org.apache.log4j.varia.DenyAllFilter
+
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+log4j.appender.CONSOLE.Threshold=DEBUG
+log4j.appender.CONSOLE.filter.1=org.apache.log4j.config.StartsWithFilter
+log4j.appender.CONSOLE.filter.2=org.apache.log4j.varia.LevelMatchFilter
+log4j.appender.CONSOLE.filter.2.LevelToMatch=INFO
+log4j.appender.CONSOLE.filter.2.AcceptOnMatch=true
+log4j.appender.CONSOLE.filter.3=org.apache.log4j.varia.DenyAllFilter
+
+log4j.appender.FILE=org.apache.log4j.FileAppender
+log4j.appender.FILE.Threshold=DEBUG
+log4j.appender.FILE.File=${test.tmpDir}/file-appender.log
+log4j.appender.FILE.filter.1=org.apache.log4j.config.StartsWithFilter
+log4j.appender.FILE.filter.2=org.apache.log4j.varia.LevelMatchFilter
+log4j.appender.FILE.filter.2.LevelToMatch=INFO
+log4j.appender.FILE.filter.2.AcceptOnMatch=true
+log4j.appender.FILE.filter.3=org.apache.log4j.varia.DenyAllFilter
+
+log4j.appender.RFA=org.apache.log4j.RollingFileAppender
+log4j.appender.RFA.Threshold=DEBUG
+log4j.appender.RFA.File=${test.tmpDir}/rolling-file-appender.log
+log4j.appender.RFA.filter.1=org.apache.log4j.config.StartsWithFilter
+log4j.appender.RFA.filter.2=org.apache.log4j.varia.LevelMatchFilter
+log4j.appender.RFA.filter.2.LevelToMatch=INFO
+log4j.appender.RFA.filter.2.AcceptOnMatch=true
+log4j.appender.RFA.filter.3=org.apache.log4j.varia.DenyAllFilter
+
+log4j.appender.DRFA=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.DRFA.Threshold=DEBUG
+log4j.appender.DRFA.File=${test.tmpDir}/daily-rolling-file-appender.log
+log4j.appender.DRFA.filter.1=org.apache.log4j.config.StartsWithFilter
+log4j.appender.DRFA.filter.2=org.apache.log4j.varia.LevelMatchFilter
+log4j.appender.DRFA.filter.2.LevelToMatch=INFO
+log4j.appender.DRFA.filter.2.AcceptOnMatch=true
+log4j.appender.DRFA.filter.3=org.apache.log4j.varia.DenyAllFilter
+
+log4j.rootLogger=TRACE, LIST, LIST2, CONSOLE, FILE, RFA, DRFA
\ No newline at end of file
diff --git a/log4j-1.2-api/src/test/resources/log4j-multipleFilters.xml 
b/log4j-1.2-api/src/test/resources/log4j-multipleFilters.xml
new file mode 100644
index 0000000..ea256e7
--- /dev/null
+++ b/log4j-1.2-api/src/test/resources/log4j-multipleFilters.xml
@@ -0,0 +1,92 @@
+<?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">
+    <param name="Threshold" value="DEBUG" />
+    <filter class="org.apache.log4j.config.StartsWithFilter" />
+    <filter class="org.apache.log4j.varia.LevelMatchFilter">
+      <param name="LevelToMatch" value="INFO" />
+      <param name="AcceptOnMatch" value="true" />
+    </filter>
+    <filter class="org.apache.log4j.varia.DenyAllFilter" />
+  </appender>
+
+  <appender name="LIST2" 
class="org.apache.logging.log4j.test.appender.ListAppender">
+    <param name="Threshold" value="DEBUG" />
+    <filter class="org.apache.log4j.config.StartsWithFilter" />
+    <filter class="org.apache.log4j.varia.LevelMatchFilter">
+      <param name="LevelToMatch" value="INFO" />
+      <param name="AcceptOnMatch" value="true" />
+    </filter>
+    <filter class="org.apache.log4j.varia.DenyAllFilter" />
+  </appender>
+
+  <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+    <param name="Threshold" value="DEBUG" />
+    <filter class="org.apache.log4j.config.StartsWithFilter" />
+    <filter class="org.apache.log4j.varia.LevelMatchFilter">
+      <param name="LevelToMatch" value="INFO" />
+      <param name="AcceptOnMatch" value="true" />
+    </filter>
+    <filter class="org.apache.log4j.varia.DenyAllFilter" />
+  </appender>
+
+  <appender name="FILE" class="org.apache.log4j.FileAppender">
+    <param name="Threshold" value="DEBUG" />
+    <param name="File" value="${test.tmpDir}/file-appender.log" />
+    <filter class="org.apache.log4j.config.StartsWithFilter" />
+    <filter class="org.apache.log4j.varia.LevelMatchFilter">
+      <param name="LevelToMatch" value="INFO" />
+      <param name="AcceptOnMatch" value="true" />
+    </filter>
+    <filter class="org.apache.log4j.varia.DenyAllFilter" />
+  </appender>
+
+  <appender name="RFA" class="org.apache.log4j.RollingFileAppender">
+    <param name="Threshold" value="DEBUG" />
+    <param name="File" value="${test.tmpDir}/rolling-file-appender.log" />
+    <filter class="org.apache.log4j.config.StartsWithFilter" />
+    <filter class="org.apache.log4j.varia.LevelMatchFilter">
+      <param name="LevelToMatch" value="INFO" />
+      <param name="AcceptOnMatch" value="true" />
+    </filter>
+    <filter class="org.apache.log4j.varia.DenyAllFilter" />
+  </appender>
+
+  <appender name="DRFA" class="org.apache.log4j.DailyRollingFileAppender">
+    <param name="Threshold" value="DEBUG" />
+    <param name="File" value="${test.tmpDir}/daily-rolling-file-appender.log" 
/>
+    <filter class="org.apache.log4j.config.StartsWithFilter" />
+    <filter class="org.apache.log4j.varia.LevelMatchFilter">
+      <param name="LevelToMatch" value="INFO" />
+      <param name="AcceptOnMatch" value="true" />
+    </filter>
+    <filter class="org.apache.log4j.varia.DenyAllFilter" />
+  </appender>
+
+  <root>
+    <priority value="TRACE" />
+    <appender-ref ref="LIST" />
+    <appender-ref ref="LIST2" />
+    <appender-ref ref="CONSOLE" />
+    <appender-ref ref="FILE" />
+    <appender-ref ref="RFA" />
+    <appender-ref ref="DRFA" />
+  </root>
+</log4j:configuration>
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/CompositeFilter.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/CompositeFilter.java
index 6286876..43a8c0d 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/CompositeFilter.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/CompositeFilter.java
@@ -62,9 +62,9 @@ public final class CompositeFilter extends AbstractLifeCycle 
implements Iterable
         if (filter instanceof CompositeFilter) {
             final int size = this.filters.length + ((CompositeFilter) 
filter).size();
             final Filter[] copy = Arrays.copyOf(this.filters, size);
-            final int index = this.filters.length;
+            int index = this.filters.length;
             for (final Filter currentFilter : ((CompositeFilter) 
filter).filters) {
-                copy[index] = currentFilter;
+                copy[index++] = currentFilter;
             }
             return new CompositeFilter(copy);
         }
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/CompositeFilterTest.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/CompositeFilterTest.java
new file mode 100644
index 0000000..0a5698b
--- /dev/null
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/CompositeFilterTest.java
@@ -0,0 +1,49 @@
+/* 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.logging.log4j.core.filter;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.Filter.Result;
+import org.junit.jupiter.api.Test;
+
+public class CompositeFilterTest {
+
+    @Test
+    public void testConcatenation() {
+        final Filter a = 
DenyAllFilter.newBuilder().setOnMatch(Result.ACCEPT).build();
+        final Filter b = 
DenyAllFilter.newBuilder().setOnMatch(Result.NEUTRAL).build();
+        final Filter c = 
DenyAllFilter.newBuilder().setOnMatch(Result.DENY).build();
+        // The three values need to be distinguishable
+        assertNotEquals(a, b);
+        assertNotEquals(a,  c);
+        assertNotEquals(b, c);
+        final Filter[] expected = new Filter[] {a, b, c};
+        final CompositeFilter singleA = CompositeFilter.createFilters(new 
Filter[] {a});
+        final CompositeFilter singleB = CompositeFilter.createFilters(new 
Filter[] {b});
+        final CompositeFilter singleC = CompositeFilter.createFilters(new 
Filter[] {c});
+        // Concatenating one at a time
+        final CompositeFilter concat1 = singleA.addFilter(b).addFilter(c);
+        assertArrayEquals(expected, concat1.getFiltersArray());
+        // In reverse order
+        final CompositeFilter concat2 = 
singleA.addFilter(singleB.addFilter(singleC));
+        assertArrayEquals(expected, concat2.getFiltersArray());
+    }
+}

Reply via email to