Автор: Антон Козицкий
Популярные библиотеки для логирования
Интро и план
В этом разделе мы поговорим о популярных библиотеках, которые используются для логирования в реальных проектах на Java. В самом начале мы обсудим архитектуру логгеров и выясним, какие зависимости нам нужны в проекте, чтобы подключить логирование. Далее я приведу список самых популярных и часто используемых логгеров, опишу кратко их плюсы и минусы. И в конце раздела мы затронем практическую часть: создадим учебный проект и попробуем «поиграть» с разными логгерами, увидим на реальном примере нюансы работы с ними.
Важный архитектурный момент
Интерфейс и имплементация, или «лёгкий экскурс в историю»
Логгер — это две части: фасад и имплементация.
Фасад состоит из набора интерфейсов и задаёт контракт («правила игры»). Фасад нам нужен для унификации логирования и чтобы определить, как логгер и его составные части будут выглядеть. Имплементация — это конкретный двигатель под капотом, который играет по правилам, определённым интерфейсом.
В Java это популярная практика: когда есть интерфейс и когда есть одна или несколько имплементаций этого интерфейса. Вот несколько примеров для лучшего понимания:
| Интерфейс | Имплементации |
|---|---|
| JPA (Java Persistence API) — стандарт для ORM и работы с реляционными БД | |
| Bean Validation (JSR 380 / Jakarta Validation) — стандарт декларативной валидации через аннотации |
Можно пойти ещё дальше и вспомнить, что сама Java (а точнее виртуальная машина Java — JVM) — это тоже спецификация (скажем, фасад/интерфейс/контракт). И у этой спецификации есть очень много реализаций от самых разных крупных вендоров. Да, это мир Java — здесь «рулят» правила и контракты. Именно поэтому Java так хорошо себя зарекомендовала в мире Enterprise-решений.
Если всё это подытожить, то можно сказать так: сначала сообщество или коллегиум разрабатывает контракт, а затем уже вендоры разрабатывают имплементации. Это удобно и хорошо по самым разным причинам:
- единый стандарт;
- возможность простой замены одной имплементации другой, без переписывания кода;
- здоровая конкуренция — каждый вендор пишет свою имплементацию, которая может иметь свои плюсы и минусы;
- разделение обязанностей: сообщество — контракт/интерфейс, вендоры — имплементации.
«Сломанный паттерн» в логировании
Ложка дёгтя в бочке мёда. К сожалению, на момент создания первых логгеров ещё не было определено правило, что «сначала контракт, потом имплементация». Это правило пришло позже — в то время, когда первые имплементации логгеров уже увидели свет. Отсюда и возникла некоторая проблема, которая впоследствии всё же была решена. Но всё равно есть некоторый нюанс, о котором мы и поговорим ниже.
Посмотрите на эту таблицу, чтобы понять, о чём я буду говорить дальше:
| Название логгера | «Родной» интерфейс/фасад и jar | Имплементация |
|---|---|---|
| Logback | SLF4J: slf4j-api.jar |
logback-classic.jar |
| Log4j2 | Log4j: log4j-api.jar |
log4j-core.jar |
| JUL (Java Util Logging) | Является частью JDK в пакете java.util.logging. Интерфейсы и имплементация
живут вместе в одном пакете. |
|
Здесь нет единства по той самой причине: логгеры появились слишком давно — в то время, когда чёткого разделения ещё не было как такового. Но как быть теперь? Решение есть, и о нём ниже.
Если вы начинаете новый проект, вы можете выбрать подходящий для себя логгер и затем подключить «правильные»
фасад и имплементацию — то есть соответствующие друг другу, например: slf4j-api.jar и
logback-classic.jar. Скорее всего ваш проект будет подключать зависимости — сторонние библиотеки.
В этом случае с высокой долей вероятности эти библиотеки будут использовать разные логгеры, это нормально и
ожидаемо. Ваша задача здесь — привести всё это к «общему знаменателю».
Например: вы хотите использовать Log4j2 в качестве логгера для своего проекта. Некоторые зависимости используют Logback — другой логгер. Вам нужно все логи привести к одному формату — к формату Log4j2. Для этой цели были созданы специальные «bridges» — «переходники», которые транслируют логи из одной системы в другую. В секции «Практика» мы коснёмся этой темы. А сейчас предлагаю рассмотреть плюсы и минусы популярных систем логирования.
Обзор популярных Java логгеров
Поговорим про конкретные имплементации. Рассмотрим три самые популярные и самые широко используемые в Java имплементации логгеров: Logback, Log4j2 и Java Util Logging (JUL).
JUL (Java Util Logging)
Это самый простой логгер, который входит в стандартную поставку JDK и живёт в пакете
java.util.logging. Отлично подходит для простых и учебных проектов, так как можно начать
пользоваться сразу же, без необходимости подключать сторонние зависимости, делать конфигурацию и так далее.
Logback
Это логгер по умолчанию в Spring Boot. Он тоже относится к простым логгерам, которые хорошо делают свою рядовую работу. Включили его в Spring Boot как раз за его простоту и функциональность. Для 90% всех проектов он отлично подходит и выполняет все свои задачи на «ура».
Logback умеет писать логи в файлы, умеет отправлять в базу данных, умеет отправлять по сети. Для некоторых функций может потребоваться подключение расширений дополнительно. Из-за своей простоты Logback не имеет таких глубоких оптимизаций как Log4j2, поэтому для высоконагруженных проектов он может оказаться не самым лучшим решением.
Log4j2
Если нам нужны высокая производительность, либо какие-то особые функции, которых нет в Logback, мы можем подключить Log4j2. Например, для отправки логов по сети или в Kafka нам не нужно подключать дополнительные расширения — всё уже есть в логгере. Если наш проект очень большой и мы ожидаем, что будем очень много логировать, ожидаем высоких нагрузок на наш сервис — в этом случае Log4j2 может стать разумным выбором.
Переходим к практике
SLF4J + Logback
Начнём с того, что подключим фасад:
<dependencies>
<!-- Source: https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.17</version>
<scope>compile</scope>
</dependency>
</dependencies>
Далее добавим класс, из которого будем логировать:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
log.trace("Logging trace message");
log.debug("Logging debug message");
log.info("Logging info message");
log.warn("Logging warn message");
log.error("Logging error message");
}
}
И попробуем запустить наше тестовое приложение:
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
Process finished with exit code 0
Здесь нам сам фасад говорит, что имплементаций не было найдено и поэтому логирование невозможно. Но в то же
время ошибку это не создаёт — просто фасад уходит в состояние no-operation («ничего не делаю»).
Добавим имплементацию logback-classic — «родную» имплементацию фасада:
<dependencies>
<!-- Source: https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.17</version>
<scope>compile</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.32</version>
<scope>compile</scope>
</dependency>
</dependencies>
Попробуем запустить наше тестовое приложение ещё раз:
19:16:12.799 [main] DEBUG io.devmentor.learning.Main -- Logging debug message
19:16:12.802 [main] INFO io.devmentor.learning.Main -- Logging info message
19:16:12.802 [main] WARN io.devmentor.learning.Main -- Logging warn message
19:16:12.802 [main] ERROR io.devmentor.learning.Main -- Logging error message
Process finished with exit code 0
Вот оно, решение нашей проблемы! Добавляем имплементацию, фасад видит её, регистрирует — и всё начинает работать, логи появляются в консоли.
DEBUG. А это значит, что все логи уровнем ниже (у нас это
TRACE) выводиться не будут. Чтобы это поведение изменить, нужно задать дополнительную
конфигурацию.Напомню: ROOT-логгер — это единственный логгер, который создаётся на этапе инициализации. В классе
Main мы явно создаём ещё один логгер, но не задаём ему своего уровня логирования — а значит,
будет использоваться уровень ROOT. Именно для этого ROOT в Logback и задан уровень DEBUG.
В следующей статье туториала мы будем настраивать логгеры и узнаем, что нужно добавить в конфигурацию,
чтобы вывести все пять сообщений.
Log4j API + Log4j Impl
Давайте теперь посмотрим на Log4j. Подключим фасад:
<dependencies>
<!-- Source: https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.25.3</version>
<scope>compile</scope>
</dependency>
</dependencies>
Добавим логи в тестовое приложение:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Main {
private static final Logger log = LogManager.getLogger(Main.class);
public static void main(String[] args) {
log.trace("Logging trace message");
log.debug("Logging debug message");
log.info("Logging info message");
log.warn("Logging warn message");
log.error("Logging error message");
}
}
И попробуем запустить. Для этого логгера мы получим такое сообщение:
2026-02-21T12:16:25.988147800Z main ERROR Log4j API could not find a logging provider.
Process finished with exit code 0
Если Logback нас просто информировал об отсутствии имплементации, то Log4j не просто говорит об этом, но и падает с ошибкой. Другими словами, с Log4j мы либо получаем логи работающими, либо падаем с ошибкой — и приложение не запустится, пока мы не исправим проблему.
Давайте добавим провайдер:
<dependencies>
<!-- Source: https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.25.3</version>
<scope>compile</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.25.3</version>
<scope>compile</scope>
</dependency>
</dependencies>
Теперь получаем такой вывод в консоль:
2026-02-22T18:26:01.386235500Z main ERROR Logging error message
Process finished with exit code 0
Логирование заработало! Но только один лог из пяти появился в консоли. Это связано с тем, что в отличие от
Logback в Log4j уровень логирования по умолчанию — ERROR. Если нас это не устраивает, нам нужно
создать файл конфигурации и задать правила. О том, как этого добиться, мы будем говорить в следующей статье
туториала.
Java Util Logging
И последний логгер. Для него, как мы уже говорили выше, не нужны никакие дополнительные зависимости — всё,
что нужно, уже есть в самой Java (в JDK, в пакете java.util.logging).
Так выглядит наше тестовое приложение:
package io.devmentor.learning;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Main {
private static final Logger logger = Logger.getLogger(Main.class.getName());
public static void main(String[] args) {
logger.log(Level.ALL, "Logging all message");
logger.log(Level.FINEST, "Logging finest message");
logger.log(Level.FINER, "Logging finer message");
logger.log(Level.FINE, "Logging fine message");
logger.log(Level.CONFIG, "Logging config message");
logger.log(Level.INFO, "Logging info message");
logger.log(Level.WARNING, "Logging warning message");
logger.log(Level.SEVERE, "Logging severe message");
logger.log(Level.OFF, "Logging off message");
}
}
Запускаем и получаем такой вывод в консоль:
Mar 12, 2026 8:55:48 AM io.devmentor.learning.Main main
INFO: Logging info message
Mar 12, 2026 8:55:48 AM io.devmentor.learning.Main main
WARNING: Logging warning message
Mar 12, 2026 8:55:48 AM io.devmentor.learning.Main main
SEVERE: Logging severe message
Mar 12, 2026 8:55:48 AM io.devmentor.learning.Main main
OFF: Logging off message
Process finished with exit code 0
Как видите, у этого логгера уровень логирования по умолчанию находится на уровне INFO.
Выводы
Как видите, все три логгера позволяют нам логировать поведение нашего приложения, используя разные уровни логирования, что очень удобно: такие логи можно фильтровать по уровню важности и видеть самые важные из них, которым нужно уделить самое пристальное внимание. В то время как остальные нужны только когда мы отлаживаем работу приложения и могут включаться по требованию через конфигурацию.
Как вы заметили, все три логгера по умолчанию выводят логи в разном формате. Для наглядности я собрал три лога от разных логгеров, идущие друг за другом в одном блоке:
09:12:14.333 [main] INFO io.devmentor.learning.Main -- Logging info message
2026-03-12T08:12:50.540241400Z main ERROR Logging error message
Mar 12, 2026 8:55:48 AM io.devmentor.learning.Main main
INFO: Logging info message
И представьте себе, как у нас будут выглядеть общие логи нашего приложения, если в нём используются разные логгеры одновременно. Согласитесь, что это не очень удобно. Да и фильтровать потом такие логи будет непросто, так как для каждого типа нужны будут свои правила.
Напомню, что ситуация с разными логгерами может получиться, если мы подключаем зависимости в проект и эти зависимости используют разные логгеры у себя. Для решения этой проблемы были придуманы «переходники» («bridges»). Они позволяют транслировать логи одного типа в другой автоматически. То есть в нашем финальном лог-файле мы всегда видим логи в одинаковом формате — в формате логгера, который мы выбрали для своего приложения.
И, конечно же, часто бывает так, что нам нужно настроить разные уровни логирования в зависимости от пакета и/или в зависимости от окружения (локальное, тестовое, продакшн). Здесь нам на помощь приходит возможность конфигурации, которую мы и рассмотрим в следующей статье.