Привет! При написании лекций я особо отмечаю, если какая-то конкретная тема обязательно будет использоваться в реальной работе. Так вот, ВНИМАНИЕ! Тема, которой мы коснемся сегодня, точно пригодится тебе на всех твоих проектах с первого дня работы. Мы поговорим о логировании. Тема эта совсем не сложная (я бы даже сказал легкая). Но на первой работе и без того будет достаточно стресса, чтобы еще разбираться с очевидными вещами, поэтому лучше досконально разобрать ее сейчас :) Итак, начнем. Что такое логирование? Логирование — это запись куда-то данных о работе программы. Место, куда эти данные записываются называется «лог». Возникает сразу два вопроса — куда и какие данные записываются? Начнем с «куда». Записывать данные о работе программы можно во множество разных мест. Например, ты во время учебы часто выводил данные в консоль с помощью 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. Если ты умеешь им пользоваться, лучше попробуй начать с него: это обычно намного проще Отлично, с настройками разобрались :) Давай рассмотрим, как работает 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. С форматом 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 достиг определенного размера:) А наше занятие на этом завершено! Ты сегодня познакомился с очень важной темой, и эти знания точно пригодятся тебе в дальнейшей работе. До новых встреч! :)