Log4j 2
Log4j 2 — реализация логирования: API создаёт events, Core направляет их через filters/layouts/appenders. В приложении часто пишут через SLF4J façade, а Log4j 2 подключают как единственный backend.
flowchart LR Code["logger.info(...)"] --> API["SLF4J / Log4j API"] API --> Backend["Log4j Core"] Backend --> Filter["Level + Filter"] Filter --> Layout["Pattern / JSON Layout"] Layout --> Appender["Console / File / Remote"]
Уровни
TRACE < DEBUG < INFO < WARN < ERROR. Настройка INFO пропускает INFO и более серьёзные events. Уровень выбирают по смыслу:
- TRACE — детальная последовательность алгоритма;
- DEBUG — диагностические детали для разработчика;
- INFO — редкие события жизненного цикла и значимые операции;
- WARN — деградация, из которой система восстановилась;
- ERROR — операция не выполнена и требует внимания.
Параметризованное сообщение
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
log.info("Order accepted, orderId={}, itemCount={}", orderId, items.size());
try {
process(order);
} catch (RuntimeException ex) {
log.error("Order processing failed, orderId={}", orderId, ex);
throw ex;
}Placeholder не строит строку заранее при выключенном уровне. Exception передаётся последним аргументом, иначе stack trace можно потерять.
Контекст
MDC/ThreadContext добавляет traceId, requestId и другие короткие идентификаторы ко всем events потока:
try (var ignored = MDC.putCloseable("requestId", requestId)) {
service.handle(command);
}В thread pool или reactive pipeline контекст нужно переносить явно и очищать, иначе один запрос получит metadata другого.
Конфигурация
<Configuration status="WARN">
<Appenders>
<Console name="Console">
<PatternLayout
pattern="%d{ISO8601} %-5level [%t] %c requestId=%X{requestId} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="com.example.demo" level="DEBUG"/>
<Root level="INFO">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>В контейнере обычно пишу в stdout/stderr, а rotation и доставку берёт платформа. File appender требует лимитов, rotation и контроля диска.
Async logging
Асинхронный appender уменьшает задержку caller, но добавляет очередь. При перегрузке надо знать политику: блокировать, отбрасывать или синхронно писать. Перед shutdown очередь должна успеть flush. “Async” не означает бесплатное логирование больших объектов.
Безопасность
Не логирую пароль, token, cookie, полный персональный payload. Динамическая конфигурация и старые версии logging libraries — часть attack surface; зависимости обновляю и проверяю transitives. Запрещаю lookup/format features, которые не нужны приложению.
Типичные сбои
- В classpath два backend, и SLF4J сообщает о нескольких providers.
- Один event пишется дважды из-за additivity родительского logger.
DEBUGвключён глобально и заполняет диск.- Stack trace потерян из-за
log.error(ex.getMessage()). - MDC не очищен в thread pool.
- Remote appender блокирует business thread при недоступности collector.
Вопросы для собеседования
Чем logger отличается от appender? Logger создаёт/маршрутизирует event, appender записывает его в destination.
Зачем façade SLF4J? Отделить application API от конкретного backend.
Почему parameterized logging лучше конкатенации? Не строит строку без необходимости и сохраняет структурируемые аргументы.
Что такое additivity? Передача event от дочернего logger его родительским appenders.
Какой риск у async appender? Очередь может переполниться, а последние events — не успеть записаться при аварийном shutdown.