1. Почему регистраторы?
При написании программы или разработке производственного корпоративного приложения использование System.out.println
кажется самым простым и легким вариантом. В путь к классам не нужно добавлять дополнительные библиотеки и выполнять дополнительные настройки.
Но использование System.out.println
сопряжено с несколькими недостатками, которые влияют на его удобство использования во многих ситуациях. В этом уроке мы обсудим, почему и когда мы хотим использовать Logger вместо старых простых System.out
и System.err
. Мы также покажем несколько быстрых примеров с использованием среды ведения журналов Log4J2.
2. Настройка
Прежде чем мы начнем, давайте рассмотрим необходимые зависимости и конфигурации Maven.
2.1. Зависимости Maven
Начнем с добавления зависимости Log4J2 в наш pom.xml
:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.12.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency>
Мы можем найти последние версии log4j-api
и log4j-core
на Maven Central.
2.2. Конфигурация Log4J2
Использование System.out
не требует дополнительной настройки. Однако для использования Log4J2 нам нужен файл конфигурации log4j.xml
:
<Configuration status="debug" name="foreach" packages="">
<Appenders>
<Console name="stdout" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %p %m%n"/>
</Console>
</Appenders>
<Root level="error">
<AppenderRef ref="STDOUT"/>
</Root>
</Configuration>
Почти все фреймворки для ведения журналов потребуют некоторого уровня настройки либо программно , либо с помощью внешнего файла конфигурации, такого как XML-файл, показанный здесь.
3. Разделение вывода журнала
3.1. System.out
и System.err
Когда мы развертываем наше приложение на сервере, таком как Tomcat, сервер использует собственный регистратор. Если мы используем System.out
, журналы попадают в catalina.out
. Наше приложение гораздо проще отлаживать, если логи выносить в отдельный файл. С Log4j2 нам нужно включить в конфигурацию файловый аппендер, чтобы сохранять журналы приложений в отдельном файле.
Кроме того, с помощью System.out.println
нет контроля или фильтрации того, какие журналы должны быть напечатаны. Единственный возможный способ разделить журналы — использовать System.out.println
для информационных журналов и System.err.println
для журналов ошибок:
System.out.println("This is an informational message");
System.err.println("This is an error message");
3.2. Уровни ведения журнала Log4J2
В средах отладки или разработки мы хотим видеть всю информацию, которую печатает приложение. Но в действующем корпоративном приложении большее количество журналов означает увеличение задержки. Фреймворки ведения журналов, такие как Log4J2, предоставляют несколько элементов управления уровнем ведения журнала:
- ФАТАЛЬНО
- ОШИБКА
- ПРЕДУПРЕЖДАТЬ
- ИНФОРМАЦИЯ
- ОТЛАЖИВАТЬ
- СЛЕД
- ВСЕ
Используя эти уровни, мы можем легко фильтровать, когда и где печатать какую информацию :
logger.trace("Trace log message");
logger.debug("Debug log message");
logger.info("Info log message");
logger.error("Error log message");
logger.warn("Warn log message");
logger.fatal("Fatal log message");
Мы также можем настроить уровни для каждого пакета исходного кода индивидуально. Дополнительные сведения о настройке уровня журнала см. в нашей статье Ведение журналов Java.
4. Запись журналов в файлы
4.1. Перенаправление System.out
и System.err
Можно направить System.out.println
в файл с помощью метода System.setOut()
:
PrintStream outStream = new PrintStream(new File("outFile.txt"));
System.setOut(outStream);
System.out.println("This is a foreach article");
И в случае System.err
:
PrintStream errStream = new PrintStream(new File("errFile.txt"));
System.setErr(errStream);
System.err.println("This is a foreach article error");
При перенаправлении вывода в файл с помощью System.out
или System.err
мы не можем контролировать размер файла , поэтому файл продолжает расти в течение всего времени работы приложения.
По мере увеличения размера файла могут возникнуть трудности с открытием или анализом этих больших журналов.
4.2. Вход в файлы с помощью Log4J2
Log4J2 предоставляет механизм для систематической записи журналов в файлы, а также свертывания файлов на основе определенных политик. Например, мы можем настроить прокрутку файлов на основе шаблона даты/времени :
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<File name="fout" fileName="log4j/target/foreach-log4j2.log"
immediateFlush="false" append="false">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %p %m%n"/>
</File>
<Loggers>
<AsyncRoot level="DEBUG">
<AppenderRef ref="stdout"/>
<AppenderRef ref="fout"/>
</AsyncRoot>
</Loggers>
</Configuration>
Или мы можем свернуть файлы в зависимости от размера, как только они достигнут заданного порога :
...
<RollingFile name="roll-by-size"
fileName="target/log4j2/roll-by-size/app.log" filePattern="target/log4j2/roll-by-size/app.%i.log.gz"
ignoreExceptions="false">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss} %p %m%n</Pattern>
</PatternLayout>
<Policies>
<OnStartupTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="5 KB"/>
</Policies>
</RollingFile>
5. Вход во внешние системы
Как мы видели в предыдущем разделе, фреймворки регистраторов позволяют записывать журналы в файл. Точно так же они также предоставляют приложения для отправки журналов в другие системы и приложения . Это позволяет отправлять журналы в базу данных Kafka Stream или Elasticsearch с помощью приложений Log4J, а не с помощью System.out.println.
Пожалуйста, обратитесь к нашей статье о приложении Log4j для получения более подробной информации о том, как использовать такие приложения.
6. Настройка вывода журнала
С помощью регистраторов мы можем настроить, какая информация должна быть напечатана вместе с фактическим сообщением. Информация, которую мы можем распечатать, включает имя пакета, уровень журнала, номер строки, отметку времени, имя метода и т. д.
Хотя это было бы возможно с System.out.println,
это потребовало бы много ручной работы, в то время как фреймворки ведения журналов предоставляют эту функциональность из коробки. С регистраторами мы можем просто определить шаблон в конфигурации регистратора :
<Console name="ConsoleAppender" target="SYSTEM_OUT">
<PatternLayout pattern="%style{%date{DEFAULT}}{yellow}
%highlight{%-5level}{FATAL=bg_red, ERROR=red, WARN=yellow, INFO=green} %message"/>
</Console>
Если мы рассмотрим Log4J2 для нашего фреймворка логгера, то у нас будет несколько шаблонов, которые мы можем выбрать или настроить. Обратитесь к официальной документации Log4J2 , чтобы узнать о них больше.
7. Избегайте printStackTrace()
, вместо этого протоколируя вывод исключений
Когда мы обрабатываем исключения в нашем коде, нам часто нужно узнать, какие исключения действительно возникли во время выполнения. Для этого есть два распространенных варианта: printStackTrace()
или вызов регистратора.
Довольно часто можно увидеть обработку исключений, которая использует printStackTrace()
для вывода сведений об исключении:
try {
// some code
} catch (Exception e) {
e.printStackTrace();
}
Проблема здесь в том, что printStackTrace()
печатает свою информацию в System.err
, а мы уже сказали, что хотим этого избежать.
Вместо этого мы можем зарегистрировать исключение, используя структуру ведения журнала, а затем мы сможем легко получить журналы:
try {
// some code
} catch (Exception e) {
logger.log("Context message", e);
}
8. Заключение
В этой статье объясняются различные причины, по которым следует использовать структуру регистратора и почему не полагаться только на System.out.println
для журналов наших приложений. Хотя использование System.out.println
для небольших тестовых программ оправдано, мы бы предпочли не использовать его в качестве основного источника ведения журналов для корпоративных производственных приложений.
Как всегда, примеры кода в статье доступны на GitHub .