[Из песочницы] Java logging. Hello World12.01.2015 14:03
Вступление
Думаю, ни для кого не секрет, что такое логгеры и для чего они нужны. За время существования java было создано немало фреймворков логгирования. Среди самых известных можно выделить: JUL — java.util.logging
log4j
JCL — jakarta commons logging
Logback
SLF4J — simple logging facade for java
В данной статье будет рассмотрен каждый из указанных выше фреймворков на уровне «hello world». Будут приведены простые примеры использования основного функционала и конфигурирования. Статья не преследует цель сравнения логгеров между собой и выявление лучшего из них, эту возможность автор оставляет за вами, уважаемые читатели. В конце статьи будут приведены источники, где можно получить более детальную информацию по каждому фреймворку. Также перед прочтением данной статьи рекомендую ознакомиться с публикацией «Java Logging: история кошмара», где описана история развития систем логгирования в Java.System.err.println
Первым и самым примитивным способом логгирования был метод System.err.println. Думаю, комментарии излишние, достаточно взглянуть на приведенный ниже код:
// Определяем файл в который будем писать лог
System.setErr (new PrintStream (new File («log.txt»)));
// Выводим сообщения
System.err.println («Сообщение 1»);
System.err.println («Сообщение 2»);
// Выводим сообщение об ошибке
try {
throw new Exception («Сообщение об ошибке»);
} catch (Exception e) {
e.printStackTrace ();
}
Java.util.logging
Данный фреймворк включен в стандарт и поставляется вместе с JDK, поэтому ничего дополнительно скачивать и подключать вам не надо. JUL имеет следующие уровни логгирования по возрастанию: FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, а так же ALL и OFF, включающий и отключающий все уровни соответственно.Логгер создается вызовом одного из статических методов класса java.util.logging.Logger:
Logger log = Logger.getLogger (LoggingJul.class.getName ());
Методы логгера могут принимать в качестве аргументов строковые сообщения, шаблоны сообщений, исключения, ресурсы локализованных текстовок сообщений, а также, начиная с Java 8, поставщиков строковых сообщений:
// Строковое сообщение
String stringMessage = «Сообщение»;
// Строковое сообщение с параметрами
String stringMessageFormat =«Сообщение {0}»;
// Исключение
Throwable throwable = new Throwable ();
// ResourceBundle хранящий сообщения
ResourceBundle resourceBundle = ResourceBundle.getBundle («logging.jul.bundle»);
// Поставщик сообщений
Supplier stringMessageSupplier = ()→«Сообщение»;
Выделяется две группы методов: название которых соответствует уровню логгирования и методы log, loggp, logrb, принимающие уровень логгирования в качестве параметра с типом Level. Первая группа содержит методы двух типов: принимающих строковое сообщение или поставщика строковых сообщений:
log.info (stringMessage);
log.info (stringMessageSupplier);
Вторая группа методов имеет следующие вариации:
// Вывести сообщение с указанием уровня логгирования
log.log (new LogRecord (Level.INFO, stringMessage));
log.log (Level.INFO, stringMessage);
log.log (Level.INFO, stringMessageSupplier);
log.log (Level.INFO, stringMessageFormat, args);
log.log (Level.INFO, stringMessage, throwable);
log.log (Level.INFO, throwable, stringMessageSupplier);
// Вывести сообщение с указанием уровня логгирования, класса и метода
log.logp (Level.INFO, «ClassName», «MethodName», stringMessage);
log.logp (Level.INFO, «ClassName», «MethodName», stringMessageSupplier);
log.logp (Level.INFO, «ClassName», «MethodName», stringMessageFormat, args);
log.logp (Level.INFO, «ClassName», «MethodName», stringMessage, throwable);
log.logp (Level.INFO, «ClassName», «MethodName», throwable, stringMessageSupplier);
// Вывести сообщение с указанием уровня логгирования, класса,
// метода и resourceBundle, хранящего сообщения
log.logrb (Level.INFO, «ClassName», «MethodName», resourceBundle, «messageId»);
log.logrb (Level.INFO, «ClassName», «MethodName», resourceBundle, «messageId», throwable);
// Вывести сообщение об ошибке
log.throwing («ClassName», «MethodName», throwable);
Теперь обратимся к конфигурации фреймворка. По умолчанию JUL будет выводить сообщения на консоль, однако можно задать конфигурацию в файле свойств. Для задания способа вывода сообщений необходимо для вашего логгера указать какие хендлеры он будет использовать. Существует следующие классы хендлеров: FileHandler, ConsoleHandler, StreamHandler, SocketHandler, MemoryHandler. Особенностью JUL является то, что настройки хендлеров задаются в целом для всего класса, а не для конкретного экземпляра, что может порождать не мало проблем, например если вам потребуется сообщения различных логгеров выводить в различные файлы или с различным форматированием. Рассмотрим простой пример конфигурационного файла:
# Настройки глобального логгера
handlers =java.util.logging. FileHandler
.level=ALL
# Конфигурация файлового хендлера
java.util.logging.FileHandler.level =ALL
java.util.logging.FileHandler.formatter =java.util.logging.SimpleFormatter
java.util.logging.FileHandler.limit = 1000000
java.util.logging.FileHandler.pattern = log.txt
# Конфигурация консольного хендлера
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.pattern = log.log
java.util.logging.ConsoleHandler.formatter =java.util.logging.SimpleFormatter
Для того что бы JUL применил данную конфигурацию нужно передать параметр -Djava.util.logging.config.file = , либо при старте приложения выполнить код:
LogManager.getLogManager ().readConfiguration (<ваш класс>.class.getResourceAsStream («logging.properties»));
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:
Logger log = LogManager.getLogger (LoggingLog4j.class);
// или Logger log = LogManager.getLogger («name»);
Логгер умеет принимать по мимо привычных нам String, Object и Throwable еще два новых типа — MapMessage и Marker:
// Карта сообщений (напечатается как msg1=«Сообщение 1» msg2=«Сообщение 2»)
MapMessage mapMessage = new MapMessage ();
mapMessage.put («msg1», «Сообщение 1»);
mapMessage.put («msg2», «Сообщение 2»);
// Маркер, объект по которому можно фильтровать сообщения
Marker marker = MarkerManager.getMarker («fileonly»);
// Строковое сообщение
String stringMessage = «Сообщение»;
// Строковое сообщение с параметрами
String stringMessageFormat = «Сообщение {}, от {}»;
// Исключение
Throwable throwable = new Throwable ();
// Объект
Object object = new Object ();
В классическом для логгеров стиле методы делятся на два типа: совпадающие с названием уровня логгирования и методы log, принимающие уровень логгирования в качестве параметра. Первые имеют вид:
log.info (mapMessage);
log.info (object);
log.info (stringMessage);
log.info (marker, mapMessage);
log.info (marker, object);
log.info (marker, stringMessage);
log.info (object, throwable);
log.info (stringMessage, throwable);
log.info (stringMessageFormat, args);
log.info (marker, mapMessage, throwable);
log.info (marker, object, throwable);
log.info (marker, stringMessageFormat, args);
log.info (marker, stringMessage, throwable);
log.throwing (throwable);
Методы log в log4j2 выглядят так:
log.log (Level.INFO, mapMessage);
log.log (Level.INFO, object);
log.log (Level.INFO, stringMessage);
log.log (Level.INFO, marker, mapMessage);
log.log (Level.INFO, marker, object);
log.log (Level.INFO, marker, stringMessage);
log.log (Level.INFO, object, throwable);
log.log (Level.INFO, stringMessageFormat, args);
log.log (Level.INFO, stringMessage, throwable);
log.log (Level.INFO, marker, mapMessage, throwable);
log.log (Level.INFO, marker, object, throwable);
log.log (Level.INFO, marker, stringMessageFormat, args);
log.log (Level.INFO, marker, stringMessage, throwable);
log.throwing (Level.INFO, throwable);
Если не определить конфигурацию, то при запуске log4j2 выдаст гневное сообщение, о том, что конфигурация не задана и будет печатать ваши сообщения на консоль уровнем не ниже ERROR. Конфигурация log4j2 задается несколькими вариантами: xml, json, yaml. Стоит отметить, что со второй версии нет поддержки конфигурации из property файла. Файл с конфигурацией автоматически ищется classpath, должен иметь название log4j2 и располагаться в пакете по умолчанию.Конфигурация 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 может создавать множество различающихся аппендеров одного и того же класса, например несколько файловых аппендеров, которые пишут в разные файлы.Рассмотрим пример конфигурации, в которой объявлены два логгера (корневой и для нашего класса), первый из которых пишет в файл log.log, а второй пишет в log2.log с использованием фильтрации по маркеру:
%d %p %c{1.} [%t] %m %ex%n
%d %p %c{1.} [%t] %m %ex%n
Commons-logging
Довольно старый проект, который представляет собой обертку над JUL и log4j, не привносящая никакого дополнительного функционала. Уровни логгирования у JCL совпадают с log4j, а в случае взаимодействия с JUL происходит следующее сопоставление:
fatal = Level.SEVERE
error = Level.SEVERE
warn = Level.WARNING
info = Level.INFO
debug = Level.FINE
trace = Level.FINEST
Для использования JCL подключаем commons-logging-1.x.jar. Создаем логгер вызовом метода фабрики:
Log log = LogFactory.getLog (LoggingCl.class);
// или Log log = LogFactory.getLog («name»);
Методы JCL очень простые, совпадают с названием уровней логгирования, принимают только объекты и исключения и имеют две вариации:
Object object = «Сообщение»;
Throwable throwable = new Throwable ();
log.info (object);
log.info (object, throwable);
Конфигурация JCL содержит отдельные блоки для log4j, JUL и собственной реализации. Если не задать конфигурацию, то используется собственная реализация, именуемая SimpleLog, которая выводит сообщения на консоль. Рассмотрим пример конфигурационного файла:
#Log4j
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
log4j.configuration=log4j.properties
#JUL
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Jdk14Logger
handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler
.level=INFO
java.util.logging.FileHandler.pattern=jul.log
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.limit=50000
java.util.logging.FileHandler.count=1
#SimpleLog
org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
org.apache.commons.logging.simplelog.defaultlog=fatal
org.apache.commons.logging.simplelog.showlogname=true
org.apache.commons.logging.simplelog.showShortLogname=true
org.apache.commons.logging.simplelog.showdatetime=true
Указать файл конфигурации JCL можно следующим образом:
java -Djava.util.logging.config.file=/absolute/path/to/your/config/file/commons-logging.properties -jar /absolute/path/to/your/jar/file/MyClass.jar
Logback
Данный фреймворк используется только в связке с оберткой SLF4J, которую мы будем рассматривать позднее. Для начала работы вам необходимы logback-core-1.x.jar и logback-classic-1.x.x.jar, а также slf4j-api-1.x.x.jar.Взаимодействие с логгером мы будем осуществлять через API предоставляемый оберткой SLF4J. Уровни логгирования совпадают с log4j. Создание логгера в таком случае выглядит следующим образом:
org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger (LoggingLogback.class);
// или org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger («name»);
API позволяет выводить строковые сообщения, шаблоны строковых сообщений, исключения, а также использовать маркеры:
// Строковое сообщение
String stringMessage = «Сообщение»;
// Шаблон сообщения
String stringMessageFormat = «Сообщение {} {}»;
// Ошибка
Throwable throwable = new Throwable ();
// Маркер
Marker marker = MarkerFactory.getMarker («marker»);
Названия методов совпадают с уровнями логгирования и имеют вид:
log.info (stringMessage);
log.info (stringMessageFormat, args);
log.info (stringMessage, throwable);
log.info (marker, stringMessage);
log.info (marker, stringMessage, throwable);
log.info (marker, stringMessageFormat, args);
Теперь рассмотрим непосредственны функционал logback. Конфигурация ищется в classpath в следующем порядке: Пытается найти logback.groovy
Иначе пытается найти logback-test.xml
Иначе пытается найти logback.xml
Иначе использует базовую конфигурацию — выводим сообщения на консоль
Основными элементами конфигурации являются логгеры, аппендеры, лайауты, и фильтры.Имеются следующие фильтры: Regular filters
LevelFilter
ThresholdFilter
EvaluatorFilter
Matchers
TurboFilters
CountingFilter
Имеются следующие аппендеры: OutputStreamAppender
ConsoleAppender
FileAppender
RollingFileAppender
SocketAppender and SSLSocketAppender
ServerSocketAppender and SSLServerSocketAppender
SMTPAppender
SyslogAppender
SiftingAppender
AsyncAppender
О том что такое Layouts и Encoders в logback предлагаю прочитать подробно в документации, а сейчас лишь приведу простой пример файла logback.xml:
log.log
%date %level [%thread] %logger{10} [%file:%line] %msg%n
%d{HH: mm: ss.SSS} [%thread] %-5level %logger{36} — %msg%n
marker
DENY
SLF4J
Как уже говорилось ранее SLF4J является оберткой над logback, а также над JUL, log4j, или JCL, а также над любым логгером, который реализует ее интерфейс. Для работы с SLF4J нужны библиотека slf4j-api-1.x.x.jar и реализация одного из логгеров либо заглушка. Как правило реализации всех логгеров (кроме logback) поставляются вместе с SLF4J и имеют названия на подобии slf4j-jcl-1.x.jar, slf4j-log4j12–1.x.jar, slf4j-nop-1.x.jar и т.п. Если в classpath не будет найдена реализация логгера (или заглушка nop) SLF4J гневно ругнется и работать откажется. Конфигурация соответственно будет искаться в зависимости от положенной в classpath реализации.API SLF4J мы рассмотрели в предыдущем пункте, поэтому давайте рассмотрим еще одну возможность обертки. В идеальном мире мы должны выводить сообщения через интерфейс обертки, и тогда у нас все будет хорошо, но реальный жестокий мир говорит о том, что всем нам приходится взаимодействовать со сторонними библиотеками или кодом, в которых используются другие логгеры и которые знать не знают о SLF4J. Что бы не подстраиваться под каждый логгер, а пустить все сообщения через одну реализацию интерфейса SLF4J, можно использовать bridging. В поставке обертки содержаться библиотеки jcl-over-slf4j.jar, log4j-over-slf4j.jar и jul-to-slf4j.jar, которые переопределяют поведение соответствующих логгеров и перенаправляют сообщения в обертку.Что бы стало понятнее выше сказанное, рассмотрим пример. Допустим у нас имеются следующие логгеры:
java.util.logging.Logger julLog = java.util.logging.Logger.getLogger («julLog»);
java.util.logging.Logger log4jLog = java.util.logging.Logger.getLogger («log4jLog»);
org.slf4j.Logger slf4jLog = org.slf4j.LoggerFactory.getLogger (LoggingSlf4j.class);
julLog.info («Сообщение от jul»);
log4jLog.info («Сообщение от log4j»);
slf4jLog.info («Сообщение от slf4j»);
Мы хотим, что бы сообщение от JUL записывались в один файл, от log4j в другой, а от slf4j выводились на консоль. В качестве реализации обертки будем использовать logback, конфигурация сего безобразия будет выглядеть следующим образом:
log_jul.log
%date %level [%thread] %logger{10} [%file:%line] %msg%n
log_log4j.log
%date %level [%thread] %logger{10} [%file:%line] %msg%n
%d{HH: mm: ss.SSS} [%thread] %-5level %logger{36} — %msg%n
Для того, что бы мост заработал необходимо выполнить код:
SLF4JBridgeHandler.removeHandlersForRootLogger ();
SLF4JBridgeHandler.install ();
Хочу узнать больше
Заключение
В заключение хотелось бы вам сказать, что конечный выбор фреймворка логгирования остается всегда за вами, но к этому надо подходить здраво. Выбор должен обуславливаться удовлетворением многих критериев, как высокая производительность, удобный API, наличие нужных способов хранения логгируемых данных, так и спецификой ваших проектов, например, если ваш продукт будет использоваться в других проектах, то не стоит решать за пользователя каким логгером ему придется пользоваться, а в место этого отдать предпочтение обертке.
© Habrahabr.ru