☕️ Как работает @Slf4j Если пишешь
@Slf4j над классом, вызываешь
log.info(...) и не задумываешься, а как это работает, то пост для тебя. Разберём, что стоит за аннотацией, и как не напороться на неочевидные грабли.
🔹
Что делает аннотация@Slf4j аннотация Lombok'а. При компиляции он через annotation processor генерирует в классе поле:
private static final org.slf4j.Logger log =
org.slf4j.LoggerFactory.getLogger(YourClass.class);
Всё, никакой магии в рантайме. Просто кодогенерация на этапе javac. Если открыть .class — поле будет там как родное.
Нюанс: если и родитель, и наследник аннотированы
@Slf4j, каждый получит свой собственный логгер, привязанный к своему классу.
🔹 SLF4J — это фасад, а не реализацияКлючевая идея, которую стоит держать в голове: SLF4J сам ничего не логирует. Это API-контракт. Реальная работа на binding'е:
→ Logback — нативный бэкенд, писался теми же людьми
→
Log4j2 — через
log4j-slf4j2-impl→
java.util.logging — через
slf4j-jdk14Binding определяется
в classpath в compile/runtime. SLF4J при старте ищет класс org.slf4j.spi.SLF4JServiceProvider (начиная с 2.x) или StaticLoggerBinder (в 1.x). Нашёл — работает. Не нашёл — NOP logger, и ты молча теряешь все логи. В 2.x хотя бы warning кинет.
🔹
Два binding'а в classpathКлассическая проблема транзитивных зависимостей. Если в classpath окажутся и logback-classic, и log4j-slf4j2-impl — SLF4J выберет один (фактически — какой первый найдёт classloader). Поведение недетерминировано. Лечится в Maven через <exclusions> или в Gradle через exclude group.
🔹 Что под капотом вызоваlog.info("User {} logged in from {}", username, ip);Здесь
нет конкатенации строк. SLF4J использует параметризованные сообщения — плейсхолдеры {} заменяются лениво, только если уровень логирования активен. Это важно: вызов log.debug(...) при выключенном DEBUG не тратит ресурсы на toString() аргументов.
Но есть подвох. Если ты передаёшь выражение, которое само по себе дорого вычислять:
log.debug("Heavy result: {}", calculateSomethingExpensive());Метод
всё равно вызовется — аргументы вычисляются до передачи в метод (JLS, eager evaluation). Вариант обхода:
if (log.isDebugEnabled()) {
log.debug("Heavy result: {}", calculateSomethingExpensive());
}Или, если на Log4j2 API напрямую — там есть Supplier-based ленивые аргументы. В SLF4J 2.x появился fluent API:
log.atDebug()
.setMessage("Heavy result: {}")
.addArgument(() -> calculateSomethingExpensive())
.log();
🔹 Логирование исключенийПравильный способ — throwable
последним аргументом без плейсхолдера:
log.error("Payment failed for order {}", orderId, exception);Два {} — нет. Два аргумента — да. SLF4J сам определит, что последний аргумент — Throwable, и выведет стектрейс. Если засунуть exception в {}, получишь только toString() без стектрейса. Это одна из самых частых ошибок.
🔹
Мелочи, на которые стоит обратить внимание▪️ @Slf4j vs @Log4j2Lombok умеет генерировать логгеры для разных фасадов/реализаций.
@Slf4j — для SLF4J API,
@Log4j2 — напрямую для Log4j2 API (со всеми его фичами типа Supplier-аргументов и Markers). Выбирай осознанно.
▪️ Имя логгераПо умолчанию это FQCN класса. Можно переопределить:
@Slf4j(topic = "audit"). Удобно для выделения audit/security логов в отдельный appender.
▪️ Serializable классыLogger не сериализуем. Если класс implements Serializable, после десериализации log будет null. Lombok добавляет transient, но при ручном объявлении — легко забыть.
▪️ ТестыЕсли в тестовом classpath нет binding'а, логи молча пропадут. Кинь slf4j-simple в testImplementation / test scope, чтобы видеть что происходит.
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸
Библиотека джависта #CoreJava