How to conditionally enable/disable a Log4J2 appender

In our production environment, we want to conditionally disable console logging.

One option is to redirect console output to /dev/null:

java -jar app.jar >/dev/null 2>&1

But this would suppress all console output, even output generated by means other than logging.

Another option is to conditionally disable a given appender. To do this, first we need a script engine.

As Nashorn is now deprecated and marked for removal, I prefer to use Groovy. Just add the dependency to your project:

Listing 1. build.gradle
implementation 'org.codehaus.groovy:groovy-jsr223:2.5.8'

And then write a script to make your script conditional. For example:

Listing 2. log4j2.xml ScriptFilter
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Scripts>
        <Script name="isConsoleAppenderEnabled" language="groovy"><![CDATA[
            return System.getProperty("CONSOLE_APPENDER_ENABLED", 'true').equalsIgnoreCase('true');
        ]]></Script>
    </Scripts>

    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss,SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>

    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console">
                <ScriptFilter onMatch="ACCEPT" onMisMatch="DENY">
                    <ScriptRef ref="isConsoleAppenderEnabledGroovy"/>
                </ScriptFilter>
            </AppenderRef>
        </Root>
    </Loggers>
</Configuration>

With this setup, the Console appender is enabled by default but you can disable it by passing -DCONSOLE_APPENDER_ENABLED=false to your Java runtime.

The problem with this approach, though, is that the script is evaluated for every log record which means that the script engine and the Groovy runtime will remain in memory for the entire lifetime of the application. A more efficient approach is to instead use ScriptAppenderSelector:

Listing 3. log4j2.xml ScriptAppenderSelector
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="ScriptAppenderSelectorExample">
    <Appenders>
        <ScriptAppenderSelector name="ConsoleOrNull">
            <Script language="groovy"><![CDATA[
                if (System.getProperty("CONSOLE_APPENDER_ENABLED", 'true').equalsIgnoreCase('true')) {
                    return "Console"
                } else {
                    return "Null"
                }
            ]]></Script>
            <AppenderSet>
                <Console name="Console" target="SYSTEM_OUT">
                    <PatternLayout pattern="%d{HH:mm:ss,SSS} [%t] %-5level %logger{36} - %msg%n"/>
                </Console>
                <Null name="Null" />
            </AppenderSet>
        </ScriptAppenderSelector>

        <ScriptAppenderSelector name="FileOrNull">
            <Script language="groovy"><![CDATA[
                if (System.getProperty("FILE_APPENDER_ENABLED", 'true').equalsIgnoreCase('true')) {
                    return "File"
                } else {
                    return "Null"
                }
            ]]></Script>
            <AppenderSet>
                <File name="File" fileName="application.log">
                    <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSSSSS Z} [%t] [%level] %logger - %msg%n" />
                </File>
                <Null name="Null" />
            </AppenderSet>
        </ScriptAppenderSelector>

        <ScriptAppenderSelector name="SmtpOrNull">
            <Script language="groovy"><![CDATA[
                if (System.getProperty("SMTP_APPENDER_ENABLED", 'true').equalsIgnoreCase('true')) {
                    return "SMTP"
                } else {
                    return "Null"
                }
            ]]></Script>
            <AppenderSet>
                <SMTP name="SMTP"
                      subject="App: Error"
                      from="log4j@example.com"
                      to="support@example.com"
                      smtpHost="smtp.example.com"
                      smtpPort="25"
                      bufferSize="5">
                </SMTP>
                <Null name="Null" />
            </AppenderSet>
        </ScriptAppenderSelector>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="ConsoleOrNull"/>
            <AppenderRef ref="FileOrNull"/>
            <AppenderRef ref="SmtpOrNull"/>
        </Root>
    </Loggers>
</Configuration>

This way, after Log4J runs the script to select the appender, it will ignore the script afterwards.