How do I conditionally add log4j2 appender depending on java system property?
Asked Answered
M

6

12

I'm trying to figure out how I can add an appender to a logger dependent on whether a java system property is given / set.

So let's say I have a basic configuration like this:

<Logger name="myLogger" level="info" additivity="false">
  <AppenderRef ref="myAppender1" />
  <AppenderRef ref="myAppender2" />
</Logger>

So now I'd like to figure out a way to conditionally only add the 2nd appender if I provide a parameter -PaddAppender2. Something like this:

<Logger name="myLogger" level="info" additivity="false">
  <AppenderRef ref="myAppender1" />
  <?if (${sys:enableAppender2:-false) == "true"}>
  <AppenderRef ref="myAppender2" />
  </?if> 
</Logger>

How do I do that?

I know I can for example make the level dynamic on a given property ("logLevel") like that (where "info" is the default if the property is not given):

<Logger name="test" level="${sys:logLevel:-info}" additivity="false">

I looked at the documentation for filters, and I can't figure it out. That is of course if filters are even the right way to go here.

Midmost answered 28/1, 2016 at 7:6 Comment(0)
S
20

Solution without any scripting:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error" strict="true">
    <Properties>
        <Property name="appenderToUse">stdout_${sys:LOG4J_LAYOUT:-plain}</Property>
    </Properties>

    <Appenders>
        <Appender type="Console" name="stdout_plain">
            <Layout type="PatternLayout" pattern="%d [%t] %-5p %c - %m%n"/>
        </Appender>

        <Appender type="Console" name="stdout_json">
            <Layout type="JSONLayout" compact="true" eventEol="true" stacktraceAsString="true" properties="true"/>
        </Appender>
    </Appenders>

    <Loggers>
        <Root level="info">
            <AppenderRef ref="${appenderToUse}"/>
        </Root>
    </Loggers>
</Configuration>
Schoolboy answered 24/7, 2019 at 3:41 Comment(2)
In conjuction with NullAppender, this solution makes me happy - now I can turn on/off Console appender when I want!Xena
Even works inline, without the explicit <Property> declarationUrsola
B
5

The solution provided by Robert works, but it is not efficient as the script will be evaluated once per log record.

A more efficient solution that evaluates the script only once is to use ScriptAppenderSelector together with the NullAppender:

According to the docs:

ScriptAppenderSelector

When the configuration is built, the ScriptAppenderSelector appender calls a Script to compute an appender name. Log4j then creates one of the appender named listed under AppenderSet using the name of the ScriptAppenderSelector. After configuration, Log4j ignores the ScriptAppenderSelector.

NullAppender

An Appender that ignores log events. Use for compatibility with version 1.2 and handy for composing a ScriptAppenderSelector.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="ScriptAppenderSelectorExample">
    <Appenders>
        <ScriptAppenderSelector name="SelectConsole">
            <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="SelectFile">
            <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{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
                </File>
                <Null name="Null" />
            </AppenderSet>
        </ScriptAppenderSelector>

        <ScriptAppenderSelector name="SelectSMTP">
            <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="[email protected]"
                      to="[email protected]"
                      smtpHost="smtp.example.com"
                      smtpPort="25"
                      bufferSize="5">
                </SMTP>
                <Null name="Null" />
            </AppenderSet>
        </ScriptAppenderSelector>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="SelectConsole"/>
            <AppenderRef ref="SelectFile"/>
            <AppenderRef ref="SelectSMTP"/>
        </Root>
    </Loggers>
</Configuration>

References

Brynn answered 27/10, 2019 at 0:38 Comment(1)
groovy doesn't seem to be supportedWig
D
4

Similar to rgoers solution but using instead of . This solution benefits from fact that Nashorn engine is part of Java 8 so there is no additional dependencies needed.

<Scripts>
  <Script name="isAppender2Enabled" language="nashorn"><![CDATA[
    var System = Java.type('java.lang.System'),
        Boolean = Java.type('java.lang.Boolean');
    Boolean.parseBoolean(System.getProperty('enableAppender2', 'false'));
  ]]></Script>
</Scripts>

<Loggers>
  <Logger name="myLogger" level="info" additivity="false">
    <AppenderRef ref="myAppender1" />
    <AppenderRef ref="myAppender2">
      <ScriptFilter onMatch="ACCEPT" onMisMatch="DENY">
        <ScriptRef ref="isAppender2Enabled" />
      </ScriptFilter>
    </AppenderRef>
  </Logger>
</Loggers>

Note that ScriptFilter is evaluating the script every time when Log4j event occurs. Therefore it is possible to enable/disable the appender on the run time (by changing the value of the system property) with immediate effect. On the other hand, script evaluation can have negative impact on logging performance.

Dilemma answered 9/3, 2018 at 8:37 Comment(1)
WARNING: The Nashorn JavaScript engine is marked as deprecated in Java 11 - see tweets from Mark Reinhold: twitter.com/mreinhold/status/1006572693825601536. Following warning is produced in Java 11: Warning: Nashorn engine is planned to be removed from a future JDK release. You can use JVM option to prevent printing of the warning: -Dnashorn.args="--no-deprecation-warning".Barth
P
2

Building on some of the ideas in this thread, here's what I did to conditionally log to console.

Example use-case

  1. Always log to a file appender.
  2. Log to Console only in some environments.

Solution

  • For Console logging, set system property additional.log.appender=console
  • Or, disable Console logging by omitting this property.
  • In the Logger AppenderRef, use ${sys:additional.log.appender:-null}.
    • Sends logs to the console appender if the system property was set, or defaults to null appender if not set. (null appender ignores logs)

System Property

# set for console logging
additional.log.appender=console

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Appenders>
        <RollingFile name="file"
            fileName="my-file.log"
            filePattern="my-file%i.log">
            <PatternLayout pattern="%d %5p [%t] %c - %m%n" />
            <Policies>
                <SizeBasedTriggeringPolicy size="10 MB" />
            </Policies>
            <DefaultRolloverStrategy max="10" />
        </RollingFile>
 
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d %5p [%t] %c - %m%n" />
        </Console>
 
        <Null name="null" />
    </Appenders>
 
    <Loggers>
        <Logger name="com.acme" level="DEBUG">
        </Logger>
 
        <Root level="INFO">
            <AppenderRef ref="file" />
            <AppenderRef ref="${sys:additional.log.appender:-null}" />
        </Root>
    </Loggers>
</Configuration>
Pottage answered 6/3, 2022 at 4:3 Comment(0)
M
0

I wasn't able to figure out a solution via config file alone, but I found one that solves the problem programmatically.

Note that in our specific case, we always log to a "local log" ("splunk local"), but in given cases (controlled by the property), we also want to log the same information to another location (that is not relative) and is periodically read and forwarded to a splunk server ("splunk forwarder").

And that's why we can copy most of the properties from one logger to the other.

private static final Logger SPLUNK_LOG = getLogger();

private static Logger getLogger() {
    if (!BooleanUtils.toBoolean(SystemUtils.getJavaPropertyValue(ENABLE_PROPERTY_NAME, "false"))) {
        return LoggerFactory.getLogger(SPLUNK_LOG_NAME);
    } else {
        LOG.info("Dynamically adding splunk forwarder appender");
        try {
            final LoggerContext loggerContext = (LoggerContext) LogManager.getContext();
            final Configuration configuration = loggerContext.getConfiguration();

            // configure appender based on local splunk appender
            final RollingFileAppender splunkLocal = (RollingFileAppender) configuration.getAppender(LOCAL_LOG_NAME);
            final RollingFileAppender splunkForwarder = RollingFileAppender.createAppender(FORWARDER_FILE_NAME,
                    FORWARDER_FILE_PATTERN, FORWARDER_APPEND, FORWARDER_NAME, null, null, null,
                    splunkLocal.getManager().getTriggeringPolicy(), splunkLocal.getManager().getRolloverStrategy(),
                    splunkLocal.getLayout(), splunkLocal.getFilter(), null, FORWARDER_ADVERTISE, null, null);
            splunkForwarder.start();

            // add splunk forwarder appender to splunk logger
            final LoggerConfig loggerConfig = configuration.getLoggerConfig(SPLUNK_LOG_NAME);
            loggerConfig.addAppender(splunkForwarder, Level.INFO, null);

            LOG.info("Successfully added splunk forwarder appender");
            return loggerContext.getLogger(SPLUNK_LOG_NAME);
        } catch (Exception ex) {
            throw new IllegalStateException("Failed to dynamically add splunk forwarder appender", ex);
        }
    }
}

If anyone knows how to do this via config file alone, that would be great.

Midmost answered 7/2, 2016 at 6:7 Comment(0)
C
0

The way this is intended to be handled is by using a filter. In this case you can use a Script filter.

<Logger name="myLogger" level="info" additivity="false">
  <AppenderRef ref="myAppender1" />
  <AppenderRef ref="myAppender2">
     <ScriptFilter onMatch="ACCEPT" onMisMatch="DENY">
      <Script language="groovy"><![CDATA[
         return System.getProperty("enableAppender2", "false").equalsIgnoreCase("true");
      ]]></Script>
    </ScriptFilter>
  </AppenderRef>
</Logger>
Countermarch answered 7/2, 2016 at 6:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.