Pull to refresh

Java logging. Hello World

Reading time14 min
Views290K

Вступление


Думаю, ни для кого не секрет, что такое логгеры и для чего они нужны. За время существования 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<String> 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 с использованием фильтрации по маркеру:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <!-- Секция аппендеров -->
  <Appenders>
    <!-- Файловый аппендер -->
    <File name="file" fileName="log.log">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m %ex%n</Pattern>
      </PatternLayout>
    </File>
    <!-- Файловый аппендер -->
    <File name="file2" fileName="log2.log">
      <!-- Фильтр по маркеру -->
      <MarkerFilter marker="fileonly" onMatch="DENY" onMismatch="ACCEPT"/>
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m %ex%n</Pattern>
      </PatternLayout>
    </File>
  </Appenders>
  <!-- Секция логгеров -->
  <Loggers>
    <!-- Корневой логгер -->
    <Root level="trace">
      <AppenderRef ref="file" level="DEBUG"/>
    </Root>
    <!-- Логгер нашего класса -->
    <Logger name="logging.log4j.LoggingLog4j" level="info" additivity="false">
        <AppenderRef ref="file2" level="INFO"/>
    </Logger>
  </Loggers>
</Configuration> 

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 в следующем порядке:
  1. Пытается найти logback.groovy
  2. Иначе пытается найти logback-test.xml
  3. Иначе пытается найти logback.xml
  4. Иначе использует базовую конфигурацию — выводим сообщения на консоль

Основными элементами конфигурации являются логгеры, аппендеры, лайауты, и фильтры.
Имеются следующие фильтры:
  • 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:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <!--Аппендеры -->
  <!--Файловый аппендер -->
  <appender name="file" class="ch.qos.logback.core.FileAppender">
        <file>log.log</file>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</Pattern>
        </layout>
  </appender>
  <!--Консольный аппендер -->
  <appender name="sout" class="ch.qos.logback.core.ConsoleAppender">
    <layout class="ch.qos.logback.classic.PatternLayout">
        <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
    </layout>
  </appender>
  <!-- Фильтры -->
  <!-- Фильтр по маркеру -->
  <turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
    <Marker>marker</Marker>
    <OnMatch>DENY</OnMatch>
  </turboFilter>
  <!-- Логгеры -->
  <!-- Корневой логгер -->
  <root level="info">
    <appender-ref ref="file" />
  </root>
  <!-- Логгер нашего класса  -->
  <logger name="logging.logback.LoggingLogback" level="info" >
    <appender-ref ref="sout" />
  </logger>
</configuration>


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, конфигурация сего безобразия будет выглядеть следующим образом:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <!--Аппендеры -->
  <!--Файловый аппендер для JUL -->
  <appender name="jul" class="ch.qos.logback.core.FileAppender">
        <file>log_jul.log</file>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</Pattern>
        </layout>
  </appender>
    <!--Файловый аппендер для log4j -->
  <appender name="log4j" class="ch.qos.logback.core.FileAppender">
        <file>log_log4j.log</file>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</Pattern>
        </layout>
  </appender>
  <!--Консольный аппендер -->
  <appender name="sout" class="ch.qos.logback.core.ConsoleAppender">
    <layout class="ch.qos.logback.classic.PatternLayout">
        <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
    </layout>
  </appender>
  <!-- Логгеры -->
  <!-- Корневой логгер -->
  <root level="info" >
    <appender-ref ref="sout" />
  </root>
  <!-- Логгер для jul  -->
  <logger name="julLog"  additivity="false" >
    <level value="trace" />
    <appender-ref ref="jul" />
  </logger>
  <!-- Логгер для log4j  -->
  <logger name="log4jLog"  additivity="false" >
    <level value="trace" />
    <appender-ref ref="log4j" />
  </logger>
</configuration>

Для того, что бы мост заработал необходимо выполнить код:

SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();

Хочу узнать больше



Заключение


В заключение хотелось бы вам сказать, что конечный выбор фреймворка логгирования остается всегда за вами, но к этому надо подходить здраво. Выбор должен обуславливаться удовлетворением многих критериев, как высокая производительность, удобный API, наличие нужных способов хранения логгируемых данных, так и спецификой ваших проектов, например, если ваш продукт будет использоваться в других проектах, то не стоит решать за пользователя каким логгером ему придется пользоваться, а в место этого отдать предпочтение обертке.
Tags:
Hubs:
+12
Comments14

Articles

Change theme settings