Log4j 2 filtering with multiple filters

Filtering in Log4j 2 is an extra way to control which messages make it to the appender. The documentation states that:

Filters may be configured in one of four locations

Filters at these locations do not behave the same but this is explained right afterwards.

This post is about the fact that the configuration does not really work the same in each of these locations. Most often I started out with a filter in the appender section because this is what the examples show you. Take the MarkerFilter example:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
    <Appenders>
        <RollingFile name="RollingFile" fileName="logs/app.log"
                     filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
            <MarkerFilter marker="FLOW" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout>
                <pattern>%d %p %c{1.} [%t] %m%n</pattern>
            </PatternLayout>
            <TimeBasedTriggeringPolicy/>
        </RollingFile>
    </Appenders>
    <Loggers>
        <Root level="error">
            <AppenderRef ref="RollingFile"/>
        </Root>
    </Loggers>
</Configuration>

Adding a second MarkerFilter seems a no-brainer. Just duplicate the line and change some attributes:

<RollingFile name="RollingFile" fileName="logs/app.log"
             filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
    <MarkerFilter marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
    <MarkerFilter marker="FLOW2" onMatch="ACCEPT" onMismatch="DENY"/>
    <PatternLayout>
        <pattern>%d %p %c{1.} [%t] %m%n</pattern>
    </PatternLayout>
    <TimeBasedTriggeringPolicy/>
</RollingFile>

Oddly enough, this corrupts the whole configuration. You get:

ERROR appender RollingFile has no parameter that matches element MarkerFilter

This log message is easily missed as it happened to me. How to fix this? Use a CompositeFilter which is a surrounding element named Filters.

<RollingFile name="RollingFile" fileName="logs/app.log"
             filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
    <Filters>
        <MarkerFilter marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
        <MarkerFilter marker="FLOW2" onMatch="ACCEPT" onMismatch="DENY"/>
    </Filters>
    <PatternLayout>
        <pattern>%d %p %c{1.} [%t] %m%n</pattern>
    </PatternLayout>
    <TimeBasedTriggeringPolicy/>
</RollingFile>

The logic here is quite simple. Only one filter is allowed. If you want more you have to wrap them in a CompositeFilter. Now, at the beginning I told you that there are four locations where filters are permitted. Another location is context-wide. These are filters which are direct sub-elements of the Configuration element. And there it is totally fine to add more than one filter. You get no error by log4j and it works as expected:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
    <MarkerFilter marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
    <MarkerFilter marker="FLOW2" onMatch="ACCEPT" onMismatch="DENY"/>
    ...
</Configuration>

It is also ok to wrap them in a CompositeFilter:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
    <Filters>
        <MarkerFilter marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
        <MarkerFilter marker="FLOW2" onMatch="ACCEPT" onMismatch="DENY"/>
    </Filters>
    ...
</Configuration>

Best practice I derive from this weird behavior is to always wrap them. Then I can copy and paste between locations without thinking too much.