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.

См. Логирование Java, Основы иcпользования Maven.