Привет!
При написании лекций я особо отмечаю, если какая-то конкретная тема обязательно будет использоваться в реальной работе.
Так вот, ВНИМАНИЕ!
Тема, которой мы коснемся сегодня, точно пригодится тебе на всех твоих проектах с первого дня работы.
Мы поговорим о логировании.
Тема эта совсем не сложная (я бы даже сказал легкая). Но на первой работе и без того будет достаточно стресса, чтобы еще разбираться с очевидными вещами, поэтому лучше досконально разобрать ее сейчас :)
Итак, начнем.
Что такое логирование?
Логирование — это запись куда-то данных о работе программы. Место, куда эти данные записываются называется «лог».
Возникает сразу два вопроса — куда и какие данные записываются?
Начнем с «куда».
Записывать данные о работе программы можно во множество разных мест. Например, ты во время учебы часто выводил данные в консоль с помощью
System.out.println()
. Это настоящее логирование, хоть и самое простое.
Конечно, для клиента или команды поддержки продукта это не очень удобно: они явно не захотят устанавливать IDE и мониторить консоль :)
Есть и более привычный человеку формат записи информации — в текстовый файл. Людям гораздо удобнее читать их в таком виде, и уж точно гораздо удобнее хранить!
Теперь второй вопрос: какие данные о работе программы должны записываться в лог?
А вот здесь все зависит от тебя!
Система логирования в Java очень гибкая. Ты можешь настроить ее таким образом, что в лог попадет весь ход работы твоей программы.
Это, с одной стороны, хорошо. Но с другой — представь себе, каких размеров могут достичь логи Facebook или Twitter, если туда писать вообще все.
У таких крупных компаний наверняка есть возможность хранить даже такое количество информации. Но вообрази, как сложно будет искать информацию об одной критической ошибке в логах на 500 гигабайт текста?
Это даже хуже, чем иголка в стоге сена. Поэтому логирование в Java можно настроить так, чтобы в журнал (лог) записывались только данные об ошибках. Или даже только о критических ошибках!
Хотя, говорить «логирование в Java» не совсем верно.
Дело в том, что потребность ведения логов возникла у программистов раньше, чем этот функционал был добавлен в язык.
И к тому времени, как в Java появился собственная библиотека для логирования, все уже пользовались библиотекой log4j. История появления логирования в Java на самом деле очень долгая и познавательная, на досуге можешь почитать этот пост на Хабре.
Короче говоря, своя библиотека логирования в Java есть, но ей почти никто не пользуется :)
Позже, когда появились несколько разных библиотек логирования, и все программисты начали пользоваться разными, возникла проблема совместимости.
Чтобы люди не делали одно и то же с помощью десятка разных библиотек с разными интерфейсами, был создан абстрагирующий фреймворк slf4j («Service Logging Facade For Java»).
Абстрагирующим он называется потому, что хотя ты и пользуешься классами slf4j и вызываешь их методы, под капотом у них работают все предыдущие фреймворки логирования: log4j, стандартный java.util.logging и другие.
Если тебе в данный момент нужна какая-то специфическая фича log4j, которой нет у других библиотек, но ты не хотел бы при этом жестко привязывать проект именно к этой библиотеке, просто используй slf4j. А о она уже «дернет» методы log4j.
Если ты передумаешь и решишь, что фичи log4j тебе больше не нужны, тебе надо только перенастроить «обертку» (то есть slf4j) на использование другой библиотеки. Твой код не перестанет работать, ведь в нем ты вызываешь методы slf4j, а не конкретной библиотеки.
Небольшое отступление. Чтобы следующие примеры заработали, тебе нужно скачать библиотеку slf4j отсюда, и библиотеку log4j отсюда.
Далее архив нужно распаковать,и добавить нужные нам jar-файлы в classpath через Intellij IDEA.
Пункты меню: File -> Project Structure -> Libraries
Выбираешь нужные jar-ники и добавляешь в проект (в архивах, которые мы скачали, лежит много jar’ников, посмотри нужные на картинках)
Примечание — эта инструкция для тех студентов, которые не умеют использовать Maven. Если ты умеешь им пользоваться, лучше попробуй начать с него: это обычно намного проще
Если используешь Maven, добавь такую зависимость:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.0</version>
</dependency>
Отлично, с настройками разобрались :)
Давай рассмотрим, как работает slf4j.
Как же нам сделать так, чтобы ход работы программы куда-то записывался?
Для этого нам нужны две вещи — логгер и аппендер.
Начнем с первого. Логгер — это объект, который полностью управляет ведением записей.
Создать логгер очень легко: это делается с помощью статического метода — LoggerFactory.getLogger()
. В качестве параметра в метод нужно передать класс, работа которого будет логироваться.
Запустим наш код:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyTestClass {
public static final Logger LOGGER = LoggerFactory.getLogger(MyTestClass.class);
public static void main(String[] args) {
LOGGER.info("Test log record!!!");
LOGGER.error("В программе возникла ошибка!");
}
}
Вывод в консоль:
ERROR StatusLogger No Log4j 2 configuration file found. Using default configuration (logging only errors to the console), or user programmatically provided configurations. Set system property 'log4j2.debug' to show Log4j 2 internal initialization logging. See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2
15:49:08.907 [main] ERROR MyTestClass - В программе возникла ошибка!
Что же мы тут видим?
Сначала мы видим сообщение об ошибке. Она появилась, потому что сейчас у нас не хватает необходимых настроек.
Поэтому наш логгер сейчас умеет выводить только сообщения об ошибках (ERROR) и только в консоль.
Метод logger.info()
выполнен не был. А вот logger.error()
сработал! В консоли появилась текущая дата, метод, где возникла ошибка (main
), слово ERROR и наше сообщение!
ERROR — это уровень логгрования.
В общем, если запись в логе помечена словом ERROR, значит, в этом месте программы произошла ошибка. Если запись помечена словом INFO — значит это просто текущая информация о нормальной работе программы.
В библиотеке SLF4J довольно много разных уровней логгирования, которые позволяют гибко настроить ведение журнала.
Управлять ими очень легко: вся необходимая логика уже заложена в класс Logger
.
Тебе достаточно просто вызывать нужные методы. Если ты хочешь залогировать обычное сообщение, вызывай метод logger.info()
. Сообщение об ошибке — logger.error()
. Вывести предупреждение — logger.warn()
Теперь поговорим об аппендере.
Аппендер — это место, куда приходят твои данные. Можно сказать, противоположность источнику данных — «точка B».
По умолчанию данные выводятся в консоль. Обрати внимание, в предыдущем примере нам не пришлось ничего настраивать: текст появился в консоли сам, но при этом логгер из библиотеки log4j умеет выводить в консоль только сообщения уровня ERROR.
Людям же, очевидно, удобнее читать логи из текстового файла и хранить логи в таких же файлах.
Чтобы изменить поведение логгера по умолчанию, нам нужно сконфигурировать свой файловый аппендер.
Для начала, прямо в папке src нужно создать файл log4j.xml, или в папке resources, если используешь Maven, or in the resources folder, in case you use Maven.
С форматом xml ты уже знаком, у нас недавно была лекция про него :)
Вот таким будет его содержимое:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<File name="MyFileAppender" fileName="C:\Users\Username\Desktop\testlog.txt" immediateFlush="false" append="false">
<PatternLayout pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="MyFileAppender"/>
</Root>
</Loggers>
</Configuration>
Выглядит не особо-то и сложно :)
Но давай все-таки пройдемся по содержимому.
<Configuration status="INFO">
Это так называемый status-logger. Он не имеет отношения к нашему логгеру и используется во внутренних процессах log4j.
Можешь установить status=”TRACE” вместо status=”INFO”, и в консоль будет выводиться вся информация о внутренней работе log4j (status-logger выводит данные именно в консоль, даже если наш аппендер для программы будет файловым). Нам это сейчас не нужно, поэтому оставим все как есть.
<Appenders>
<File name="MyFileAppender" fileName="C:\Users\Евгений\Desktop\testlog.txt" append="true">
<PatternLayout pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
</Appenders>
Тут мы создаем наш аппендер.
Тег <File>
указывает что он будет файловым.
name="MyFileAppender"
— имя нашего аппендера.
fileName="C:\Users\Username\Desktop\testlog.txt"
— путь к лог-файлу, куда будут записываться все данные.
append="true"
— нужно ли дозаписывать ли данные в конец файла. В нашем случае так и будет. Если установить значение false, при каждом новом запуске программы старое содержимое лога будет удаляться.
<PatternLayout pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
— это настройки форматирования. Здесь мы с помощью регулярных выражений можем настраивать формат текста в нашем логе.
<Loggers>
<Root level="INFO">
<AppenderRef ref="MyFileAppender"/>
</Root>
</Loggers>
Здесь мы указываем уровень логгирования (root level).
У нас установлен уровень INFO: то есть, все сообщения уровней выше INFO (по таблице, которую мы рассматривали выше) в лог не попадут.
У нас в программе будет 3 сообщения: одно INFO, одно WARN и одно ERROR. С текущей конфигурацией все 3 сообщения будут записаны в лог. Если ты поменяешь значение root level на ERROR, в лог попадет только последнее сообщение из LOGGER.error().
Кроме того, сюда же помещается ссылка на аппендер. Чтобы создать такую ссылку, нужно внутри тега <Root>
создать тег <ApprenderRef>
и добавить ему параметр ref=”имя твоего аппендера”
.
Имя аппендера мы создали вот тут, если ты забыл:
<File name="MyFileAppender"
А вот и код нашей программы!
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyTestClass {
public static final Logger LOGGER = LoggerFactory.getLogger(MyTestClass.class);
public static void main(String[] args) {
LOGGER.info("Начало работы программы!!!");
try {
LOGGER.warn("Внимание! Программа пытается разделить одно число на другое");
System.out.println(12/0);
} catch (ArithmeticException x) {
LOGGER.error("Ошибка! Произошло деление на ноль!");
}
}
}
Он, конечно, немного кривоватый (перехват RuntimeException — идея так себе), но для нашей целей отлично подойдет :)
Давай запустим наш метод main()
4 раза подряд и посмотрим на наш файл testlog.txt. Создавать его заранее не нужно: библиотека сделает это автоматически.
Все заработало! :)
Теперь у тебя есть настроенный логгер. Ты можешь поиграться с какими-то написанными тобой ранее программами, добавив вызовы логгера во все методы, и посмотреть на получившийся журнал:)
В качестве дополнительного чтения очень рекомендую тебе вот эту статью.
Там тема логирования рассмотрена углубленно, и за один раз прочитать ее будет непросто. Но в ней содержится очень много полезной дополнительной информации.
Например, ты научишься конфигурировать логгер так, чтобы он создавал новый текстовый файл, если наш файл testlog.txt достиг определенного размера:)
А наше занятие на этом завершено! Ты сегодня познакомился с очень важной темой, и эти знания точно пригодятся тебе в дальнейшей работе.
До новых встреч! :)
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ