Log4j 2.9.1 программно добавляет ThresholdFilter в Appender

У меня серьезная проблема с log4j 2.9.x. Я пишу "серьезно", потому что документация по log4j просто дерьмо! Для этой версии нет четкой и актуальной документации. Даже существующий задокументированный код не проходит через компилятор, поскольку время от времени он вносил критические изменения, и вы не можете быть уверены, какую версию они задокументировали. Я гуглил целый день, но то, что я нашел, тоже в основном устарело.
Так что я немного запутался в программной настройке log4j2.

чего я хочу добиться:

1) дополнительный «лог-уровень», который всегда выводит на консоль независимо от текущего лог-уровня (аналог градации LIFECYCLE)

2) войти в консоль и файл

3) имя файла определяется во время выполнения

Я нашел способ добиться 1) и 2) с помощью XML. Я не нашел подходящего способа для 3) . Мне не нравится использовать статический контекст или системную переменную для имени файла, так как я ожидаю проблем с параллелизмом.

Поэтому мой подход заключается в программной настройке log4j. Вот моя фабрика конфигураций:

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)
    }
}

Вы могли заметить две закомментированные строки. Это моя проблема.

Когда я использую builder.add(builder.newFilter("ThresholdFilter"..., все журналы будут отфильтрованы, если они не соответствуют уровню журнала. Даже журналы «жизненного цикла», которые я хочу регистрировать в любом случае.

Когда я использую appenderBuilder.add(builder.newFilter("ThresholdFilter..., я получаю сообщение об ошибке 2017-11-23 12:12:34,905 main ERROR appender CONSOLE has no parameter that matches element ThresholdFilter

Что странно. Потому что, когда я настраиваю все с помощью xml, я могу делать именно это (добавляя пороговый фильтр к одному приложению), и это работает.

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

Чтобы завершить здесь весь xml, который работает для требований 1) и 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>

Должен быть способ добавить ThresholdFilter в приложение. Я нашел класс ThresholdFilter. Но я совершенно не представляю, как его использовать. создание экземпляров работает ThresholdFilter.createFilter(...), но где я могу его добавить?

Короче говоря..

Как я могу добавить ThresholdFilter к одному Appender без xml?


person Pirax    schedule 23.11.2017    source источник


Ответы (1)


к вашему сведению

Мой подход к решению всех требований:

Я вернулся к XML и добавил некоторые динамические свойства. Некоторые свойства берутся из системных свойств. Если они не существуют, будет принято значение по умолчанию, определенное как свойство xml.

Обратите особое внимание на свойство "logFile". Это сочетание системного свойства (для исправления пути к файлу журнала) и динамически определяется log4j. Это не совсем потокобезопасно. Но в нашем случае приемлемо.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info" strict="true" name="XMLConfig"
               packages="org.apache.logging.log4j.test">
    <Properties>
        <Property name="logLevel">INFO</Property>
        <Property name="logFileDir">log/</Property>
        <Property name="logLevelFile">OFF</Property>
        <Property name="logFile">${sys:logFileDir}ConfigMgmt-${date:yyyyMMdd_HHmmss-SSS}.log</Property>
        <Property name="packageName">com.hamburgsud</Property>
        <Property name="messagePattern">%d{HH:mm:ss.SSS} (Test) %-5level %logger{1.} - %msg%n</Property>
    </Properties>

    <Appenders>
        <!-- Console Logging -->
        <Appender type="Console" name="STDOUT">
            <Filters>
                <Filter type="MarkerFilter" marker="lifecycle" onMatch="DENY" onMismatch="NEUTRAL"/>
                <Filter type="ThresholdFilter" level="${sys:logLevel}" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <Layout type="PatternLayout" pattern="${sys:messagePattern}"/>
        </Appender>
        <!-- LIFECYCLE is analog to gradles lifecycle log level - means always logging
             When the marker for lifecycle-logging is added, the message will always be logged -->
        <Appender type="Console" name="LIFECYCLE">
            <Filters>
                <Filter type="MarkerFilter" marker="lifecycle" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <Layout type="PatternLayout" pattern="%logger{1.} - %msg%n"/>
        </Appender>


        <!-- File Logging -->
        <Appender type="File" name="FILE" fileName="${sys:logFile}" createOnDemand="true">
            <Filters>
                <Filter type="MarkerFilter" marker="lifecycle" onMatch="DENY" onMismatch="NEUTRAL"/>
                <Filter type="ThresholdFilter" level="${sys:logLevelFile}" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <Layout type="PatternLayout" pattern="${sys:messagePattern}"/>
        </Appender>
        <!-- Counterpart to consoles LIFECYCLE logging. When file logging is activated and a log is marked with
             lifecycle, the message will be logged to the file too. -->
        <Appender type="File" name="FILELIFECYCLE" fileName="${sys:logFile}" createOnDemand="true">
            <Filters>
                <Filter type="MarkerFilter" marker="lifecycle" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
            <Layout type="PatternLayout" pattern="%logger{1.} - %msg%n"/>
        </Appender>
    </Appenders>

    <Loggers>
        <Root level="trace" additivity="false">
            <AppenderRef ref="STDOUT"/>
            <AppenderRef ref="LIFECYCLE"/>
            <AppenderRef ref="FILE"/>
            <AppenderRef ref="FILELIFECYCLE"/>
        </Root>
    </Loggers>
</Configuration>
person Pirax    schedule 14.02.2018