Уровни логирования в Java: настройка Log4j2, Logback и SLF4J в 2026 году | AdminWiki

Уровни логирования в Java: настройка Log4j2, Logback и SLF4J в 2026 году

01 октября 2026 12 мин. чтения

Порядок уровней логирования в Java одинаков в SLF4J, Logback и Log4j2: TRACE < DEBUG < INFO < WARN < ERROR < FATAL. Логгер пишет сообщение только тогда, когда его уровень не ниже эффективного уровня самого логгера. При root level=INFO вызов logger.debug("...") не создаст ни одной строки в логе, хотя код выполнится полностью.

Рабочая схема для продакшена: root на INFO, отдельные пакеты на DEBUG только там, где разбирают инцидент. События WARN и ERROR уходят в отдельный файл с более длинным сроком хранения, а консольный appender остаётся для контейнеров и kubectl logs.

Ниже собраны готовые конфиги: logback.xml для XML-настройки, log4j2.properties для Log4j2, шаблоны MDC с requestId и userId, фильтры ThresholdFilter и LevelFilter, счётчики ошибок в Micrometer и чек-лист миграции с Log4j на Logback.

Уровни логирования в Java: полная иерархия и правила выбора

Шесть уровней покрывают весь путь сообщения: от трассировки вызовов до аварийного завершения процесса. Ниже таблица с примерами и рекомендациями, на каких стендах уровень уместен.

УровеньЧто писатьПример сообщенияDevProd
TRACEВход и выход из методов, состояние циклов, полные тела HTTP-запросов к внешним API"enter createOrder, items=12"ДаНет
DEBUGЗначения переменных, параметры SQL, выбранные ветки условий, содержимое кэша"cache miss for key=user:42"ДаТочечно
INFOСтарт и остановка приложения, успешные бизнес-операции, смена конфигурации"User {} logged in"ДаДа
WARNПовторный запрос, устаревший вызов API, приближение к лимиту пула соединений"Retry 2/3 for payment gateway"ДаДа
ERRORОшибка, после которой приложение продолжает обслуживать трафик"Order {} failed: timeout"ДаДа
FATALСбой, после которого процесс не может работать дальше"Cannot bind port 8080"РедкоРедко

В SLF4J метода fatal() нет, в Logback класса FATAL тоже нет, он есть только в Log4j2. Практический вывод: на уровне ERROR в Logback пишите события, которые в Log4j2 уходили в FATAL, и не заводите отдельную ветку логики под этот уровень.

Пример расстановки вызовов в сервисе на SLF4J:

private static final Logger log = LoggerFactory.getLogger(OrderService.class);

log.trace("enter createOrder, items={}", items.size());
log.debug("resolved warehouseId={} for sku={}", warehouseId, sku);
log.info("Order {} created, total={}", orderId, total);
log.warn("Payment gateway slow: {} ms, retry {}/3", elapsed, attempt);
log.error("Order {} failed, fallback to queue", orderId, ex);

Фигурные скобки в шаблоне подставляются только при включённом уровне, поэтому toString() у аргументов не вызывается впустую. Конкатенацию строк вида "Order " + orderId + " failed" в горячем коде не используйте: она выполняется всегда. Для дорогих вычислений оставляйте защиту: if (log.isDebugEnabled()) { ... }.

Как уровень логгера влияет на фильтрацию сообщений

У каждого логгера есть эффективный уровень. Если уровень не задан явно, он наследуется от ближайшего родителя в иерархии имён, вплоть до root. Сообщения ниже эффективного уровня отбрасываются до создания LoggingEvent, поэтому до appender они не доходят.

Пример: root level=INFO, logger com.example level=DEBUG. Тогда сообщения DEBUG из пакета com.example попадут в лог, а такие же DEBUG из com.other будут проигнорированы. Проверить, какой уровень оказался эффективным, можно программно: LoggerFactory.getLogger("com.example").isDebugEnabled() вернёт true, а для com.other тот же вызов даст false.

Уровень задаётся в конфиге, а не в коде. В Logback это элемент logger с атрибутом level, в Log4j2 строка logger.example.level = debug, в Spring Boot свойство logging.level.com.example=DEBUG, которое перекрывает значение из logback.xml.

Таблица соответствия уровней в SLF4J, Logback и Log4j2

УровеньSLF4J (метод API)Logback (ch.qos.logback.classic.Level)Log4j2 (org.apache.logging.log4j.Level)
TRACElog.trace()TRACETRACE
DEBUGlog.debug()DEBUGDEBUG
INFOlog.info()INFOINFO
WARNlog.warn()WARNWARN
ERRORlog.error()ERRORERROR
FATALнет методанет константыFATAL

Порядок сравнения во всех трёх библиотеках совпадает, поэтому конфиг переносится между Logback и Log4j2 почти без правок. Единственное расхождение при миграции: вызовы log.fatal(...) нужно заменить на log.error(...) или на log.error(MarkerFactory.getMarker("FATAL"), ...), иначе проект не скомпилируется.

Настройка Logback через XML: готовый конфиг для production

Logback читает logback.xml из корня classpath. В Spring Boot берите logback-spring.xml, чтобы работали теги springProfile и springProperty. Ориентируйтесь на ветки Logback 1.5.x и SLF4J 2.0.x: связка SLF4J 1.7 остаётся только в legacy-проектах. Точный номер патча сверяйте в Maven Central перед деплоем, регрессии в связке 1.5.x случались именно на минорных обновлениях.

Пример logback.xml с комментариями

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

  <!-- Куда писать: консоль для контейнеров, файлы для виртуальных машин -->
  <property name="LOG_DIR" value="${LOG_DIR:-./logs}"/>
  <property name="PATTERN"
            value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} req=%X{requestId} user=%X{userId} - %msg%n"/>

  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder><pattern>${PATTERN}</pattern></encoder>
  </appender>

  <!-- Основной файл: ротация по дню и по 100 МБ, хранение 14 дней -->
  <appender name="APP" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_DIR}/app.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
      <fileNamePattern>${LOG_DIR}/app.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
      <maxFileSize>100MB</maxFileSize>
      <maxHistory>14</maxHistory>
      <totalSizeCap>5GB</totalSizeCap>
    </rollingPolicy>
    <encoder><pattern>${PATTERN}</pattern></encoder>
  </appender>

  <!-- Только ERROR и FATAL, храним месяц -->
  <appender name="ERRORS" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_DIR}/errors.log</file>
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
      <level>ERROR</level>
    </filter>
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
      <fileNamePattern>${LOG_DIR}/errors.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
      <maxFileSize>50MB</maxFileSize>
      <maxHistory>30</maxHistory>
    </rollingPolicy>
    <encoder><pattern>${PATTERN}</pattern></encoder>
  </appender>

  <!-- Точечный DEBUG: только на время разбора инцидента -->
  <logger name="com.example" level="DEBUG"/>
  <logger name="org.hibernate.SQL" level="DEBUG"/>

  <root level="INFO">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="APP"/>
    <appender-ref ref="ERRORS"/>
  </root>
</configuration>

Что здесь важно для эксплуатации. SizeAndTimeBasedRollingPolicy даёт в имени файла и дату, и индекс %i, поэтому ротация не затирает логи при всплеске объёма внутри суток. totalSizeCap=5GB защищает диск от переполнения, когда приложение внезапно начинает писать в 10 раз больше. Файл errors.log получает только ERROR и FATAL за счёт ThresholdFilter, поэтому дежурный читает его целиком, без grep по шуму.

Проверить конфиг без пересборки приложения можно так:

java -Dlogback.configurationFile=/path/to/logback.xml -jar app.jar

Фильтрация по уровню с помощью ThresholdFilter и LevelFilter

ThresholdFilter пропускает события с уровнем не ниже указанного, всё остальное отбрасывает. LevelFilter пропускает только один конкретный уровень и управляется двумя действиями: onMatch и onMismatch со значениями ACCEPT, DENY, NEUTRAL.

Файл warnings.log, куда попадают только предупреждения:

<appender name="WARN_ONLY" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <file>${LOG_DIR}/warnings.log</file>
  <filter class="ch.qos.logback.classic.filter.LevelFilter">
    <level>WARN</level>
    <onMatch>ACCEPT</onMatch>
    <onMismatch>DENY</onMismatch>
  </filter>
  <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>${LOG_DIR}/warnings.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
    <maxHistory>30</maxHistory>
  </rollingPolicy>
  <encoder><pattern>${PATTERN}</pattern></encoder>
</appender>

Фильтры в одном appender применяются по порядку и комбинируются: LevelFilter с onMismatch=DENY отсекает всё, кроме WARN, а добавленный следом ThresholdFilter с уровнем ERROR вернёт в поток ошибки, если они нужны в том же файле. Для отдельного файла с ошибками достаточно одного ThresholdFilter, как в примере выше.

Настройка Log4j2 через properties: пошаговое руководство

Log4j2 принимает XML, JSON, YAML и properties. Properties удобнее для ревью в Git: плоский список строк виден в diff целиком. Порядок блоков значения не имеет, важна только корректность имён. Ориентируйтесь на ветку 2.24.x; версии ниже 2.17 в продакшене использовать нельзя из-за Log4Shell.

Пример log4j2.properties с комментариями

# status=warn покажет внутренние ошибки конфигурации в stderr
status = warn
name = PropertiesConfig

# Консольный appender
appender.console.type = Console
appender.console.name = console
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} req=%X{requestId} user=%X{userId} - %msg%n

# Файловый appender с ротацией по времени и размеру
appender.rolling.type = RollingFile
appender.rolling.name = rolling
appender.rolling.fileName = logs/app.log
appender.rolling.filePattern = logs/app-%d{yyyy-MM-dd}-%i.log.gz
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} req=%X{requestId} user=%X{userId} - %msg%n
appender.rolling.policies.type = Policies
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
appender.rolling.policies.time.interval = 1
appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
appender.rolling.policies.size.size = 100MB
appender.rolling.strategy.type = DefaultRolloverStrategy
appender.rolling.strategy.max = 14

# Отдельный файл только для ошибок
appender.errors.type = RollingFile
appender.errors.name = errors
appender.errors.fileName = logs/errors.log
appender.errors.filePattern = logs/errors-%d{yyyy-MM-dd}-%i.log.gz
appender.errors.layout.type = PatternLayout
appender.errors.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
appender.errors.filter.threshold.type = ThresholdFilter
appender.errors.filter.threshold.level = error
appender.errors.policies.type = Policies
appender.errors.policies.time.type = TimeBasedTriggeringPolicy
appender.errors.policies.time.interval = 1

# Корневой логгер
rootLogger.level = info
rootLogger.appenderRef.console.ref = console
rootLogger.appenderRef.rolling.ref = rolling
rootLogger.appenderRef.errors.ref = errors

# Точечный DEBUG для пакета com.example
logger.example.name = com.example
logger.example.level = debug
logger.example.additivity = false
logger.example.appenderRef.console.ref = console

Две настройки экономят часы отладки. status = warn печатает в stderr сообщения о том, что Log4j2 не смог прочитать конфиг, при status = debug видно, откуда именно загружен файл. Параметр additivity = false останавливает передачу событий родительскому логгеру, иначе строки com.example попадут в консоль дважды: через собственный appender и через root.

java -Dlog4j2.configurationFile=/path/to/log4j2.properties -jar app.jar

Фильтрация по уровню в Log4j2 properties

Фильтр в properties задаётся тремя строками: тип, имя и уровень. ThresholdFilter в Log4j2 пропускает события не ниже указанного уровня, остальные отклоняет:

appender.errors.filter.threshold.type = ThresholdFilter
appender.errors.filter.threshold.level = error

Чтобы такой файл вообще заполнялся, appender нужно подключить к rootLogger строкой rootLogger.appenderRef.errors.ref = errors. Полезно держать в конфиге и защиту от шума самого движка логирования: logger.log4j.name = org.apache.logging.log4j, logger.log4j.level = warn. Так внутренние сообщения библиотеки не попадут в общий поток.

MDC и контекст логирования: добавление requestId и userId

MDC (Mapped Diagnostic Context) хранит пары ключ-значение в контексте текущего потока, и PatternLayout выводит их через %X{key}. Значения живут до очистки, поэтому в шаблоне появляется связка запроса и пользователя без правки каждой строки логирования.

MDC.put("requestId", requestId);
MDC.put("userId", userId);
try {
    process(request);
} finally {
    MDC.clear();
}

В Log4j2 аналог называется ThreadContext, синтаксис тот же: ThreadContext.put("requestId", requestId). Шаблон вывода в Logback и Log4j2 совпадает: %X{requestId} %X{userId}. При работе с пулами потоков и @Async контекст не переносится автоматически: поток возвращается в пул вместе с чужими значениями. Для Reactor и MicrometerContext есть библиотека io.micrometer:context-propagation, которая прокидывает MDC между потоками. Подробнее о структурировании таких полей мы разбирали в материале про структурированное логирование и observability.

Пример MDC в Spring Boot с фильтром

Фильтр генерирует requestId, кладёт его в MDC до входа в контроллер и очищает контекст после ответа. Наследование от OncePerRequestFilter гарантирует один запуск на запрос.

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.UUID;

@Component
public class MdcRequestFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        String requestId = request.getHeader("X-Request-Id");
        if (requestId == null || requestId.isBlank()) {
            requestId = UUID.randomUUID().toString();
        }
        MDC.put("requestId", requestId);
        MDC.put("userId", resolveUserId(request));
        try {
            chain.doFilter(request, response);
        } finally {
            response.setHeader("X-Request-Id", requestId);
            MDC.clear();
        }
    }
}

Возврат X-Request-Id в заголовке ответа даёт бесплатную связку: клиент или балансировщик присылает этот идентификатор в тикет, и поиск в логах сводится к одному grep. MDC.clear() в finally обязателен, иначе значение протечёт в следующий запрос на том же потоке Tomcat.

Интеграция логирования со Spring Boot и Micrometer

Spring Boot даёт два уровня управления: свойства для быстрой смены уровней и обычный конфиг Logback или Log4j2 для appender'ов, фильтров и ротации. В application.properties достаточно четырёх строк:

logging.level.root=INFO
logging.level.com.example=DEBUG
logging.level.org.hibernate.SQL=DEBUG
logging.file.name=logs/app.log
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} req=%X{requestId} - %msg%n
logging.logback.rollingpolicy.max-file-size=100MB
logging.logback.rollingpolicy.max-history=14

Свойства logging.level.* имеют приоритет над level в logback.xml, поэтому уровень для пакета можно поднять через переменную окружения LOGGING_LEVEL_COM_EXAMPLE=DEBUG без пересборки образа. Политику ротации для Logback удобнее держать в properties, тогда один и тот же образ работает и на стенде, и в продакшене.

Переключение Spring Boot на Log4j2

Стартер spring-boot-starter-logging тянет Logback, поэтому его исключают и добавляют spring-boot-starter-log4j2:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <exclusions>
    <exclusion>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-logging</artifactId>
    </exclusion>
  </exclusions>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

Конфигурацию Boot ищет в файле log4j2-spring.xml, а не в log4j2.xml: только в нём работают теги springProfile и springProperty. После переключения проверьте, что в дереве зависимостей не осталось logback-classic, иначе оба движка будут писать в консоль одновременно.

Сбор метрик логирования с Micrometer

Уровни логирования хорошо превращаются в метрики: график роста ERROR показывает инцидент раньше, чем дежурный откроет файл. Счётчик по имени логгера регистрируется через MeterRegistry и уходит в Prometheus или Grafana.

import io.micrometer.core.instrument.MeterRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    private static final Logger log = LoggerFactory.getLogger(OrderService.class);
    private final MeterRegistry registry;

    public OrderService(MeterRegistry registry) {
        this.registry = registry;
    }

    public void process(String orderId) {
        try {
            // бизнес-логика заказа
        } catch (Exception ex) {
            registry.counter("app.log.errors", "logger", "OrderService").increment();
            log.error("Order {} failed", orderId, ex);
        }
    }
}

Связку логирования и метрик удобно строить поверх общего конвейера телеметрии, где логи и метрики маршрутизируются одними правилами: готовый пример есть в руководстве по маршрутизации логов и метрик. Отдельный счётчик по каждому сообщению создавать не нужно, кардинальность метрики быстро выйдет из-под контроля: держите один счётчик с тегом уровня и тегом сервиса. Для автоматического учёта есть LogbackMetrics: биндер подписывается на события Logback и публикует метрику logback.events с тегом level, поэтому свои счётчики нужны только для бизнес-специфичных ошибок.

Миграция с Log4j на Logback: пошаговый чек-лист

Log4j 1.x не получает обновлений с 2015 года, а в Log4j2 уязвимость Log4Shell (CVE-2021-44228) закрыта в ветках 2.16 и 2.17. Если проект остаётся на Log4j2, держите ветку 2.24.x и проверяйте зависимости транзитивно. Если переходите на Logback, порядок шагов такой.

Замена зависимостей и конфигов

  1. Соберите текущее дерево зависимостей: mvn dependency:tree | grep -i log4j или ./gradlew dependencies | grep -i log4j. Так находятся транзитивные log4j-core и log4j:log4j, которые тянут старые библиотеки.
  2. Исключите оба артефакта и добавьте logback-classic версии 1.5.x вместе с SLF4J 2.0.x.
  3. Перенесите конфиг: log4j.properties или log4j.xml заменяется на logback.xml, структура отображается построчно.
  4. Замените импорты org.apache.log4j.Logger и org.apache.logging.log4j.Logger на org.slf4j.Logger, а Logger.getLogger(...) на LoggerFactory.getLogger(...).
  5. Прогоните тесты и проверьте логи на стенде до продакшена.
<dependency>
  <groupId>com.example</groupId>
  <artifactId>legacy-lib</artifactId>
  <exclusions>
    <exclusion>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
    </exclusion>
    <exclusion>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
    </exclusion>
  </exclusions>
</dependency>

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.5.x</version>
</dependency>

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>2.0.x</version>
</dependency>
configurations.all {
    exclude group: "log4j", module: "log4j"
    exclude group: "org.apache.logging.log4j", module: "log4j-core"
}

dependencies {
    implementation "ch.qos.logback:logback-classic:1.5.x"
    implementation "org.slf4j:slf4j-api:2.0.x"
}

Соответствие настроек при переносе конфига:

Log4jLogback
log4j.rootLogger=INFO, stdout<root level="INFO"> с appender-ref на stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout<encoder><pattern>...</pattern></encoder>
log4j.logger.com.example=DEBUG<logger name="com.example" level="DEBUG"/>
log4j.appender.file.MaxFileSize=100MBSizeAndTimeBasedRollingPolicy с maxFileSize

Дальше логи имеет смысл перевести в JSON, чтобы request_id и level стали полями, а не текстом: пошаговый план перехода без остановки сервисов разобран в статье про переход от plain text к JSON-логам.

Проверка после миграции

  1. В дереве зависимостей нет log4j: mvn dependency:tree | grep -i log4j возвращает пустой вывод.
  2. Приложение стартует, в консоли нет сообщений об ошибке конфигурации Logback. Для диагностики поднимите <configuration debug="true"> на стенде и уберите флаг после проверки.
  3. Логи пишутся и в консоль, и в файл: tail -f logs/app.log показывает свежие строки.
  4. Уровни соответствуют ожидаемым: grep -c "ERROR" logs/app.log и grep -c "DEBUG" logs/app.log дают правдоподобные числа.
  5. MDC выводится: в строке есть req= и user=, при отключённом фильтре значения не протекают между запросами.
  6. Ротация работает: после понижения maxFileSize до 1MB появляются файлы app.<дата>.0.log.gz и app.<дата>.1.log.gz.

Чек-лист по безопасности и воспроизводимости конфигов логирования

  1. Не пишите в лог пароли, токены, номера карт и персональные данные. Перед релизом пройдитесь по сообщениям уровней DEBUG и TRACE, критерии отбора собраны в материале что логировать, а что нет.
  2. В продакшене держите root на INFO или WARN. DEBUG включается точечно для конкретного пакета и снимается после инцидента.
  3. Настройте ротацию с ограничением объёма: maxHistory и totalSizeCap в Logback, DefaultRolloverStrategy.max в Log4j2.
  4. Ограничьте доступ к файлам: chmod 640 logs/app.log и каталог с правами 750, чтобы логи не читал посторонний пользователь в контейнере или на shared-хосте. Проверка: ls -l logs/.
  5. Зафиксируйте версии библиотек в манифесте сборки: Logback 1.5.x, SLF4J 2.0.x, Log4j2 2.24.x. Плавающие диапазоны версий усложняют воспроизведение бага на другом стенде.
  6. Проверьте конфиг на тестовом стенде до деплоя: java -Dlogback.configurationFile=... -jar app.jar и java -Dlog4j2.configurationFile=... -jar app.jar позволяют прогнать файл без пересборки.
  7. Очищайте MDC в finally, иначе контекст одного запроса попадёт в строки следующего.
  8. Для Spring Boot держите профили в logback-spring.xml или log4j2-spring.xml, чтобы dev и prod отличались уровнями, а не правками в коде.
  9. Проверьте, что логи с хоста не удаляются вместе с контейнером: смонтируйте каталог логов в volume или отправляйте поток в сборщик.
  10. Добавьте в CI шаг, который падает при появлении log4j-core в дереве зависимостей, если проект уже переведён на Logback.

Начните с малого: поднимите errors.log через ThresholdFilter и один счётчик app.log.errors в Micrometer. Эти два изменения дают дежурному точку входа в инцидент без чтения всего потока логов.

Поделиться: