Здравствуйте, уважаемые Хабровчане.
Изложенный в статье материал предназначен для новичков и, возможно, как и мне сэкономит несколько часов поисков на StackOverFlow и других сайтах с целью получить удобную систему логирования, которая сама поймет куда логировать — в консоль, файл или logstash.
На старте проекта всегда возникает задача правильно настроить логирование, при этом на локальном окружении логи должны выводиться в консоль и в файл для удобства в процессе отладки, а на удаленном сервере выводить логи в консоль крайне не желательно, но вместо этого их надо писать в файл, logstash или в БД. А если у вас локально Windows, а на удаленной машине Linux, то отличается и адрес расположения этого файла с логами.
Таким образом возникает немало ситуаций, в которых нужно руками постоянно править конфигурацию системы логирования в зависимости от внешних обстоятельств.
Постоянно помнить и комментировать appender'ы в своё время мне надоело и я выработал способ настройки Log4j2 так, чтобы в зависимости от выбранного Maven-профиля автоматически включались только нужные appender'ы.
Ниже приведена инструкция по настройке проекта с использованием Spring Boot + Maven + Log4j2, результатом выполнения которой будет настроенная система логирования и два appender'а: CONSOLE и SOCKET.
В первую очередь внесём изменения в конфигурацию Maven (pom.xml):
В переменные на уровне всего pom.xml добавляем адрес host для logstash-appender:
<properties>
<logstash.host>logstashcsm.example.ru</logstash.host>
</properties>
Добавляем необходимые для работы зависимости:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
</dependencies>
Обратите внимание, что из spring-boot-starter-web мы исключаем зависимость spring-boot-starter-logging.
Настроим Maven-профили, чтобы динамически управлять подключенными appender'ами. На уровне каждого профиля задаем переменные logstash.port, logger.console.tresholdFilter, logger.socket.tresholdFilter
.
<profiles>
<profile>
<id>local</id>
<properties>
<logstash.port>10000</logstash.port>
<logger.console.tresholdFilter>ALL</logger.console.tresholdFilter>
<logger.socket.tresholdFilter>OFF</logger.socket.tresholdFilter>
</properties>
</profile>
<profile>
<id>dev</id>
<properties>
<logstash.port>10001</logstash.port>
<logger.console.tresholdFilter>OFF</logger.console.tresholdFilter>
<logger.socket.tresholdFilter>ALL</logger.socket.tresholdFilter>
</properties>
</profile>
</profiles>
logstash.port — порт, на который нужно отправлять логи.
logger.console.tresholdFilter — значение задаёт уровень фильтрации логов, выводимых на консоль. В нашем случае ALL означает, что лог-записи всех уровней будут выводиться в консольный аппендер.
logger.socket.tresholdFilter — значение задает уровень фильтрации логов, которые отправляются в logstash. OFF — означает, что никакие записи отправлены в этот аппендер не пройдут.
Теперь нам необходимо внести изменения в application.properties, чтобы из файла Log4j2.xml можно было получить доступ к значению переменных, указанных в pom.xml:
logstash.host=@logstash.host@
logstash.port=@logstash.port@
logger.console.tresholdFilter=@logger.console.tresholdFilter@
logger.socket.tresholdFilter=@logger.socket.tresholdFilter@
И, наконец, настраиваем конфигурацию самого Log4j2 в файле log4j2.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Properties>
<Property name="socket.host">${bundle:application:logstash.host}</Property>
<Property name="socket.port">${bundle:application:logstash.port}</Property>
<Property name="console.thresholdFilter">${bundle:application:logger.console.tresholdFilter}</Property>
<Property name="socket.thresholdFilter">${bundle:application:logger.socket.tresholdFilter}</Property>
</Properties>
<Appenders>
<Console name="CONSOLE" target="SYSTEM_OUT">
<ThresholdFilter level="${console.thresholdFilter}"/>
<PatternLayout pattern="%d %-5p [%t] %c{10} - %m%n"/>
</Console>
<Socket name="SOCKET" host="${socket.host}" port="${socket.port}" immediateFlush="true">
<ThresholdFilter level="${socket.thresholdFilter}"/>
<JSONLayout eventEol="true" compact="true"/>
</Socket>
<Async name="ASYNC">
<AppenderRef ref="CONSOLE"/>
<AppenderRef ref="SOCKET"/>
</Async>
</Appenders>
<Loggers>
<Logger name="ru.example" level="debug" additivity="false">
<AppenderRef ref="ASYNC"/>
</Logger>
<Root level="error">
<AppenderRef ref="ASYNC"/>
</Root>
</Loggers>
</Configuration>
Теперь, чтобы вызвать логер в своём классе необходимо создать его экземпляр:
private static Logger logger = LoggerFactory.getLogger(YourClass.class);
и обратиться к нему:
logger.info("Эта запись будет залогирована");
Вот и всё. Теперь логирование будет работать с учетом Maven-профиля, активного в проекте.
Обратите внимание, что в листинге файла log4j2.xml
определено два логера — ru.example
и root
, и у них разные уровни логирования событий. Первый будет работать на все события, порожденные классами из пакета ru.example.*
и логировать всё от уровня DEBUG, а root
логер будет фиксировать события из абсолютно всех классов, но с уровня ERROR.
При этом, чтобы записи не дублировались в логах, используется настройка additivity="false"
.
Комментарии (9)
AstarothAst
04.06.2018 09:59-1А в чем удобство? Вы описали обычную настройку логгера, logback настраивается примерно так же, с той разницей, что, скорее всего, отдельно подключать его не понадобится, поскольку он идет прицепом за spring-boot-starter-web (https://docs.spring.io/spring-boot/docs/current/reference/html/howto-logging.html). Настройка аппендеров ничуть не сложнее, разве что xml'ник называется по другому. А насчет
private static Logger logger = LoggerFactory.getLogger(YourClass.class);
я сейчас скажу, как сделать действительно удобно. Во-первых подключите lombok. Во-вторых не слушайте эстетов, которые при слове lombok начинают шипеть и плеваться ядом. В-третьих в lombok.config напишите:
lombok.log.fieldName=logger
Готово, теперь достаточно поставить аннотацию @Slf4j, и можно использовать ту самую переменную logger.ivanglushnev Автор
04.06.2018 11:37+11. удобство в том, что при выборе профиля в Maven автоматически определяются нужные appender'ы, вся суть в этом;
2. за трюк с lombok спасибо;
cepreu4habr
06.06.2018 08:29Подскажите, а что за решение вы используете для подстановки maven properties в файл *.properties? Я встречал решение с
<resource> <filtering>true</filtering>
Но у него было бы другое содержимоеapplication.properties
. Типа такого:
logstash.host=${logstash.host}
Может это я отстал от жизни?ivanglushnev Автор
06.06.2018 10:00Это фича Spring Boot, которая позволяет без использования maven-resource-plugin расширить property файл значениями из Maven или Gradle конфигурации.
Вот тут подробнее:
https://docs.spring.io/spring-boot/docs/current/reference/html/howto-properties-and-configuration.html#howto-automatic-expansion-maven
Prototik
А чем продиктован выбор log4j2 вместо slf4j + logback?
ivanglushnev Автор
Попадались статьи, в которых он показывал лучший результат по производительности. Ну и с Log4j2 отношения у меня сложились лучше в процессе его конфигурации, было меньше проблем, чем с logback.
Prototik
Если дело в logback, то тогда почему не slf4j over log4j2? Просто подход slf4j очень классный — мы даём вам api для логирования, а движок это вообще не наше дело. И slf4j стал уже негласным стандартом для множества проектов...
AstarothAst
По производительности чего? Записи в лог на единицу времени? Если логгирование бьет по производительности системы, то, думаю, нужно не логгеры менять, а присмотреться к консерватории — что-то там не так…
YuryB
«slf4j» не имеет всех фишек, что есть у log4j2, + лишняя прослойка в которой разные вещи могут быть реализованы не очень то и оптимально, к примеру log4j2 может вообще не делать мусор если вы логируете сообщения длиной около 460 символов, есть штука типа маркеров (не знаю есть ли такое в slf4j), lazy получение параметров через лямбды. slf4j имеет смысл использовать если вы пишете какую-то библиотеку и не знаете кем, где и с какими логерами она будет использоваться, а если вы пишете что-то лично для себя то можно не заморачиваться с использование «all-size-fits-on» решением