Slf4j настройка логирования в файл
В ходе моей работы в компании DataArt я, в числе прочего, занимаюсь менторской деятельностью. В частности это включает в себя проверку учебных заданий сделанных практикантами. В последнее время в заданиях наметилась тенденция «странного» использования логеров. Мы с коллегами решили включить в текст задания ссылку на статью с описанием java logging best practices, но оказалось, что такой статьи в которой бы просто и без лишних деталей на практике объяснялось бы как надо писать в лог на Java, вот так вот с ходу не находится.
Данная статья не содержит каких-то откровений, в ней не рассматриваются тонкости какого либо из многочисленных java logging frameworks. Здесь рассказываю как записать в лог так, чтобы это не вызвало удивления у Ваших коллег, основная цель написания включить ее в список обязательного чтения для практикантов. Если все еще интересно, читайте дальше
- Весь код примеров использует java.util.logging framework. Вопрос «Какой из фреймворков логирования ниболее кошерен» я оставлю за кадром. Скажу только что до java.util.logging проще всего дотянуться ибо он уже идет вместе с JRE и на самом деле рассказанное в данной статье с минимальными косметическими правками верно для подавляющего большинства систем логирования.
- В целом рецепты приведенные в данной статье не являются единственно верными, есть моменты о которых можно поспорить, но в целом эти рецепты используются многие годы, многими разработчиками, во многих проектах и они достаточно хороши чтобы им следовать если у Вас нет каких-то совсем уже серьезных возражений.
- В статье не рассматриваются такие «продвинутые» топики как:
- Конфигурирование уровней для отдельных логеров
- Форматирования логов
- Асинхронное логирование
- Создание собственных уровней логирования в Log4J
- Контекстное логирование
- И многое другое
Пример №1
Хорошо
- Логер это статическое поле класса инициализируемое при загрузке класса, имеет простое, короткое имя, важно чтобы во всех Ваших классах переменная логера называлась одинаково (это диктуется общим правилом, одинаковые вещи в программе должны делаться одинаковым образом).
- В качестве имени логера я использую имя класса, на самом деле это не единственный способ, можно пытаться организовать какую-то свою иерархию логирования (например transport layer/app layer для подсистем имеющих дело с обменом данными), но как показывает практика выдумывать и главное потом неукоснительно следовать такой иерархии крайне сложно, а вариант с именами логеров совпадающими с именами классов весьма хорош и используется в 99% проектов
- Здесь для записи в лог я использую короткий метод .info, а не более общий метод .log, так много лаконичнее
- Имя логера берется как SomeClass.class.getName(), а не как «com.dataart.demo.java.logging.SomeClass», оба способа по идее одинаковы, но первый защищает Вас от сюрпризов при рефакторинге имени/пакета класса
Плохо
По сути тоже самое но букв больше и читается не так легко.Замечание между примерами
Пример №2
Хорошо
Плохо
Если логировать только ex.toString(), то потом Вы не сможете понять в какой строке изначально сработало исключение.Пример №3
Какие тут есть варианты
По умолчанию: Файл logging.properties для уровня INFO, вывод в консоль
создаст вот такие файлы (последняя колонка — размер в байтах)
Мы указали максимальный размер 50 байтов, в реальной жизни надо скорее указывать не меньше мегабайта, например вот так (я знаю, что 1000000 это чуть меньше мегабайта, но кому охота по памяти писать 1048576, если суть дела это фактически не меняет)
copy & paste конфиг для реальной жизни, его вполне хватает для большинства service, console и desktop приложений.
Последняя часть магии
- Из командной строки запуска приложения
- В первых строчках кода Вашего приложения
Первый чуть более правильный ибо он декларативный и работает сразу, до того как начал работать код Вашего приложения.
Вот так
java Djava.util.logging.config.file=logging.properties com.dataart.application.ClassName
Но к сожалению менять строку запуска не всегда можно или не всегда удобно. Второй способ тоже неплохо работает.
Java существует уже много лет. В этом языке программирования огромное количество древнего наследия, особенностей и запутывающих новичков костылей. В первых версиях Java не было нормальной системы логирования, что привело к настоящему кошмару и порождению целого моря несовместимых друг с другом стандартов и библиотек, решающих одну и ту же задачу.
В прошлых статьях я описывал кучу различных библиотек логирования: System.err, JUL, Log4j 1.2, Apache Commons Logging, Log4j 2. В новых приложениях, как правило, ни один из них не используется. Сейчас правильным подходом считается использование API Slf4j и его реализации Logback.
Но что делать со всем старым кодом? Мы же не можем просто выбросить то огромное количество логеров и библиотек, которое уже существует. Для них нужно подключать специальные зависимости, содержащие их API, но вместо реализации перенаправляющие вывод в Slf4j:
Кроме вышеперечисленных зависимостей, перенаправляющих в 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:
Давайте сделаем простое приложение с использованием Slf4j. Создайте новый проект Maven. Добавьте туда зависимость от Logback и Slf4j-api:
Давайте разберем реальные случаи, в которых логирование решало бы проблему. Вот пример из моей работы. Есть точки приложений, которые интегрируются с другими сервисами. Я использую логирование этих точек для “алиби”: если интеграция не сработает, будет легко разобраться, с какой стороны возникла проблема. Еще желательно логировать важную информацию, которая сохраняется в базу данных. Например создание пользователя администратора. Это как раз то, что хорошо бы логировать.
Инструменты для логирования в Java
- log4j
- JUL — java.util.logging
- JCL — jakarta commons logging
- Logback
- SLF4J — simple logging facade for java
System.err.println
Первым и самым примитивным способом логгирования был метод System.err.println. Думаю, комментарии излишние, достаточно взглянуть на приведенный ниже код:
JUL — java.util.logging
Одно из ключевых преимуществ это решения — JUL включен в JDK (Java development kit). К сожалению, при его разработке за основу взяли не популярный log4j, а решение от IBM, что и повлияло на его развитие. По факту на данный момент JUL есть, но им никто не пользуется. Из “такого себе”: в JUL уровни логирования отличаются от того, что есть в Logback, Log4j, Slf4j, и это ухудшает понимание между ними. Создание логгера более менее похожее. Для этого нужно сделать импорт: Имя класса специально передается для того, чтобы знать, откуда идет логирование. Начиная с Java 8, можно передавать Supplier
. Это помогает считать и создавать строку только в тот момент, когда это действительно нужно, а не каждый раз, как это было до этого. Только с выходом Java 8 разработчики решили важные проблемы, после чего JUL по-настоящему стало возможно в использовании. А именно, методы с аргументом Supplier msgSupplier , как показано ниже: Commons-logging
Довольно старый проект, который представляет собой обертку над JUL и log4j, не привносящая никакого дополнительного функционала. Уровни логгирования у JCL совпадают с log4j, а в случае взаимодействия с JUL происходит следующее сопоставление:
Для использования JCL подключаем commons-logging-1.x.jar. Создаем логгер вызовом метода фабрики:
Методы JCL очень простые, совпадают с названием уровней логгирования, принимают только объекты и исключения и имеют две вариации:
Указать файл конфигурации JCL можно следующим образом:Популярные ошибки в логировании
- Избыток логирования. Не стоит логировать каждый шаг, который чисто теоретически может быть важным. Есть правило: логи могут нагружать работоспособность не более, чем на 10%. Иначе будут проблемы с производительностью.
- Логирование всех данных в один файл. Это приведет к тому, что в определенный момент чтение/запись в него будет очень сложной, не говоря о том, что есть ограничения по размеру файлов в определенных системах.
- Использование неверных уровней логирования. У каждого уровня логирования есть четкие границы, и их стоит соблюдать. Если граница расплывчатая, можно договориться какой из уровней использовать.
Log4j
Данный фреймворк на текущий момент имеет уже вторую версию, которая увы не совместима с первой. Поскольку первая версия log4j существует достаточно давно и, в виду ее большой популярности, существует не мало статей на просторах интернета, сегодня мы рассмотрим вторую. Для использования log4j2 вам необходимо подключить библиотеки log4j-api-2.x и log4j-core-2.x. Log4j имеет несколько отличное от JUL именование уровней логгирования: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, а так же ALL и OFF включающий и отключающий все уровни соответственно.
Логгер создается вызовом статического метода класса org.apache.logging.log4j.Logger:
Логгер умеет принимать помимо привычных нам String, Object и Throwable еще два новых типа — MapMessage и Marker:
В классическом для логгеров стиле методы делятся на два типа: совпадающие с названием уровня логгирования и методы log, принимающие уровень логгирования в качестве параметра. Первые имеют вид:
Методы log в log4j2 выглядят так:- BurstFilter
- CompositeFilter
- DynamicThresholdFilter
- MapFilter
- MarkerFilter
- RegexFilter
- StructuredDataFilter
- ThreadContextMapFilter
- ThresholdFilter
- TimeFilter
- AsyncAppender
- ConsoleAppender
- FailoverAppender
- FileAppender
- FlumeAppender
- JDBCAppender
- JMSAppender
- JPAAppender
- MemoryMappedFileAppender
- NoSQLAppender
- OutputStreamAppender
- RandomAccessFileAppender
- RewriteAppender
- RollingFileAppender
- RollingRandomAccessFileAppender
- RoutingAppender
- SMTPAppender
- SocketAppender
- SyslogAppender
Пошаговая настройка Log4j.properties
Думаю, ни для кого не секрет, что такое логгеры и для чего они нужны. За время существования java было создано немало фреймворков логгирования. Среди самых известных можно выделить:
- JUL — java.util.logging
- log4j
- JCL — jakarta commons logging
- Logback
- SLF4J — simple logging facade for java
Logback
- улучшена производительность;
- добавлена нативная поддержка slf4j;
- расширена опция фильтрации.
Запись и отправка логов: Appender
- для записи в файл — решение DailyRollingFileAppender;
- для получения данных в консоль приложения — ConsoleAppender;
- для записи логов в базу данных — JDBCAppender;
- для контроля передачи через TCP/IP — TelnetAppender;
- для того, чтобы запись логов не била по быстродействию — AsyncAppender.
Хочу узнать больше
- JUL — читать Java Logging: Logger и Java Logging: Configuration
- Log4j2 — читать Welcome to Log4j 2
- JCL- читать How to use Commons Logging
- Logback- читать The logback manual
- SLF4J — читать SLF4J documentation и Bridging legacy APIs
Что нужно логировать
- Начало/конец работы приложения. Нужно знать, что приложение действительно запустилось, как мы и ожидали, и завершилось так же ожидаемо.
- Вопросы безопасности. Здесь хорошо бы логировать попытки подбора пароля, логирование входа важных юзеров и т.д.
- Некоторые состояния приложения. Например, переход из одного состояния в другое в бизнес процессе.
- Некоторая информация для дебага, с соответственным уровнем логирования.
- Некоторые SQL скрипты. Есть реальные случаи, когда это нужно. Опять-таки, умелым образом регулируя уровни, можно добиться отличных результатов.
- Выполняемые нити(Thread) могут быть логированы в случаях с проверкой корректной работы.
SLF4J
Для того, что бы мост заработал необходимо выполнить код:Java.util.logging
Данный фреймворк включен в стандарт и поставляется вместе с JDK, поэтому ничего дополнительно скачивать и подключать вам не надо. JUL имеет следующие уровни логгирования по возрастанию: FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, а так же ALL и OFF, включающий и отключающий все уровни соответственно.
Логгер создается вызовом одного из статических методов класса java.util.logging.Logger:
Вторая группа методов имеет следующие вариации:
Для того что бы JUL применил данную конфигурацию нужно передать параметр -Djava.util.logging.config.file = , либо при старте приложения выполнить код:Logback
Данный фреймворк используется только в связке с оберткой SLF4J, которую мы будем рассматривать позднее. Для начала работы вам необходимы logback-core-1.x.jar и logback-classic-1.x.x.jar, а также slf4j-api-1.x.x.jar.
Взаимодействие с логгером мы будем осуществлять через API предоставляемый оберткой SLF4J. Уровни логгирования совпадают с log4j. Создание логгера в таком случае выглядит следующим образом:
Названия методов совпадают с уровнями логгирования и имеют вид:System.err.println
Первоначально был, разумеется, System.err.println (вывод записи в консоль). Его и сейчас используют для быстрого получения лога при дебаге. Конечно, говорить о каких-то настройках здесь не приходится, поэтому просто запомним его и пойдем дальше.
Уровни логирования
- OFF: никакие логи не записываются, все будут проигнорированы;
- FATAL: ошибка, после которой приложение уже не сможет работать и будет остановлено, например, JVM out of memory error;
- ERROR: уровень ошибок, когда есть проблемы, которые нужно решить. Ошибка не останавливает работу приложения в целом. Остальные запросы могут работать корректно;
- WARN: обозначаются логи, которые содержат предостережение. Произошло неожиданное действие, несмотря на это система устояла и выполнила запрос;
- INFO: лог, который записывает важные действия в приложении. Это не ошибки, это не предостережение, это ожидаемые действия системы;
- DEBUG: логи, необходимые для отладки приложения. Для уверенности в том, что система делает именно то, что от нее ожидают, или описания действия системы: “method1 начал работу”;
- TRACE: менее приоритетные логи для отладки, с наименьшим уровнем логирования;
- ALL: уровень, при котором будут записаны все логи из системы.
Заключение
В заключение хотелось бы вам сказать, что конечный выбор фреймворка логгирования остается всегда за вами, но к этому надо подходить здраво. Выбор должен обуславливаться удовлетворением многих критериев, как высокая производительность, удобный API, наличие нужных способов хранения логгируемых данных, так и спецификой ваших проектов, например, если ваш продукт будет использоваться в других проектах, то не стоит решать за пользователя каким логгером ему придется пользоваться, а в место этого отдать предпочтение обертке.
Просмотрев топики по логированию с использование Java, я с удивлением обнаружил только один топик описывающий EDL (новая система логирования, которую предлагает автор) и всё. Поэтому ниже я хотел бы рассмотреть основные инструменты, с которыми сталкивается разработчик, решающий добавить логирование в свою систему.- Apache Log4J
- JDK Logging API
- Commons Logging API
- SLF4J
- Logback
Apache Log4J
Это библиотека была разработана, как видно из названия, сообществом Apache и является стандартом де-факто в мире логирования Java. Она появилась раньше всех остальных. Так первая альфа версия появилась в 1999 году, во времена Java 1.2. Версия с номером 1.0 появилась в 2001 году, то есть во времена Java 1.3. Я так много говорю о датах и версиях Java потому, что до появления Java 1.4 в 2002 году у разработчиков было всего две альтернативы: самим реализовывать систему логирования, либо использовать Log4J. Соответственно, ясно, что большинство стало использовать Log4J, и продолжают ей пользоваться. С тех пор, библиотека продолжает развиваться и поддерживаться.
Сконфигурировать Log4J можно либо с помощью XML, либо с помощью файла свойств, имеющего стандартный Java Properties синтаксис.
Она поддерживает множество способов вывода логов: консоль, файлы, GUI компоненты, сокеты, JMS, NT Event Logger, Unix Syslog, SMTP, Telnet, базы данных. Форматирование доступно в форматах: XML, обычный текстовый вывод, HTML, TTCCLayout и форматирование на основе паттерна.
JDK Logging API
JDK Logging API был создан, как результат JSR 47, и теперь включён в пакет java.util.logging (JUL). JUL очень схож с Log4J как синтаксически, так и логически, но предоставляет меньше возможностей. Разницу в функциональности можно описать следующей фразой: «Всё что умеет делать JUL, Apache Log4J тоже умеет, а также умеет многое другое».
Так JUL предоставляет возможность вывода логов в файл, консоль, сокет и буффер и форматирование в виде xml и обычного текстового вывода. Это всё. Настройка JUL возможна только с помощью файла свойств (Java properties).
Commons Logging API
Commons Logging API ещё один API о котором можно часто услышать при разговоре о логировании в Java. На самом деле Commons Logging API (JCL) является всего лишь обёрткой над различными логирующими системами. В настоящий момент поддерживаются вышеописанные Apache Log4J, JUL и Avalon LogKit. Причём в большинстве случаев, JCL является обёрткой над Log4J и возможность безболезненно перейти на JUL не используется. Таким образом JCL лишь утяжеляет систему логирования, уменьшает её производительность (из-за через мерного использования механизма reflection), приводит к утечкам памяти и приводит возможности логирования к наименьшему общему делителю, то есть к JUL.
Таким образом, очень часто использование JCL абсолютно не оправданно, создаёт больше проблем, чем решает. Почему же JCL используется во многих библиотеках и проекта? Причина в том, что существует множество проектов, которые уже используют JCL и для совместимости с ними приходится пользоваться JCL.
Вот высказывание автора JCL Rod Waldhoff о своей библиотеке:
SLF4J
SLF4J представляется собой набор модулей, ответственных за «переброс» логов от одной системы к другой. В ней нет никакого динамического связывания и никакой магии classloader'ов, как в JCL. Так, например, чтобы настроить передачу JUL логов в Log4J достаточно положить в CLASSPATH 3 *.jar файла: jul-to-slf4j-*.jar, slf4j-api-*.jar,slf4j-log4j12-*.jar.
SLF4J поддерживает в настоящий момент JCL, JUL, Log4J и Logback.
Её используют многие проекты, включая, Apache Geronimo, LIFERAY, Sonar, Spring Dynamic Modules for OSGi, Hibernate и многие другие.Logback
Logback проект, созданный Ceki Gülcü — человеком, который создал Log4J. Logback концептуально очень похож на Log4J и JUL. Он является развитием Log4J и отличается от него тем, что обладает более высокой производительностью, может быть сконфигурирован с помощью XML и Groovy, поддерживает автоматическую загрузку конфигурационных файлов, автоматическую поддержку архивирования, новые фильтры и многое другое.
Logback является «нативной» реализацией SLF4J API, то есть, если Вы используете Logback, Вы используете SLF4J API. Таким образом, при логировании с помощью SLF4J и Logback Вы не теряете в производительности из-за обёртки.
Выводы
Так что же стоит использовать в новых проектах? Какие системы наиболее оптимальны? Что выбрать, если такая возможность существует?
Большинство разработчиков (я в том числе), считают, что сейчас вариант SLF4J + Logback является наиболее оптимальным. Logback обладает всеми преимуществами Log4J и тем более JUL, при этом превосходя их по многим параметрам. А использование SLF4J всегда позволит переключится на другую логирующую систему безболезненно и при этом не обладает минусами JCL.
Из вышеуказанного совета есть одно исключение. Если Ваш проект крайне простой и не предполагает сложной и гибкой системы логирования, то разумно использовать JUL. При этом Вам не придётся тянуть за собой другие библиотеки, можно будет ограничиться стандартным JDK.
Log4j
Это уже было полноценное решение, которое создавалось из потребностей разработчиков. Получился действительно интересный инструмент, который можно использовать. В силу разных обстоятельств это решение так и не попало в JDK, чем очень расстроило все комьюнити. В log4j были возможности по конфигурации таким образом, чтобы можно было включить логирование в пакете com.example.type и выключить его в подпакете com.example.type.generic . Это позволяло быстро отсечь то, что нужно логировать, от того, что не нужно. Здесь важно отметить, что есть две версии log4j: 1.2.х и 2.х.х, которые несовместимы друг с другом. log4j добавил такое понятие как appender, то есть инструмент, с помощью которого записываются логи и layout — форматирование логов. Это позволяет записывать только то, что нужно и как нужно. Больше о appender поговорим чуть позже.
SLF4J — simple logging facade for java
Узлы логирования
JCL — jakarta commons logging
Из-за того, что долгое время не было промышленного стандарта в логировании и был период, когда многие создавали свой кастомный логгер, решили выпустить JCL — общую обертку, которая использовалась бы над другими. Почему? Когда в проект добавлялись какие-то зависимости, они могли использовать логгер, отличный от логгера на проекте. Из-за этого они транзитивно добавлялись в проект, что создавало реальные проблемы при попытке все это собрать воедино. К сожалению, обертка была очень бедна на функциональность и никаких дополнений не вносила. Наверное, было бы удобно, если бы все использовали JCL для работы. Но на деле так не получалось, поэтому на данный момент применять JCL — не лучшая идея.
Читайте также: