Log4j 2.9.1 adding ThresholdFilter to Appender programmatically

Viewed 1890

I have a serious problem with log4j 2.9.x I write "serious" because the documentation of log4j is reeeeally crap! There is no clear and up-to-date documentation for this version. Even the existing documented code don't pass the compiler since they did breaking changes every now and then and you can't be sure which version they have documented. I googled a whole day but what i found is mostly outdated too.
So I am a bit confused configuring log4j2 programmatically.

what I want to achieve:

1) additional "loglevel" that always prints to console independently of current loglevel (analog to gradles LIFECYCLE)

2) log to console and file

3) filename determined at runtime

I found a way to achieve 1) and 2) with XML. I haven't found a suitable way for 3) . I dont like to use a static context or system variable for the filename since i expect concurrency issues.

Therefore my approach is to configure log4j programmatically. Here my configuration factory:

package my.abc

import org.apache.logging.log4j.Level
import org.apache.logging.log4j.core.Filter
import org.apache.logging.log4j.core.LoggerContext
import org.apache.logging.log4j.core.config.Configuration
import org.apache.logging.log4j.core.config.ConfigurationFactory
import org.apache.logging.log4j.core.config.ConfigurationSource
import org.apache.logging.log4j.core.config.Order
import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder
import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration
import org.apache.logging.log4j.core.config.plugins.Plugin

@Plugin(name = "CustomConfigurationFactory", category = ConfigurationFactory.CATEGORY)
@Order(50)
class CustomConfigurationFactory extends ConfigurationFactory {

    private static String MESSAGE_PATTERN = "%d{HH:mm:ss.SSS} [%t] %-5level 123 %logger{36} - %msg%n"

    Level logLevel
    String packageToScan

    CustomConfigurationFactory(Level logLevel, String packageToScan) {
        this.logLevel = logLevel
        this.packageToScan = packageToScan
    }

    private Configuration createConfiguration(final String name, ConfigurationBuilder<BuiltConfiguration> builder) {
        builder.setConfigurationName(name)
        builder.setStatusLevel(Level.ERROR)

        //add appender
        builder.add(createConsoleAppender(builder))
        builder.add(createLifecycleAppender(builder))

        //add logger
        builder.add(createLogger(builder))

//        builder.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.DENY)
//                .addAttribute("level", logLevel))

        builder.add(builder.newRootLogger(Level.TRACE).add(builder.newAppenderRef("stdout")))
        return builder.build()
    }

    @Override
    Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) {
        return getConfiguration(loggerContext, source.toString(), null)
    }

    @Override
    Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) {
        ConfigurationBuilder<BuiltConfiguration> builder = newConfigurationBuilder()
        return createConfiguration(name, builder)
    }

    @Override
    protected String[] getSupportedTypes() {
        return [{ "*" }]
    }

    private AppenderComponentBuilder createConsoleAppender(ConfigurationBuilder<BuiltConfiguration> builder) {
        AppenderComponentBuilder appenderBuilder = builder.newAppender("stdout", "CONSOLE")
        appenderBuilder.add(builder.newLayout("PatternLayout").
                addAttribute("pattern", MESSAGE_PATTERN))
        appenderBuilder.add(builder.newFilter("MarkerFilter", Filter.Result.DENY,
                Filter.Result.NEUTRAL).addAttribute("marker", "lifecycle"))
//        appenderBuilder.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT,
//                Filter.Result.DENY).addAttribute("level", logLevel))
        return appenderBuilder
    }

    private AppenderComponentBuilder createLifecycleAppender(ConfigurationBuilder<BuiltConfiguration> builder) {
        AppenderComponentBuilder appenderBuilder = builder.newAppender("lifecycle", "CONSOLE")
        appenderBuilder.add(builder.newLayout("PatternLayout").
                addAttribute("pattern", "%d{HH:mm:ss.SSS} [%t] LIFECYCLE %logger{36} - %msg%n"))
        appenderBuilder.add(builder.newFilter("MarkerFilter", Filter.Result.ACCEPT, Filter.Result.DENY)
                .addAttribute("marker", "lifecycle"))
        return appenderBuilder
    }

    private LoggerComponentBuilder createLogger(ConfigurationBuilder<BuiltConfiguration> builder) {
        builder.newLogger(packageToScan, "trace")
                .add(builder.newAppenderRef("stdout"))
                .add(builder.newAppenderRef("lifecycle"))
                .addAttribute("additivity", false)
    }
}

You may noticed the 2 outcommented lines. Thats my problem.

When i use builder.add(builder.newFilter("ThresholdFilter"... all logs will be filtered out when they dont fit the loglevel. Even the "lifecycle"-logs that i want to be logged in any case.

When i use appenderBuilder.add(builder.newFilter("ThresholdFilter... i got the error message 2017-11-23 12:12:34,905 main ERROR appender CONSOLE has no parameter that matches element ThresholdFilter

What is wierd. Because when i configure everything with xml, i can do exactly that thing(adding thresholdfilter to a single appender) and it works.

<Appender type="Console" name="STDOUT" >
   <Filters>
      <Filter type="MarkerFilter" marker="lifecycle" onMatch="DENY" onMismatch="NEUTRAL"/>
      <Filter type="ThresholdFilter" level="${logLevel}" onMatch="ACCEPT" onMismatch="DENY" />
   </Filters>
   <Layout type="PatternLayout" pattern="${pattern}"/>
</Appender>

To beeing complete here the whole xml that works for erequirements 1) and 2)

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info" strict="true" name="XMLConfigTest"
               packages="org.apache.logging.log4j.test">
    <Properties>
        <Property name="logLevel">error</Property>
        <Property name="filename">target/test.log</Property>
        <Property name="pattern">%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Property>
    </Properties>

    <Appenders>
        <Appender type="Console" name="STDOUT">
            <Filters>
                <Filter type="MarkerFilter" marker="lifecycle" onMatch="DENY" onMismatch="NEUTRAL"/>
                <Filter type="ThresholdFilter" level="${logLevel}" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <Layout type="PatternLayout" pattern="${pattern}"/>
        </Appender>
        <Appender type="Console" name="LIFECYCLE">
            <Filters>
                <Filter type="MarkerFilter" marker="lifecycle" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <Layout type="PatternLayout" pattern="%d{HH:mm:ss.SSS} [%t] LIFECYCLE %logger{36} - %msg%n"/>
        </Appender>
        <Appender type="File" name="File" fileName="${filename}">
            <Layout type="PatternLayout">
                <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
            </Layout>
        </Appender>
    </Appenders>

    <Loggers>
        <Logger name="my.abc" level="trace" additivity="false">
            <AppenderRef ref="STDOUT"/>
            <AppenderRef ref="LIFECYCLE"/>
        </Logger>

        <Root level="error">
            <AppenderRef ref="STDOUT"/>
        </Root>
    </Loggers>
</Configuration>

There has to be a way to add ThresholdFilter to the appender. I found the class ThresholdFilter. But i have absolutely no idea how to use it. instantiating works ThresholdFilter.createFilter(...) but where can I add it?

To cut a long story short..

How can I add ThresholdFilter to a single Appender without xml?

1 Answers
Related