7
votes

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.

5

5 Answers

9
votes

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>
4
votes

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.

4
votes

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

0
votes

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.

0
votes

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>