Log4j2: how to get a conditional appender without any code
Log4j2 does not have a conditional feature so if you need to use an appender depending on some environment variable or system property it does not look that trivial. However, log4j2 hides a feature making it not that hard to implement.
If log4j2 does not have conditions, it has what is called a routing appender which, once well configured, can be used as a conditional appender.
First, let's illustrate this need by an example - likely easier than real life use case which will switch between kafka appender, the console and rolling files but this is enough to get the idea. Our use case will be to switch between System.out and System.err without configuring the target of the Console appender dynamically (which would be simpler). Hardcoding an appender to System.err and another one to System.out will simulate we have two different appenders (let say a file and a console or a file and kafka).
A high level configuration can be:
<Configuration status="INFO">
<Appenders>
<Console name="ConsoleOut" target="SYSTEM_OUT" />
<Console name="ConsoleErr" target="SYSTEM_ERR" />
<!-- something missing -->
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="?????" />
</Root>
</Loggers>
</Configuration>
This configuration has a ConsoleOut appender logging on System.out and its sibling ConsoleErr logging on System.err. Then we just need to be able to switch between both and reference the right appender in our logger(s) - concretely we want an appender selecting one of the two console ones instead of the comment 'something missing' and we want to put that appender name instead of the '?????' in the ref of our root logger.
To do that we will use the environment variable STREAM_TO_USE which will take the value OUT or ERR to switch between both appenders. The routing appender evaluates a pattern which, thanks to log4j2 placeholding support, can be an environment variable with a default. Then the pattern allows to select the Route to use to actually log the log event. In other words, the router (or routing appender) behaves as a proxy in front of other appenders :).
Here is how we can configure it for our use case:
<Routing name="Router">
<Routes pattern="$${env:STREAM_TO_USE:-OUT}">
<Route ref="ConsoleOut" key="OUT" />
<Route ref="ConsoleErr" key="ERR" />
</Routes>
</Routing>
The $${env:STREAM_TO_USE:-OUT} syntax is may not be obvious if you are not used to log4j2 but it can be split like that:
- $${....}: it is an escaped placeholder to avoid a too early evaluation, in this case, keep in mind it behaves as a placeholder,
- env:xxxx it enables to point to the xxxx environment variable (the xxxx variable is evaluated in the namespace env which holds the environment variables),
- :-xxxx is not a smiley but the default syntax (inherited from bash), it means that if the variable is not set the value will be xxxx
So at the end, to get our feature, we can use that log4j2 configuration:
<?xml version="1.0"?>
<Configuration status="INFO">
<Appenders>
<Console name="ConsoleOut" target="SYSTEM_OUT" />
<Console name="ConsoleErr" target="SYSTEM_ERR" />
<Routing name="Router">
<Routes pattern="$${env:STREAM_TO_USE:-OUT}">
<Route ref="ConsoleOut" key="OUT" />
<Route ref="ConsoleErr" key="ERR" />
</Routes>
</Routing>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Router" />
</Root>
</Loggers>
</Configuration>
With this configuration, you have a conditional appender using the environment variable STREAM_TO_USE to switch between two appenders. Wasn't that hard after all ;)
From the same author:
In the same category: