JavaRush /Курсы /JAVA 25 SELF /Форматирование и уровни логов: best practices

Форматирование и уровни логов: best practices

JAVA 25 SELF
63 уровень , 1 лекция
Открыта

1. Структура лог-сообщения

Давайте представим, что логи — это не просто «поток сознания» вашей программы, а ценный журнал, в котором через месяц или год вы или ваш коллега сможете найти ответ на вопрос: «Что же тут, чёрт побери, случилось?». Чтобы это было возможно, каждое лог‑сообщение должно быть структурированным. Обычно (и это стандарт в большинстве библиотек) каждое сообщение содержит:

  • Время события — когда это произошло.
  • Уровень — насколько это важно (INFO, ERROR и т. д.).
  • Имя логгера — обычно это имя класса или компонента.
  • Текст сообщения — что именно произошло.
  • Стек-трейс (если есть ошибка) — чтобы понять, где и почему.

Вот пример хорошо форматированной строки лога (на примере Log4j/SLF4J):

2024-06-16 18:42:07,123 INFO  com.example.MainApp - Пользователь вошёл в систему: username=vasya

А если произошла ошибка:

2024-06-16 18:42:10,456 ERROR com.example.LoginService - Ошибка авторизации пользователя: vasya
java.lang.IllegalArgumentException: Неверный пароль
    at com.example.LoginService.checkPassword(LoginService.java:42)
    ...

Почему это важно?
Когда приложение работает долго, логи могут занимать гигабайты. Если сообщения не структурированы, найти проблему становится задачей уровня «угадай мелодию по шуму вентилятора».

2. Форматирование сообщений

Почему не стоит делать так:

logger.info("Пользователь " + username + " вошёл в систему");

Вроде бы всё просто, но здесь есть подвох: даже если уровень логирования сейчас стоит на ERROR, строка внутри скобок всё равно будет собрана (конкатенация отработает), а это — лишние траты ресурсов. В больших системах, где логов тысячи строк в секунду, это может выливаться в реальные задержки.

Как правильно: шаблоны и параметры

Современные библиотеки (например, SLF4J и Log4j 2) поддерживают шаблоны с параметрами:

logger.info("Пользователь {} вошёл в систему", username);

Здесь строка будет собрана только если уровень логирования позволяет это сообщение вывести. Если сейчас стоит, например, WARN, то даже вычисления строки не будет — экономия ресурсов и нервов.

Бонус: если передать несколько параметров, они подставятся по порядку:

logger.info("Пользователь {} выполнил действие {} на объекте {}", username, action, objectId);

Логирование исключений (stacktrace)

Если вы ловите исключение, не надо вручную добавлять стек‑трейс к сообщению:

// НЕ НАДО:
logger.error("Ошибка: " + ex.getMessage() + "\n" + Arrays.toString(ex.getStackTrace()));

Правильно:

logger.error("Ошибка при обработке запроса", ex);

SLF4J и Log4j сами красиво добавят стек‑трейс в лог.

Пример: сравнение подходов

// Плохо (конкатенация всегда выполняется)
logger.debug("Объект: " + expensiveToString(obj));

// Хорошо (ленивое формирование)
logger.debug("Объект: {}", obj);

3. Выбор уровней логирования

Если у вас в логах всё подряд на уровне ERROR, то это уже не логи, а «красная лампочка». Если всё — на DEBUG, то вы утонете в деталях. Давайте разберёмся, когда какой уровень использовать.

Уровень Для чего используется Пример сообщения
ERROR
Критические сбои, из-за которых система работает неправильно или не работает вообще «Ошибка подключения к базе данных»
WARN
Важные предупреждения, которые не критичны, но требуют внимания «Не удалось найти пользователя, использую guest»
INFO
Обычные события, отражающие нормальную работу приложения «Пользователь зарегистрирован: vasya»
DEBUG
Подробная информация для отладки, не нужна в продакшене «Вызван метод checkPassword с параметрами ...»
TRACE
Самая детальная информация, обычно для глубокой диагностики «Начало цикла обработки: i=0»

Примеры типичных сообщений

  • ERROR — не удалось записать файл, поймано необработанное исключение, сервис недоступен.
  • WARN — устаревший API, подозрительное поведение пользователя, превышён лимит попыток.
  • INFO — пользователь вошёл/вышел, завершена обработка заказа, старт приложения.
  • DEBUG — параметры запроса, значения переменных, промежуточные результаты вычислений.
  • TRACE — вход/выход в методы, внутренние циклы, подробности работы алгоритмов.

Совет:
В продакшене обычно включают только INFO и выше, иногда WARN и ERROR. DEBUG и TRACE — только при поиске сложных багов.

4. Best practices (лучшие практики логирования)

Не логируйте чувствительные данные

Пароли, токены, номера кредитных карт — всему этому не место в логах. Даже если кажется, что «лог‑файл — только для меня», вспомните про GDPR и коллегу, который случайно отправит лог в общий чат.

// Плохо:
logger.info("Пользователь {} вошёл с паролем {}", username, password);

// Хорошо:
logger.info("Пользователь {} вошёл в систему", username);

Не злоупотребляйте уровнем ERROR

Если вы пишете всё подряд через logger.error, то когда действительно случится катастрофа, никто её не заметит — все привыкли к «красным лампочкам». Используйте ERROR только для ситуаций, когда приложение реально не может продолжать работу или нарушена бизнес‑логика.

Логируйте исключения с полным стеком

Не пишите только ex.getMessage(), иначе вы никогда не узнаете, где именно произошла ошибка. Передавайте исключение вторым параметром в логгер.

logger.error("Ошибка при обработке запроса", ex);

Используйте уникальные идентификаторы (корреляция событий)

В больших системах полезно присваивать каждому запросу, пользователю или операции уникальный идентификатор. Это поможет «сшить» события из разных частей системы.

logger.info("Начата обработка заказа: orderId={}", orderId);
logger.info("Заказ обработан успешно: orderId={}", orderId);

Не пишите в логи всё подряд

Если логов слишком много — они становятся бесполезными. Не логируйте каждую строчку кода, иначе найти нужную информацию будет невозможно.

Форматируйте сообщения понятно

Пишите сообщения так, чтобы их понял не только автор кода, но и человек, который будет читать логи через полгода. Избегайте аббревиатур, неочевидных сокращений и «шуток для своих».

5. Практика: настройка формата лога и уровней

Пример настройки формата в Log4j2 (log4j2.xml)

<Configuration>
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="Console"/>
    </Root>
  </Loggers>
</Configuration>

Что это значит?

  • %d{...} — время события.
  • %-5level — уровень (ERROR, INFO и т. д.).
  • %logger{36} — имя логгера (обычно класс).
  • %msg — само сообщение.

Пример кода с разными уровнями логирования (SLF4J)

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogDemo {
    private static final Logger logger = LoggerFactory.getLogger(LogDemo.class);

    public static void main(String[] args) {
        logger.info("Приложение запущено");
        logger.debug("Значение переменной x: {}", 42);

        try {
            throw new IllegalArgumentException("Ай-ай-ай!");
            // ...
        } catch (Exception ex) {
            logger.error("Произошла ошибка при запуске", ex);
        }
    }
}

Демонстрация разницы между уровнями

Если в настройках логгера стоит уровень INFO, то сообщения с уровнем DEBUG и ниже не будут выводиться. Попробуйте поменять уровень на debug в конфиге — увидите подробности.

6. Типичные ошибки

Ошибка №1: Конкатенация строк в логах. Очень часто новички пишут так:

logger.debug("Пользователь: " + user.getName() + ", роль: " + user.getRole());

В результате даже при выключенном DEBUG-уровне эти строки будут собираться, что ведёт к лишней нагрузке. Используйте параметры!

Ошибка №2: Логирование без стека исключения. Пишут только сообщение:

logger.error("Ошибка: " + ex.getMessage());

В итоге в логах нет информации, где именно произошла ошибка. Передавайте exception вторым параметром!

Ошибка №3: Логирование всего подряд на уровне ERROR. Если всё красное — ничего не красное. Используйте уровни по назначению, иначе важные ошибки затеряются среди «мелочей».

Ошибка №4: Логирование чувствительных данных. Никогда не пишите в логи пароли, токены, номера карт. Даже если кажется, что никто не увидит, жизнь любит сюрпризы.

Ошибка №5: Непонятные сообщения. Если сообщение в логе выглядит как «ERR42: fail», то через месяц вы сами не вспомните, что это значит. Пишите понятно и подробно.

Ошибка №6: Отсутствие уникальных идентификаторов. В сложных системах без orderId, userId и других идентификаторов вы не сможете «сшить» события и понять, что происходило с конкретным пользователем или заказом.

1
Задача
JAVA 25 SELF, 63 уровень, 1 лекция
Недоступна
Архитектор
Архитектор
1
Задача
JAVA 25 SELF, 63 уровень, 1 лекция
Недоступна
Социальная сеть
Социальная сеть
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ