«1. Обзор

Log4j 2 использует плагины, такие как Appenders и Layouts, для форматирования и вывода журналов. Они известны как основные плагины, и Log4j 2 предоставляет нам множество вариантов на выбор.

Однако в некоторых случаях нам также может понадобиться расширить существующий плагин или даже написать собственный.

В этом руководстве мы будем использовать механизм расширения Log4j 2 для реализации пользовательских плагинов.

2. Расширение плагинов Log4j 2

Плагины в Log4j 2 в целом делятся на пять категорий:

  1. Core Plugins
  2. Convertors
  3. Key Providers
  4. Lookups
  5. Type Converters

Log4j 2 позволяет нам реализовывать пользовательские плагины во всех вышеперечисленных категориях, используя общий механизм. Более того, это также позволяет нам расширять существующие плагины с тем же подходом.

В Log4j 1.x единственный способ расширить существующий плагин — переопределить его класс реализации. С другой стороны, Log4j 2 упрощает расширение существующих плагинов, аннотируя класс с помощью @Plugin.

В следующих разделах мы реализуем собственный плагин для некоторых из этих категорий.

3. Основной плагин

3.1. Реализация пользовательского основного плагина

Ключевые элементы, такие как Appenders, Layouts и Filters, известны как основные плагины в Log4j 2. Хотя существует разнообразный список таких плагинов, в некоторых случаях нам может потребоваться реализовать собственный основной плагин. Например, рассмотрим ListAppender, который только записывает записи журнала в список в памяти:

@Plugin(name = "ListAppender", 
  category = Core.CATEGORY_NAME, 
  elementType = Appender.ELEMENT_TYPE)
public class ListAppender extends AbstractAppender {

    private List<LogEvent> logList;

    protected ListAppender(String name, Filter filter) {
        super(name, filter, null);
        logList = Collections.synchronizedList(new ArrayList<>());
    }

    @PluginFactory
    public static ListAppender createAppender(
      @PluginAttribute("name") String name, @PluginElement("Filter") final Filter filter) {
        return new ListAppender(name, filter);
    }

    @Override
    public void append(LogEvent event) {
        if (event.getLevel().isLessSpecificThan(Level.WARN)) {
            error("Unable to log less than WARN level.");
            return;
        }
        logList.add(event);
    }
}

Мы аннотировали класс с помощью @Plugin, что позволяет нам назвать наш плагин. Кроме того, параметры аннотируются @PluginAttribute. Вложенные элементы, такие как фильтр или макет, передаются как @PluginElement. Теперь мы можем ссылаться на этот плагин в конфигурации, используя то же имя:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration xmlns:xi="http://www.w3.org/2001/XInclude"
    packages="com.baeldung" status="WARN">
    <Appenders>
        <ListAppender name="ListAppender">
            <BurstFilter level="INFO" rate="16" maxBurst="100"/>
        </MapAppender>
    </Appenders>
    <Loggers
        <Root level="DEBUG">
            <AppenderRef ref="ConsoleAppender" />
            <AppenderRef ref="ListAppender" />
        </Root>
    </Loggers>
</Configuration>

3.2. Сборщики подключаемых модулей

Пример в последнем разделе довольно прост и принимает только одно имя параметра. Вообще говоря, основные плагины, такие как приложения, намного сложнее и обычно принимают несколько настраиваемых параметров.

Например, рассмотрим приложение, которое записывает журналы в Kafka:

<Kafka2 name="KafkaLogger" ip ="127.0.0.1" port="9010" topic="log" partition="p-1">
    <PatternLayout pattern="%pid%style{%message}{red}%n" />
</Kafka2>

Для реализации таких приложений Log4j 2 предоставляет реализацию построителя плагинов на основе шаблона Builder:

@Plugin(name = "Kafka2", category = Core.CATEGORY_NAME)
public class KafkaAppender extends AbstractAppender {

    public static class Builder implements org.apache.logging.log4j.core.util.Builder<KafkaAppender> {

        @PluginBuilderAttribute("name")
        @Required
        private String name;

        @PluginBuilderAttribute("ip")
        private String ipAddress;

        // ... additional properties

        // ... getters and setters

        @Override
        public KafkaAppender build() {
            return new KafkaAppender(
              getName(), getFilter(), getLayout(), true, new KafkaBroker(ipAddress, port, topic, partition));
        }
    }

    private KafkaBroker broker;

    private KafkaAppender(String name, Filter filter, Layout<? extends Serializable> layout, 
      boolean ignoreExceptions, KafkaBroker broker) {
        super(name, filter, layout, ignoreExceptions);
        this.broker = broker;
    }

    @Override
    public void append(LogEvent event) {
        connectAndSendToKafka(broker, event);
    }
}

Короче говоря, мы представили класс Builder и аннотировал параметры с помощью @PluginBuilderAttribute. Из-за этого KafkaAppender принимает параметры подключения Kafka из конфигурации, показанной выше.

3.3. Расширение существующего плагина

Мы также можем расширить существующий основной плагин в Log4j 2. Мы можем добиться этого, дав нашему плагину то же имя, что и существующий плагин. Например, если мы расширяем RollingFileAppender:

@Plugin(name = "RollingFile", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
public class RollingFileAppender extends AbstractAppender {

    public RollingFileAppender(String name, Filter filter, Layout<? extends Serializable> layout) {
        super(name, filter, layout);
    }
    @Override
    public void append(LogEvent event) {
    }
}

Примечательно, что теперь у нас есть два приложения с одинаковыми именами. В таком сценарии Log4j 2 будет использовать добавление, обнаруженное первым. Мы увидим больше об обнаружении плагинов в следующем разделе.

Обратите внимание, что Log4j 2 не поддерживает использование нескольких плагинов с одинаковыми именами. Вместо этого лучше реализовать собственный плагин и использовать его в конфигурации ведения журнала.

4. Плагин конвертера

Макет является мощным плагином в Log4j 2. Он позволяет нам определять структуру вывода для наших журналов. Например, мы можем использовать JsonLayout для записи логов в формате JSON.

Другой такой плагин — PatternLayout. В некоторых случаях приложение хочет публиковать информацию, такую ​​как идентификатор потока, имя потока или отметку времени, с каждым оператором журнала. Плагин PatternLayout позволяет нам встраивать такие детали через строку шаблона преобразования в конфигурацию:

<Configuration status="debug" name="baeldung" packages="">
    <Appenders>
        <Console name="stdout" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %p %m%n"/>
        </Console>
    </Appenders>
</Configuration>

Здесь %d — это шаблон преобразования. Log4j 2 преобразует этот шаблон %d с помощью DatePatternConverter, который понимает шаблон преобразования и заменяет его отформатированной датой или отметкой времени.

Теперь предположим, что приложение, работающее внутри контейнера Docker, хочет напечатать имя контейнера с каждым оператором журнала. Для этого мы реализуем DockerPatternConverter и изменим приведенную выше конфигурацию, включив в нее строку преобразования:

@Plugin(name = "DockerPatternConverter", category = PatternConverter.CATEGORY)
@ConverterKeys({"docker", "container"})
public class DockerPatternConverter extends LogEventPatternConverter {

    private DockerPatternConverter(String[] options) {
        super("Docker", "docker");
    }

    public static DockerPatternConverter newInstance(String[] options) {
        return new DockerPatternConverter(options);
    }

    @Override
    public void format(LogEvent event, StringBuilder toAppendTo) {
        toAppendTo.append(dockerContainer());
    }

    private String dockerContainer() {
        return "container-1";
    }
}

Итак, мы реализовали собственный DockerPatternConverter, аналогичный шаблону даты. Он заменит шаблон преобразования именем контейнера Docker.

«Этот плагин похож на основной плагин, который мы реализовали ранее. Примечательно, что есть только одна аннотация, которая отличается от последнего плагина. Аннотация @ConverterKeys принимает шаблон преобразования для этого плагина.

В результате этот плагин преобразует строку шаблона %docker или %container в имя контейнера, в котором работает приложение:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration xmlns:xi="http://www.w3.org/2001/XInclude" packages="com.baeldung" status="WARN">
    <Appenders>
        <xi:include href="log4j2-includes/console-appender_pattern-layout_colored.xml" />
        <Console name="DockerConsoleLogger" target="SYSTEM_OUT">
            <PatternLayout pattern="%pid %docker %container" />
        </Console>
    </Appenders>
    <Loggers>
        <Logger name="com.baeldung.logging.log4j2.plugins" level="INFO">
            <AppenderRef ref="DockerConsoleLogger" />
        </Logger>
    </Loggers>
</Configuration>

5. Плагин поиска

Плагины поиска используются для добавления динамических значений в файле конфигурации Log4j 2. Они позволяют приложениям внедрять значения среды выполнения в некоторые свойства файла конфигурации. Значение добавляется посредством поиска на основе ключа в различных источниках, таких как файловая система, база данных и т. д.

Одним из таких плагинов является DateLookupPlugin, который позволяет заменить шаблон даты текущей системной датой приложения:

<RollingFile name="Rolling-File" fileName="${filename}" 
  filePattern="target/rolling1/test1-$${date:MM-dd-yyyy}.%i.log.gz">
    <PatternLayout>
        <pattern>%d %p %c{1.} [%t] %m%n</pattern>
    </PatternLayout>
    <SizeBasedTriggeringPolicy size="500" />
</RollingFile>

~ ~~ В этом образце файла конфигурации RollingFileAppender использует поиск по дате, где выходные данные будут в формате MM-dd-yyyy. В результате Log4j 2 записывает журналы в выходной файл с суффиксом даты.

Подобно другим плагинам, Log4j 2 предоставляет множество источников для поиска. Кроме того, это упрощает реализацию пользовательских поисков, если требуется новый источник:

@Plugin(name = "kafka", category = StrLookup.CATEGORY)
public class KafkaLookup implements StrLookup {

    @Override
    public String lookup(String key) {
        return getFromKafka(key);
    }

    @Override
    public String lookup(LogEvent event, String key) {
        return getFromKafka(key);
    }

    private String getFromKafka(String topicName) {
        return "topic1-p1";
    }
}

Таким образом, KafkaLookup будет разрешать значение, запрашивая тему Kafka. Теперь мы передаем имя темы из конфигурации:

<RollingFile name="Rolling-File" fileName="${filename}" 
  filePattern="target/rolling1/test1-$${kafka:topic-1}.%i.log.gz">
    <PatternLayout>
        <pattern>%d %p %c{1.} [%t] %m%n</pattern>
    </PatternLayout>
    <SizeBasedTriggeringPolicy size="500" />
</RollingFile>

Мы заменили поиск даты в нашем предыдущем примере поиском Kafka, который будет запрашивать тему-1.

Поскольку Log4j 2 вызывает только конструктор по умолчанию плагина поиска, мы не реализовали @PluginFactory, как в более ранних плагинах.

6. Обнаружение плагинов

Наконец, давайте разберемся, как Log4j 2 обнаруживает плагины в приложении. Как мы видели в примерах выше, мы дали каждому плагину уникальное имя. Это имя действует как ключ, который Log4j 2 преобразует в класс плагина.

Существует определенный порядок, в котором Log4j 2 выполняет поиск для разрешения класса подключаемого модуля:

  1. Serialized plugin listing file in the log4j2-core library. Specifically, a Log4j2Plugins.dat is packaged inside this jar to list the default Log4j 2 plugins
  2. Similar Log4j2Plugins.dat file from the OSGi bundles
  3. A comma-separated package list in the log4j.plugin.packages system property
  4. In programmatic Log4j 2 configuration, we can call PluginManager.addPackages() method to add a list of package names
  5. A comma-separated list of packages can be added in the Log4j 2 configuration file

В качестве предварительного условия необходимо включить обработку аннотаций, чтобы позволить Log4j 2 разрешать подключаемый модуль по имени, указанному в аннотации @Plugin.

Поскольку Log4j 2 использует имена для поиска плагина, вышеприведенный порядок становится важным. Например, если у нас есть два плагина с одинаковым именем, Log4j 2 обнаружит плагин, который разрешен первым. Поэтому, если нам нужно расширить существующий плагин в Log4j 2, мы должны упаковать плагин в отдельный jar и поместить его перед log4j2-core.jar.

7. Заключение

В этой статье мы рассмотрели широкие категории подключаемых модулей в Log4j 2. Мы обсудили, что даже при наличии исчерпывающего списка существующих подключаемых модулей нам может потребоваться реализовать собственные подключаемые модули для некоторых случаев использования. .

Позже мы рассмотрели пользовательскую реализацию некоторых полезных плагинов. Кроме того, мы видели, как Log4j 2 позволяет нам называть эти плагины и впоследствии использовать это имя плагина в файле конфигурации. Наконец, мы обсудили, как Log4j 2 разрешает плагины на основе этого имени.

Как всегда, все примеры доступны на GitHub.