Java for Beginner
870 subscribers
1.01K photos
275 videos
14 files
1.69K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Logback: нативная реализация SLF4J

Logback, также созданный Ceki Gülcü, разработан как преемник Log4j 1.x и нативная реализация SLF4J.

Он состоит из трех модулей:
logback-core — базовые классы для appenders, layouts, фильтров. Независим от SLF4J, может использоваться для других целей.
logback-classic — реализация SLF4J API. Содержит LoggerContext — реестр логгеров, и систему конфигурации.
logback-access — специализированный модуль для логирования HTTP-запросов в сервлет-контейнерах.

Архитектура Logback построена на компонентах: Logger формирует событие LoggingEvent, которое передается через фильтры к Appender. Appender записывает событие через Layout в целевой ресурс — консоль, файл, сокет, базу данных. Appenders комбинируются: один логгер может иметь несколько appenders для дублирования вывода.
Конфигурация выполняется через XML (logback.xml, logback-test.xml), Groovy или программно. Logback поддерживает автоматическую перезагрузку конфигурации при изменении файла без перезапуска приложения.


Log4j2: следующее поколение

Apache Log4j2 — полная перепись Log4j 1.x, разработанная с учетом требований современных высоконагруженных систем.

Ключевые архитектурные отличия от Logback:
Асинхронные Loggers на базе LMAX Disruptor — lock-free RingBuffer, обеспечивающий пропускную способность в миллионы сообщений в секунду с минимальной латентностью. В Logback асинхронность реализована через AsyncAppender с LinkedBlockingQueue, что менее эффективно при экстремальных нагрузках.
Lazy initialization — Log4j2 не создает appenders до первого лог-сообщения, что ускоряет старт приложения. Logback инициализирует всю конфигурацию при загрузке.
Garbage-free logging — при определенных конфигурациях Log4j2 избегает аллокаций объектов при логировании, что критично для low-latency систем с чувствительным GC.
Plugin architecture — расширяемость через аннотации @Plugin, что упрощает создание кастомных appenders, layouts и фильтров.

Log4j2 может работать как самостоятельный фреймворк или через SLF4J API (log4j-slf4j-impl binding). Для приложений, уже использующих SLF4J, переход на Log4j2 требует только замены binding и конфигурации, без модификации кода.


Сравнение и выбор реализации

Выбор между Logback и Log4j2 зависит от требований проекта:
Logback предпочтителен для стандартных Spring Boot-приложений, где он является дефолтной реализацией. Конфигурация через logback-spring.xml интегрирована с профилями Spring. Logback проще в настройке, имеет меньше зависимостей, широко документирован.
Log4j2 выбирают для высоконагруженных систем, где критична пропускная способность логирования. Асинхронные логгеры на Disruptor обеспечивают порядок магнитуды лучшую производительность при пиковых нагрузках. Garbage-free режим важен для систем с жесткими требованиями к паузам GC.

Оба фреймворка поддерживают структурированное логирование в JSON, интеграцию с MDC, маркеры, фильтрацию и ротацию. Различия в производительности заметны только при экстремальных нагрузках — для большинства enterprise-приложений оба варианта адекватны.

#Java #для_новичков #beginner #logging #Log4j2 #Logback #SLF4J
👍4
Раздел 9. Исключения, логирование, отладка

Глава 2. Логирование (Logging)

Подключение SLF4J + Logback в проект (Maven/Gradle). Базовый пример получения логгера


Для использования SLF4J с Logback требуется две основные зависимости: slf4j-api — фасадный интерфейс, и logback-classic — реализация, включающая нативный binding для SLF4J. Важно понимать, что logback-classic транзитивно подтягивает logback-core и slf4j-api, поэтому явное объявление slf4j-api не обязательно, но рекомендуется для явности контракта.
<dependencies>
<!-- Фасадный API, обязателен для компиляции -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>

<!-- Реализация Logback с нативным SLF4J binding -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
<scope>runtime</scope>
</dependency>
</dependencies>


Указание scope как runtime для logback-classic корректно, потому что код приложения напрямую не ссылается на классы Logback — только на SLF4J API. Компилятору достаточно интерфейсов, а реализация требуется только при выполнении. Однако на практике часто оставляют compile scope для простоты, особенно если конфигурация Logback загружается программно.
dependencies {
implementation 'org.slf4j:slf4j-api:2.0.9'
runtimeOnly 'ch.qos.logback:logback-classic:1.4.11'
}


В Gradle Kotlin DSL:
dependencies {
implementation("org.slf4j:slf4j-api:2.0.9")
runtimeOnly("ch.qos.logback:logback-classic:1.4.11")
}



Правило единственного binding

Критически важное правило classpath: должен присутствовать ровно один SLF4J binding. Наличие нескольких bindings приводит к предупреждению при старте и непредсказуемому выбору реализации. Типичная ошибка — транзитивное включение нескольких реализаций через зависимости.
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:.../logback-classic-1.4.11.jar]
SLF4J: Found binding in [jar:file:.../slf4j-simple-2.0.9.jar]
SLF4J: See https://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]


Для разрешения конфликтов используется исключение транзитивных зависимостей:
<!-- Maven: исключение конфликтующего binding -->
<dependency>
<groupId>com.some.library</groupId>
<artifactId>library-with-log4j</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>

// Gradle: исключение конфликтующего binding
implementation('com.some.library:library-with-log4j:1.0') {
exclude group: 'org.slf4j', module: 'slf4j-log4j12'
}



#Java #для_новичков #beginner #logging #Log4j2 #Logback #SLF4J
👍4
Базовый пример получения логгера

Конвенция получения логгера в SLF4J строго определена:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PaymentService {
// Конвенция: private static final, имя logger или LOG
private static final Logger logger = LoggerFactory.getLogger(PaymentService.class);

public void processPayment(String paymentId) {
logger.debug("Starting payment processing for id={}", paymentId);

try {
executePayment(paymentId);
logger.info("Payment {} processed successfully", paymentId);
} catch (PaymentException e) {
logger.error("Payment {} failed: {}", paymentId, e.getMessage(), e);
}
}
}

LoggerFactory.getLogger(Class<?>) создает именованный логгер с полным именем класса (com.example.service.PaymentService). Это имя формирует иерархию в конфигурации, позволяя управлять уровнем детализации на уровне пакета или класса. Альтернативный вариант — LoggerFactory.getLogger(String name) для произвольного имени, используемый при динамическом создании логгеров.

Ключевые конвенции именования
:
Поле объявляется private static final для единственного экземпляра на класс
Имя поля — logger (camelCase) или LOG (UPPER_CASE) в зависимости от code style команды
В Lombok доступна аннотация @Slf4j, генерирующая поле автоматически
Spring Boot: автоматическая конфигурация
Spring Boot радикально упрощает подключение логирования. Стартер spring-boot-starter-logging транзитивно включает logback-classic, slf4j-api и необходимые bridges. Это означает, что в типичном Spring Boot проекте никаких дополнительных зависимостей для логирования подключать не требуется.


Где находятся зависимости

Стартер spring-boot-starter-web или spring-boot-starter уже включает spring-boot-starter-logging, который разрешается в следующий набор:
org.springframework.boot:spring-boot-starter-logging
ch.qos.logback:logback-classic
org.apache.logging.log4j:log4j-to-slf4j
org.slf4j:jul-to-slf4j

log4j-to-slf4j и jul-to-slf4j — bridges, перенаправляющие вызовы из Log4j API и java.util.logging в SLF4J. Это обеспечивает единый канал логирования даже если транзитивные зависимости используют разные API.


Maven для Spring Boot
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>

<dependencies>
<!-- Транзитивно включает logback-classic и slf4j-api -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Явное подключение не требуется, но допустимо для управления версией -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>


Gradle для Spring Boot
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4'
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
// Никаких дополнительных зависимостей для логирования
}



Проверка подключенных зависимостей

Для проверки фактического дерева зависимостей используются команды:
# Maven
mvn dependency:tree | grep -E "(slf4j|logback|log4j)"

# Gradle
gradle dependencies --configuration runtimeClasspath | grep -E "(slf4j|logback|log4j)"


Ожидаемый вывод для Spring Boot:
+--- org.springframework.boot:spring-boot-starter-logging -> 3.2.0
| +--- ch.qos.logback:logback-classic:1.4.11
| | +--- ch.qos.logback:logback-core:1.4.11
| | \--- org.slf4j:slf4j-api:2.0.9
| +--- org.apache.logging.log4j:log4j-to-slf4j:2.21.1
| \--- org.slf4j:jul-to-slf4j:2.0.9



#Java #для_новичков #beginner #logging #Log4j2 #Logback #SLF4J
👍5
Исключение Logback в пользу Log4j2

Spring Boot по умолчанию использует Logback, но позволяет переключиться на Log4j2. Это требует исключения spring-boot-starter-logging и подключения spring-boot-starter-log4j2:
<!-- Maven: переход на Log4j2 -->
<dependencies>
<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>
</dependencies>


// Gradle: переход на Log4j2
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web') {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
}
implementation 'org.springframework.boot:spring-boot-starter-log4j2'
}



Конфигурация в Spring Boot проектах


Spring Boot автоматически загружает конфигурацию Logback из файлов в порядке приоритета:

logback-spring.xml — предпочтительный вариант, поддерживает расширения Spring (профили, свойства окружения)
logback.xml — стандартный файл конфигурации Logback, загружается напрямую без Spring-расширений
logback-test.xml — для тестового classpath, имеет приоритет над production-конфигурацией в тестах
Файл размещается в src/main/resources/ для production или src/test/resources/ для тестовой конфигурации.


Интеграция с application.properties

Spring Boot позволяет управлять уровнями логирования через application.properties без XML-конфигурации:
# Корневой уровень
logging.level.root=INFO

# Уровень для пакета
logging.level.com.example.service=DEBUG

# Уровень для конкретного класса
logging.level.com.example.service.PaymentService=TRACE

# Формат вывода
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n

# Файл логов
logging.file.name=application.log
logging.file.path=/var/log/myapp

Эти свойства переопределяют или дополняют XML-конфигурацию. При наличии logback-spring.xml свойства application.properties применяются через механизм Spring Boot, который инжектирует значения в Logback configuration.

Пример logback-spring.xml с профилями
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<springProfile name="dev">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>

<springProfile name="prod">
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/app/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/var/log/app/application.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE"/>
</root>
</springProfile>
</configuration>

Элемент <springProfile> доступен только в logback-spring.xml, не в стандартном logback.xml. Он активирует конфигурацию на основе active Spring profiles, что позволяет разделять настройки для development, staging и production без дублирования файлов.


#Java #для_новичков #beginner #logging #Log4j2 #Logback #SLF4J
👍4
Раздел 9. Исключения, логирование, отладка

Глава 2. Логирование (Logging)

Уровни логирования: TRACE, DEBUG, INFO, WARN, ERROR. Когда что использовать. Иерархия и фильтрация

Логирование без уровней — это поток неразличимых сообщений, где критическая ошибка теряется среди рутинных уведомлений о старте компонента. Уровни логирования — это система классификации сообщений по степени серьезности и детализации, которая позволяет разделить диагностический вывод на категории и фильтровать их в зависимости от окружения и задачи.

SLF4J определяет пять стандартных уровней, упорядоченных по возрастанию серьезности: TRACE, DEBUG, INFO, WARN, ERROR. Каждый уровень включает в себя и все последующие. Если логгер настроен на уровень INFO, он пропускает сообщения INFO, WARN и ERROR, но подавляет TRACE и DEBUG. Это свойство называется иерархией уровней и является фундаментальным механизмом управления объемом логов.


Иерархия уровней SLF4J

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

Logback расширяет эту шкалу двумя техническими уровнями: ALL и OFF. ALL пропускает все сообщения независимо от уровня, OFF подавляет все сообщения полностью. Эти уровни не используются в коде приложения, а применяются в конфигурации для полного включения или отключения логирования отдельных компонентов.


TRACE — трассировка выполнения

TRACE — самый низкий и самый детальный уровень. Он предназначен для трассировки потока выполнения на уровне отдельных шагов алгоритма, входа и выхода из методов, итераций циклов и ветвлений условий. SLF4J исторически не рекомендовал использование TRACE, добавив его только под давлением сообщества, поскольку существуют альтернативы или потому что сообщения TRACE часто избыточны. Тем не менее, уровень остается полезным для локальной отладки сложных рекурсивных или конечно-автоматных алгоритмов.

TRACE никогда не включается в production. Объем данных, генерируемый на этом уровне, на порядки превышает возможности дисковой подсистемы и систем агрегации. Включение TRACE в высоконагруженном сервисе приводит к деградации производительности из-за частых системных вызовов записи и переполнению буферов асинхронных appenders.
public void quickSort(int[] array, int left, int right) {
logger.trace("Entering quickSort(left={}, right={})", left, right);

if (left < right) {
int pivot = partition(array, left, right);
logger.trace("Pivot index={}, array state={}", pivot, Arrays.toString(array));

quickSort(array, left, pivot - 1);
quickSort(array, pivot + 1, right);
}

logger.trace("Exiting quickSort(left={}, right={})", left, right);
}

В этом примере TRACE фиксирует каждый рекурсивный вызов и состояние массива. При отладке алгоритма сортировки это позволяет восстановить полную историю преобразований. В production эти строки бесполезны и подавляются конфигурацией.


#Java #для_новичков #beginner #logging #Log4j2 #Logback #SLF4J #INFO #DEBUG #WARN #ERROR #TRACE
👍5
DEBUG — диагностика

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

Ключевое различие между TRACE и DEBUG: DEBUG-логирование должно быть пригодным для включения в production на ограниченное время при расследовании инцидента. TRACE-логирование настолько детально, что его включение в production технически невозможно без деградации системы. DEBUG — это баланс между информативностью и производительностью.
public User authenticate(String username, String passwordHash) {
logger.debug("Authentication attempt for user={}", username);

User user = userRepository.findByUsername(username);
logger.debug("User lookup result: found={}, status={}",
user != null, user != null ? user.getStatus() : "N/A");

if (user == null || !user.getPasswordHash().equals(passwordHash)) {
logger.debug("Authentication failed for user={}: invalid credentials", username);
return null;
}

if (user.getStatus() == UserStatus.LOCKED) {
logger.debug("Authentication failed for user={}: account locked", username);
return null;
}

logger.debug("Authentication successful for user={}, roles={}",
username, user.getRoles());
return user;
}

Этот метод логирует ключевые точки принятия решений без избыточной детализации. При расследовании проблемы аутентификации разработчик видит, на каком этапе произошел отказ: пользователь не найден, неверный пароль или заблокированный аккаунт.


INFO — рутинные события

INFO — стандартный уровень для production. Он фиксирует значимые события жизненного цикла приложения: запуск и остановка сервиса, успешное завершение пакетной операции, вход и выход пользователя, изменение состояния сущности. INFO-сообщения формируют операционную хронологию системы — просмотр логов уровня INFO и выше дает обзор основных событий без погружения в детали.

INFO не предназначен для ежесекундных событий. Если метод вызывается тысячи раз в минуту, его логирование на уровне INFO создаст неуправляемый поток данных. INFO должен использоваться для событий, которые происходят относительно редко и представляют интерес для операционной команды.
@Service
public class OrderProcessingService {

@EventListener
public void onApplicationReady(ApplicationReadyEvent event) {
logger.info("Order processing service started, version={}, profile={}",
buildProperties.getVersion(),
Arrays.toString(environment.getActiveProfiles()));
}

public Order processOrder(OrderRequest request) {
logger.info("Processing orderId={}, customerId={}, amount={}",
request.getOrderId(), request.getCustomerId(), request.getAmount());

Order order = executeBusinessLogic(request);

logger.info("Order {} processed successfully, status={}, durationMs={}",
order.getId(), order.getStatus(), order.getProcessingDuration());
return order;
}
}

В этом примере INFO фиксирует старт сервиса и успешное завершение бизнес-операции. Оператор, просматривая логи, видит, что заказ обработан, но не видит деталей валидации каждого поля — это остается на уровне DEBUG.

#Java #для_новичков #beginner #logging #Log4j2 #Logback #SLF4J #INFO #DEBUG #WARN #ERROR #TRACE
👍4
WARN — предупреждения

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

Классические сценарии для WARN: использование устаревшего API, получение неожиданного формата данных от внешней системы, превышение порога времени выполнения операции, отказ вторичного сервиса при наличии fallback, повторная попытка операции после временного сбоя.

public void sendNotification(User user, Notification notification) {
try {
notificationGateway.send(user.getEmail(), notification);
logger.info("Notification sent to user={}", user.getId());
} catch (GatewayTimeoutException e) {
logger.warn("Notification gateway timeout for user={}, will retry asynchronously",
user.getId());
enqueueForRetry(user, notification);
}
}

Здесь таймаут шлюза уведомлений — это не ошибка, потому что операция будет повторена асинхронно. Однако это нештатная ситуация, которую операционная команда должна видеть для мониторинга здоровья внешней зависимости.


ERROR — ошибки

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

Важное правило: не каждое исключение должно логироваться на уровне ERROR. Если метод обрабатывает исключение и предпринимает корректирующие действия (retry, fallback, пропуск элемента), уровень должен быть WARN. ERROR используется, когда операция завершилась неудачно и нет механизма восстановления, или когда исключение пробрасывается на верхний уровень для аварийной обработки.
public void chargePayment(Payment payment) {
try {
paymentGateway.charge(payment);
logger.info("Payment {} charged successfully", payment.getId());
} catch (PaymentGatewayException e) {
if (e.isRetryable()) {
logger.warn("Payment {} failed with retryable error, scheduling retry",
payment.getId());
scheduleRetry(payment);
} else {
logger.error("Payment {} failed with non-retryable error: {}",
payment.getId(), e.getMessage(), e);
markAsFailed(payment);
}
}
}

В этом примере retryable-ошибка логируется как WARN, потому что система восстанавливается автоматически. Non-retryable ошибка — ERROR, потому что платеж окончательно не выполнен и требует ручного разбора.


#Java #для_новичков #beginner #logging #Log4j2 #Logback #SLF4J #INFO #DEBUG #WARN #ERROR #TRACE
👍5
Фильтрация и наследование уровней

Логгеры в SLF4J и Logback организованы в иерархическую структуру на основе их имен. Логгер com.example.service.OrderService наследует уровень от com.example.service, который наследует от com.example, и так до корневого логгера. Это свойство называется наследованием уровней.
Когда логгер не имеет явно назначенного уровня, он наследует уровень ближайшего предка, у которого уровень определен. Корневой логгер всегда имеет уровень и служит последней инстанцией. Это позволяет управлять детализацией на уровне пакета или класса без перекомпиляции.
// Псевдокод конфигурации Logback
Logger root = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
root.setLevel(Level.INFO);

Logger servicePackage = LoggerFactory.getLogger("com.example.service");
servicePackage.setLevel(Level.DEBUG);

Logger orderService = LoggerFactory.getLogger("com.example.service.OrderService");
// orderService не имеет явного уровня, наследует DEBUG от com.example.service

Свойство additivity определяет, будет ли сообщение, обработанное логгером, передаваться родительским логгерам. По умолчанию additivity равно true, что означает, что сообщение проходит через всю цепочку appenders от конкретного логгера до корневого.

Если additivity установлено в false, сообщение обрабатывается только appenders данного логгера и не поднимается выше.
<!-- Logback: отключение additivity для изоляции вывода -->
<logger name="com.example.audit" level="INFO" additivity="false">
<appender-ref ref="AUDIT_FILE"/>
</logger>


В этом примере логгер com.example.audit направляет сообщения только в файл аудита, не дублируя их в корневой appender. Это критично для разделения потоков: бизнес-аудит не должен попадать в общий лог ошибок.
Практические рекомендации по выбору уровня

Выбор уровня — это не субъективное предпочтение, а архитектурное решение, влияющее на наблюдаемость системы.

Несколько правил, выработанных практикой:
Правило единственного уровня для исключения. Одно и то же событие не должно логироваться на разных уровнях в разных местах. Если метод перехватил исключение и залогировал его как ERROR, вызывающий код не должен логировать его повторно. Дублирование создает шум и затрудняет анализ.
Правило восстановимости. Если операция может быть восстановлена автоматически — WARN. Если восстановление невозможно — ERROR. Если это ожидаемое поведение — INFO или ниже.
Правило частоты. События, происходящие чаще нескольких раз в минуту на экземпляр, не должны логироваться на INFO. Используйте DEBUG или агрегируйте метриками.
Правило контекста. Сообщение должно содержать достаточно контекста для понимания проблемы без чтения кода. Вместо logger.error("Failed") используйте logger.error("Payment processing failed for orderId={}, gateway={}", orderId, gatewayId).
Правило guard-методов. Для дорогостоящих DEBUG и TRACE сообщений используйте logger.isDebugEnabled() или параметризованные сообщения SLF4J для исключения накладных расходов при отключенном уровне.
// Правильно: ленивое вычисление через параметризацию
logger.debug("Processing batch of {} items, estimated memory={}MB",
batchSize, estimatedMemory);

// Избыточно: guard-метод не нужен для простых параметров
if (logger.isDebugEnabled()) {
logger.debug("Simple message");
}

// Оправдано: guard для дорогостоящих операций
if (logger.isDebugEnabled()) {
logger.debug("Complex state: {}", serializeFullObjectGraph(state));
}



Согласованность уровней в распределенных системах

В микросервисной архитектуре согласованность уровней между сервисами критична для корреляции событий. Если один сервис логирует отказ внешнего вызова как ERROR, а вызываемый сервис фиксирует тот же отказ как WARN, оператор теряет единую картину. Рекомендуется выработать командный стандарт: ERROR — это сбой, который требует немедленного вмешательства и не может быть восстановлен автоматически в рамках текущего запроса. WARN — сбой с автоматическим восстановлением или деградация без потери функциональности.


#Java #для_новичков #beginner #logging #Log4j2 #Logback #SLF4J #INFO #DEBUG #WARN #ERROR #TRACE
👍5
Раздел 9. Исключения, логирование, отладка

Глава 2. Логирование (Logging)

Параметризованные сообщения SLF4J: почему это лучше конкатенации строк

Конкатенация строк через оператор + внутри вызова логгера кажется безобидной, но порождает скрытые накладные расходы. Компилятор Java транслирует выражение "User " + userId + " not found" в создание объекта StringBuilder, последовательный вызов append для каждого операнда и финальный toString. Если такая операция выполняется внутри цикла или при высокой нагрузке, каждый вызов порождает несколько объектов в heap, увеличивая давление на garbage collector.

Критический недостаток проявляется при отключенном уровне логирования.

Предположим, корневой уровень установлен на INFO, а в коде присутствует:
logger.debug("User " + userId + " not found in database " + dbName + " after " + duration + "ms");


Несмотря на то что сообщение не будет выведено, конкатенация выполняется безусловно. JVM создает StringBuilder, четыре промежуточных строки и финальный результат — все это немедленно становится мусором. В hot path с тысячами вызовов в секунду такие бесполезные аллокации приводят к учащению minor GC и деградации latency.


Механизм параметризованных сообщений

SLF4J предоставляет перегруженные методы с шаблоном и переменным числом аргументов:
logger.debug("User {} not found in database {} after {}ms", userId, dbName, duration);


Здесь {} — placeholder, который заменяется на строковое представление аргумента. Ключевое отличие: подстановка выполняется внутри реализации логгера, и только если уровень DEBUG активен. Если уровень отключен, метод завершается после проверки флага, не трогая аргументы и не создавая результирующей строки.

Внутри Logback это реализовано через MessageFormatter. Класс FormattingTuple выполняет ленивую сборку сообщения, обходя аргументы массивом только при фактической необходимости записи. Для одного и двух аргументов SLF4J предоставляет оптимизированные перегрузки, избегая создания массива объектов:
// Оптимизированные перегрузки для 1-2 аргументов без varargs
public void debug(String format, Object arg);
public void debug(String format, Object arg1, Object arg2);
public void debug(String format, Object... arguments); // 3+



Производительность: измеримая разница

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

Рассмотрим микробенчмарк-логику. Конкатенация при отключенном DEBUG:
// Всегда выполняется: StringBuilder + 4 append + toString
logger.debug("User " + userId + " not found");


Параметризация при отключенном DEBUG:
// SLF4J: проверка isDebugEnabled() внутри, немедленный return
logger.debug("User {} not found", userId);


Второй вариант на порядки быстрее и не порождает мусора. Это особенно важно для логирования в циклах обработки коллекций или при логировании состояния каждого элемента в batch-операции.


#Java #для_новичков #beginner #logging #Log4j2 #Logback #SLF4J
👍5
Guard-методы и параметризация

До появления широкого использования SLF4J распространенным паттерном был явной guard:
if (logger.isDebugEnabled()) {
logger.debug("User " + userId + " not found");
}

Этот подход решает проблему конкатенации, но загромождает код и нарушает единообразие. Параметризованные сообщения устраняют необходимость в guard для простых случаев, так как внутренняя проверка уровня выполняется автоматически до форматирования.

Guard остается оправдан только при дорогостоящем вычислении аргумента:
// Аргумент требует сериализации — guard необходим
if (logger.isDebugEnabled()) {
logger.debug("Full state: {}", objectMapper.writeValueAsString(complexObject));
}


Здесь даже параметризация не спасет, потому что writeValueAsString выполняется до вызова логгера как частичное вычисление аргумента. SLF4J не может отложить выполнение произвольного выражения.


Исключения и параметризация

Особый случай — логирование исключений. SLF4J распознает последний аргумент типа Throwable и обрабатывает его отдельно от параметров:
// Правильно: throwable логируется со стектрейсом
logger.error("Payment {} failed for user {}", paymentId, userId, exception);

// Неправильно: исключение превращается в toString(), стектрейс потерян
logger.error("Payment " + paymentId + " failed: " + exception.getMessage());

В параметризованном вызове последний Throwable не подставляется в шаблон, а передается в appender для вывода полного стектрейса. При конкатенации разработчик часто ограничивается exception.getMessage(), теряя критически важную информацию о месте возникновения и цепочке вызовов.


#Java #для_новичков #beginner #logging #Log4j2 #Logback #SLF4J
👍5