Логирование с Slf4j и Logback

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

Картинка с https://xkcd.ru/927/, наглядно демонстрирующая путь Java в части логирования

В прошлых статьях я описывал кучу различных библиотек логирования: System.err, JUL, Log4j 1.2, Apache Commons Logging, Log4j 2. В новых приложениях, как правило, ни один из них не используется. Сейчас правильным подходом считается использование API Slf4j и его реализации Logback.

Но что делать со всем старым кодом? Мы же не можем просто выбросить то огромное количество логеров и библиотек, которое уже существует. Для них нужно подключать специальные зависимости, содержащие их API, но вместо реализации перенаправляющие вывод в Slf4j:

  • jcl-over-slf4j.jar содержит в себе API от Apache Commons Logging, но вместо его реализации просто перенаправляет все вызовы в Slf4j.
  • log4j-over-slf4j.jar содержит в себе API от Log4j, но вместо его реализации перенаправляет все вызовы в Slf4j.
  • jul-to-slf4j.jar содержит в себе обработчик (Handler) для JUL, который пишет все сообщения в Slf4j. Так как JUL встроен в JDK, то заменить его как в случае Apache Commons Logging и Log4j мы не можем, именно поэтому мы просто добавляем новый Handler.

Кроме вышеперечисленных зависимостей, перенаправляющих в Slf4j с API других библиотек, существуют зависимости, которые наоборот реализуют API Slf4j:

  • slf4j-log4j12.jar перенаправляет вызовы Slf4j в Log4j12, то есть позволяет использовать Log4j 1.2 в качестве реализации API Slf4.
  • slf4j-jdk14.jar перенаправляет вызовы Slf4j в JUL, то есть позволяет использовать JUL в качестве реализации API Slf4j.
  • slf4j-nop.jar просто игнорирует все вызовы Slf4j, что равносильно полному отключению логов.
  • slf4j-simple.jar перенаправляет вызовы Slf4j в System.err.
  • slf4j-jcl.jar перенаправляет вызовы Slf4j в Apache Commons Logging, то есть позволяет использовать Apache Commons Logging в качестве реализации API Slf4j. Самое интересное в этом случае то, что Apache Commons Logging тоже является лишь обёрткой с API, перенаправляющей выводы в другие реализации…
  • logback-classic.jar — это библиотека логирования, напрямую реализующая API Slf4j. В современных приложениях, как правило, используют именно её.

Надеюсь, я вас не запутал. Итак, что нам нужно сделать, чтобы использовать связку Slf4j и Logback:

  1. Подключить slf4j-api.
  2. Подключить logback-classic.
  3. Подключить jcl-over-slf4j, log4j-over-slf4j, чтобы сообщения логов от зависимостей, которые используют Apache Commons Logging и Log4j перенаправлялись в Slf4j. Можно ещё подключить jul-to-slf4j, но это не рекомендуется, так как от него сильно падает производительность.
  4. Из всех других подключаемых зависимостей убирать с помощью exclude в Maven зависимость от конкретной библиотеки логирования.
  5. Настроить Logback.
  6. Использовать slf4j-api для записи логов.

Давайте сделаем простое приложение с использованием Slf4j. Создайте новый проект Maven. Добавьте туда зависимость от Logback и Slf4j-api:

Logback сначала пытается читать конфигурацию из “logback-test.xml” в classpath, затем из “logback.groovy” в classpath, а затем из “logback.xml” в classpath.

Файл “logback-test.xml” обычно создают в “src/test/resources”, чтобы иметь отдельную конфигурацию для тестов, а файл “logback.xml” обычно создают в “src/main/resources” как основную конфигурацию логирования. Создадим простой файл “logback.xml”:

Мы просто указываем уровень debug для корневого логера, а затем подключаем к нему appender, который будет писать в консоль. В pattern мы указываем, что в лог нужно писать сначала дату с временем %d{dd.MM.yyyy HH:mm:ss.SSS}, потом название потока [%thread], затем пять символов уровеня логирования %-5level, затем название логера, пытаясь уместить его в 36 символов, %logger{36}, затем сообщение логера %msg и перевод строки %n.

Напишем простой класс, использующий связку Slf4j и Logback:

Обратите внимание, что мы используем классы из Slf4j, а не из библиотеки Logback. Сначала мы получаем логгер:

Затем выводим два тестовых сообщения в лог:

А дальше воспользуемся достижением Slf4j: мы выводем в лог строку с параметром:

Вместо {} в выходную строку будет подставляться FILENAME. Раньше, например в Apache Commons Logging, мы использовали String.format, но это приводит к дополнительным расходам, так как String.format вычисляется даже в том случае, когда в реальности строка в лог не попадёт из-за выбранного уровня логирования. Например, если мы пишем сообщение с уровнем DEBUG, а в конфигурации настроено, что в лог нужно выводить только INFO.

В случае же Slf4j мы указываем {} в тех местах, куда нужно подставить параметры при выводе лога, а затем передаём нужные значение в последующих параметрах метода. Последним параметром идёт, как правило, само исключение ( ioex).

Теперь немного модифицируем файл конфигурации, чтобы добавить вывод сообщений лога в файл:

Мы добавили ещё один appender с именем FILE, в качестве реализации appender-а выбрали RollingFileAppender и настроили его на то, чтобы он создавал новый файл каждый день, максимально хранил логи 10 дней и до 30 гигабайт.

Если мы сейчас запустим наш класс Slf4jLogbackExampleApp, то в консоли и в файле “slf4jlogbackexample.log” увидим следующее содержимое:

Отлично. Логирование работает. Теперь потренируемся в настраивании bridge-ей, который будут перенаправлять логи из других логеров в slf4j. Для этого нам нужно подключить какую-нибудь внешнюю библиотеку, которая вместо slf4j использует в качестве логирования что-то другое. Я сходу не смог найти что-то подобное, поэтому создал сам. Создайте новый проект Maven с “pom.xml”:

Затем создайте класс “Slf4jLogbackCommonsLoggingExample”:

Мы просто пишем пару сообщений в лог. Соберём этот модуль и положим его в локальный репозиторий Maven:

Теперь в наш основной проект slf4j-logback-example добавим новую зависимость:

Подключая её мы заодно получаем Apache Commons Logging в нашем classpath, что можно легко увидеть в дереве зависимостей:

В листинге выше на 17 строке видно, что заодно мы получили зависимость от commons-logging. Нам нужно её убрать. В нашем “pom.xml” меняем подключение зависимости slf4jlogback-commons-logging-example вот так:

Посмотрим дерево зависимостей снова:

Уже лучше. Но раз мы убрали Apache Commons Logging, то наш slf4jlogback-commons-logging-example не сможет найти классы org.apache.commons.logging.Log и org.apache.commons.logging.LogFactory. Что же нам делать? Мы должны подключить зависимость jcl-over-slf4j, она содержит нужные классы, но вместо настоящей реализации Apache Commons Logging просто перенаправляет вызовы в Slf4j:

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

Если мы запустим это на выполнение, то в консоли и в файле “slf4jlogbackexample.log” мы увидим следующие строки:

С Apache Commons Logging разобрались. Теперь разберём ситуацию, когда мы подключаем внешнюю библиотеку, использующую Log4j. Создадим эту библиотеку вручную. Файл “pom.xml”:

Напишем небольшой класс, который пишет что-нибудь в Log4j 1.2 (по этой библиотеку логирования у меня есть отдельная статья):

Соберём проект и положим его в локальный репозиторий Maven:

Подключим собранный проект к нашему основному проекту:

Построим дерево зависимостей и убедимся, что к нам в classpath заодно попадёт Log4j 1.2:

На 19 строке явно видно, что у нас в зависимостях подтянулся нежелательный Log4j. Его нужно убрать, а вместо него добавить log4j-over-slf4j:

В классе Slf4jLogbackExampleApp вызовем метод из добавленной библиотеки:

Запустим класс Slf4jLogbackExampleApp и увидим строки из Slf4jLogbackLog4jExample:

Конечный вариант файла “Slf4jLogbackExampleApp.java”:

Конечный вариант файла “pom.xml” от slf4j-logback-example:

В результате логирование в нашем проекте можно отразить следующей схемой:

Slf4j Logback example
Slf4j Logback example

Вот такая ситуация с логированием/журналированием в Java на текущий момент.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *