Перейти к основному содержимому

Log4j 2 Плагины

· 8 мин. чтения

1. Обзор

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

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

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

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

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

  1. Основные плагины
  2. Преобразователи
  3. Ключевые поставщики
  4. Поиски
  5. Преобразователи типов

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.foreach" 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="foreach" 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, хочет печатать имя контейнера с каждым оператором журнала. Для этого мы реализуем DockerPatterConverter и изменим приведенную выше конфигурацию, включив в нее строку преобразования:

@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.foreach" 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.foreach.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. Сериализованный файл со списком подключаемых модулей в библиотеке log4j2-core . В частности, внутри этой банки упакован файл Log4j2Plugins.dat , содержащий список подключаемых модулей Log4j 2 по умолчанию.
  2. Аналогичный файл Log4j2Plugins.dat из пакетов OSGi
  3. Список пакетов через запятую в системном свойстве log4j.plugin.packages.
  4. В программной конфигурации Log4j 2 мы можем вызвать метод PluginManager.addPackages() , чтобы добавить список имен пакетов.
  5. Список пакетов через запятую можно добавить в файл конфигурации Log4j 2.

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

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

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

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

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

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