1. Обзор
Регистрация событий является важным аспектом разработки программного обеспечения. Несмотря на то, что в экосистеме Java доступно множество фреймворков, Log4J был самым популярным на протяжении десятилетий благодаря гибкости и простоте, которые он обеспечивает.
Log4j 2 — это новая и улучшенная версия классической платформы Log4j.
В этой статье мы представим наиболее распространенные приложения, макеты и фильтры на практических примерах.
В Log4J2 приложение является просто местом назначения для событий журнала; она может быть простой, как консоль, а может быть сложной, как любая СУБД. Макеты определяют, как будут представлены журналы, а фильтры фильтруют данные в соответствии с различными критериями.
2. Настройка
Чтобы понять несколько компонентов ведения журнала и их конфигурацию, давайте настроим различные варианты использования тестов, каждый из которых состоит из файла конфигурации log4J2.xml
и тестового класса JUnit 4 .
Две зависимости maven являются общими для всех примеров:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
Помимо основного пакета log4j-core
, нам нужно включить «тестовую банку», принадлежащую пакету, чтобы получить доступ к правилу контекста, необходимому для тестирования файлов конфигурации с необычными именами.
3. Конфигурация по умолчанию
ConsoleAppender
— это конфигурация по умолчанию для основного пакета Log4J 2 .
Он записывает сообщения в системную консоль по простому шаблону:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="ConsoleAppender" target="SYSTEM_OUT">
<PatternLayout
pattern="%d [%t] %-5level %logger{36} - %msg%n%throwable"/>
</Console>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="ConsoleAppender"/>
</Root>
</Loggers>
</Configuration>
Давайте проанализируем теги в этой простой конфигурации XML:
Конфигурация
: корневой элемент файла конфигурацииLog4J 2 и
состояние
атрибута — это уровень внутренних событий Log4J, которые мы хотим регистрировать.Appenders
: этот элемент содержит один или несколько Appenders. Здесь мы настроим приложение, которое выводит на системную консоль стандартный вывод.Loggers
: этот элемент может состоять из нескольких сконфигурированных элементовLogger .
С помощью специальногокорневого
тега вы можете настроить безымянный стандартный логгер, который будет получать все лог-сообщения от приложения. Для каждого регистратора можно установить минимальный уровень ведения журнала.AppenderRef
: этот элемент определяет ссылку на элемент из разделаAppenders
. Поэтому атрибут 'ref
' связан с атрибутом appenders 'name
'.
Соответствующий модульный тест будет таким же простым. Мы получим ссылку на Logger
и напечатаем два сообщения:
@Test
public void givenLoggerWithDefaultConfig_whenLogToConsole_thanOK()
throws Exception {
Logger logger = LogManager.getLogger(getClass());
Exception e = new RuntimeException("This is only a test!");
logger.info("This is a simple message at INFO level. " +
"It will be hidden.");
logger.error("This is a simple message at ERROR level. " +
"This is the minimum visible level.", e);
}
4. ConsoleAppender
с PatternLayout
Давайте определим новый модуль консоли с настроенным цветовым шаблоном в отдельном XML-файле и включим его в нашу основную конфигурацию:
<?xml version="1.0" encoding="UTF-8"?>
<Console name="ConsoleAppender" target="SYSTEM_OUT">
<PatternLayout pattern="%style{%date{DEFAULT}}{yellow}
%highlight{%-5level}{FATAL=bg_red, ERROR=red, WARN=yellow, INFO=green}
%message"/>
</Console>
В этом файле используются некоторые переменные шаблона, которые во время выполнения заменяются на Log4J 2
:
%style{…}{colorname}
: это напечатает текст в первой паре квадратных скобок (…
) заданным цветом (colorname
).%highlight{…}{FATAL=colorname, …}
: это похоже на переменную «стиль». Но для каждого уровня журнала может быть задан свой цвет.%date{format}
: заменяется текущей датой в указанномформате
. Здесь мы используем формат даты и времени «ПО УМОЛЧАНИЮ»:«
гггг-ММ-дд ЧЧ:мм:сс,ССС»
.%-5level
: Выводит уровень сообщения журнала с выравниванием по правому краю.%message
: представляет необработанное сообщение журнала .
Но в PatternLayout
существует гораздо больше переменных и форматов .
Вы можете обратиться к официальной документации Log4J 2 .
Теперь мы включим определенный модуль консоли в нашу основную конфигурацию:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" xmlns:xi="http://www.w3.org/2001/XInclude">
<Appenders>
<xi:include href="log4j2-includes/
console-appender_pattern-layout_colored.xml"/>
</Appenders>
<Loggers>
<Root level="DEBUG">
<AppenderRef ref="ConsoleAppender"/>
</Root>
</Loggers>
</Configuration>
Модульный тест:
@Test
public void givenLoggerWithConsoleConfig_whenLogToConsoleInColors_thanOK()
throws Exception {
Logger logger = LogManager.getLogger("CONSOLE_PATTERN_APPENDER_MARKER");
logger.trace("This is a colored message at TRACE level.");
...
}
5. Асинхронное добавление файлов с JSONLayout
и BurstFilter
Иногда полезно записывать сообщения журнала асинхронно. Например, если производительность приложения имеет приоритет над доступностью журналов.
В таких случаях использования мы можем использовать AsyncAppender.
В нашем примере мы настраиваем асинхронный файл журнала JSON .
Кроме того, мы включим пакетный фильтр, который ограничивает вывод журнала с указанной скоростью:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
...
<File name="JSONLogfileAppender" fileName="target/logfile.json">
<JSONLayout compact="true" eventEol="true"/>
<BurstFilter level="INFO" rate="2" maxBurst="10"/>
</File>
<Async name="AsyncAppender" bufferSize="80">
<AppenderRef ref="JSONLogfileAppender"/>
</Async>
</Appenders>
<Loggers>
...
<Logger name="ASYNC_JSON_FILE_APPENDER" level="INFO"
additivity="false">
<AppenderRef ref="AsyncAppender" />
</Logger>
<Root level="INFO">
<AppenderRef ref="ConsoleAppender"/>
</Root>
</Loggers>
</Configuration>
Заметь:
- JSONLayout настроен
таким
образом, что записывает одно событие журнала в строку . - BurstFilter будет отбрасывать каждое событие с
уровнем
INFO и выше, если их более двух, но не более 10 отброшенных событий. - AsyncAppender
настроен
на буфер из 80 сообщений журнала; после этого буфер сбрасывается в лог-файл
Давайте взглянем на соответствующий модульный тест. Мы заполняем добавленный буфер в цикле, позволяем ему писать на диск и проверяем количество строк в файле журнала:
@Test
public void givenLoggerWithAsyncConfig_whenLogToJsonFile_thanOK()
throws Exception {
Logger logger = LogManager.getLogger("ASYNC_JSON_FILE_APPENDER");
final int count = 88;
for (int i = 0; i < count; i++) {
logger.info("This is async JSON message #{} at INFO level.", count);
}
long logEventsCount
= Files.lines(Paths.get("target/logfile.json")).count();
assertTrue(logEventsCount > 0 && logEventsCount <= count);
}
6. Приложение
RollingFile и XMLLayout
Далее мы создадим скользящий файл журнала. После заданного размера файла файл журнала сжимается и ротируется.
На этот раз мы используем макет XML :
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<RollingFile name="XMLRollingfileAppender"
fileName="target/logfile.xml"
filePattern="target/logfile-%d{yyyy-MM-dd}-%i.log.gz">
<XMLLayout/>
<Policies>
<SizeBasedTriggeringPolicy size="17 kB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="XML_ROLLING_FILE_APPENDER"
level="INFO" additivity="false">
<AppenderRef ref="XMLRollingfileAppender" />
</Logger>
<Root level="TRACE">
<AppenderRef ref="ConsoleAppender"/>
</Root>
</Loggers>
</Configuration>
Заметь:
- Приложение
RollingFile
имеет атрибут «filePattern», который используется для именования файлов журналов с ротацией и может быть настроен с помощью переменных-заполнителей. В нашем примере он должен содержать дату и счетчик перед суффиксом файла. - Конфигурация
XMLLayout
по умолчанию будет записывать отдельные объекты событий журнала без корневого элемента. - Мы используем политику на основе размера для ротации наших файлов журналов.
Наш класс модульного теста будет выглядеть так же, как в предыдущем разделе:
@Test
public void givenLoggerWithRollingFileConfig_whenLogToXMLFile_thanOK()
throws Exception {
Logger logger = LogManager.getLogger("XML_ROLLING_FILE_APPENDER");
final int count = 88;
for (int i = 0; i < count; i++) {
logger.info(
"This is rolling file XML message #{} at INFO level.", i);
}
}
7. Приложение
системного журнала
Допустим, нам нужно отправить зарегистрированное событие на удаленную машину по сети. Самый простой способ сделать это с помощью Log4J2 - использовать его Syslog Appender:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
...
<Syslog name="Syslog"
format="RFC5424" host="localhost" port="514"
protocol="TCP" facility="local3" connectTimeoutMillis="10000"
reconnectionDelayMillis="5000">
</Syslog>
</Appenders>
<Loggers>
...
<Logger name="FAIL_OVER_SYSLOG_APPENDER"
level="INFO"
additivity="false">
<AppenderRef ref="FailoverAppender" />
</Logger>
<Root level="TRACE">
<AppenderRef ref="Syslog" />
</Root>
</Loggers>
</Configuration>
Атрибуты в теге Syslog :
name
: определяет имя приложения и должно быть уникальным. Поскольку у нас может быть несколько приложений Syslog для одного и того же приложения и конфигурации.format
: он может быть установлен в BSD или RFC5424, и записи Syslog будут отформатированы соответствующим образом.host & port
: имя хоста и порт удаленного сервера Syslog.протокол
: использовать ли TCP или UPDсредство
: в какое средство системного журнала будет записано событиеconnectTimeoutMillis
: время ожидания установленного соединения, по умолчанию равно нулюreconnectionDelayMillis
: время ожидания перед повторной попыткой подключения
8. FailoverAppender
Теперь могут быть случаи, когда один из приложений не может обработать события журнала, и мы не хотим терять данные. В таких случаях FailoverAppender
пригодится.
Например, если приложение Syslog
не может отправить события на удаленный компьютер, вместо потери этих данных мы можем временно вернуться к FileAppender
.
FailoverAppender принимает первичный присоединитель
и ряд вторичных присоединителей. В случае сбоя основного, он пытается обработать событие журнала с вторичными по порядку, пока один из них не завершится успехом или пока не останется вторичных, которые можно было бы попробовать:
<Failover name="FailoverAppender" primary="Syslog">
<Failovers>
<AppenderRef ref="ConsoleAppender" />
</Failovers>
</Failover>
Давайте проверим это:
@Test
public void givenLoggerWithFailoverConfig_whenLog_thanOK()
throws Exception {
Logger logger = LogManager.getLogger("FAIL_OVER_SYSLOG_APPENDER");
Exception e = new RuntimeException("This is only a test!");
logger.trace("This is a syslog message at TRACE level.");
logger.debug("This is a syslog message at DEBUG level.");
logger.info("This is a syslog message at INFO level.
This is the minimum visible level.");
logger.warn("This is a syslog message at WARN level.");
logger.error("This is a syslog message at ERROR level.", e);
logger.fatal("This is a syslog message at FATAL level.");
}
9. Приложение JDBC
Приложение JDBC отправляет события журнала в СУБД, используя стандартный JDBC. Соединение можно получить либо с помощью любого источника данных JNDI, либо с помощью любой фабрики соединений.
Базовая конфигурация состоит из DataSource
или ConnectionFactory
, ColumnConfigs и tableName:
<JDBC name="JDBCAppender" tableName="logs">
<ConnectionFactory
class="com.foreach.logging.log4j2.tests.jdbc.ConnectionFactory"
method="getConnection" />
<Column name="when" isEventTimestamp="true" />
<Column name="logger" pattern="%logger" />
<Column name="level" pattern="%level" />
<Column name="message" pattern="%message" />
<Column name="throwable" pattern="%ex{full}" />
</JDBC>
Теперь давайте попробуем:
@Test
public void givenLoggerWithJdbcConfig_whenLogToDataSource_thanOK()
throws Exception {
Logger logger = LogManager.getLogger("JDBC_APPENDER");
final int count = 88;
for (int i = 0; i < count; i++) {
logger.info("This is JDBC message #{} at INFO level.", count);
}
Connection connection = ConnectionFactory.getConnection();
ResultSet resultSet = connection.createStatement()
.executeQuery("SELECT COUNT(*) AS ROW_COUNT FROM logs");
int logCount = 0;
if (resultSet.next()) {
logCount = resultSet.getInt("ROW_COUNT");
}
assertTrue(logCount == count);
}
10. Заключение
В этой статье показаны очень простые примеры того, как вы можете использовать различные приложения для журналирования, фильтры и макеты с Log4J2, а также способы их настройки.
Примеры, сопровождающие статью, доступны на GitHub .