← Назад к вопросам

Как подключал логирование к проекту

2.2 Middle🔥 111 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Подключение логирования к проекту

Логирование — это критичный инструмент для мониторинга и отладки приложения в production. Расскажу о системном подходе к логированию, который я применял на проектах.

1. Выбор фреймворка логирования

В Java экосистеме есть несколько вариантов:

<!-- pom.xml -->

<!-- SLF4J + Logback (рекомендуется) -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.4.11</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.9</version>
</dependency>

<!-- Исключаем старый логирование из spring-boot-starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- Spring Boot starter (автоматом подключает Logback) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

2. Конфигурация Logback (logback-spring.xml)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- Импорт Spring Boot конфигурации -->
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    
    <!-- Переменные -->
    <property name="LOG_DIR" value="./logs"/>
    <property name="LOG_FILE" value="application"/>
    
    <!-- Appender: вывод в консоль -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>
    
    <!-- Appender: вывод в файл -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/${LOG_FILE}.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf8</charset>
        </encoder>
        <!-- Ротация логов -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_DIR}/${LOG_FILE}-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
    </appender>
    
    <!-- Appender для ошибок -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/error.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n%rEx{full}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_DIR}/error-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>
    
    <!-- Профиль разработки -->
    <springProfile name="dev">
        <root level="DEBUG">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE"/>
            <appender-ref ref="ERROR_FILE"/>
        </root>
        <logger name="org.springframework.web" level="DEBUG"/>
        <logger name="org.hibernate.SQL" level="DEBUG"/>
        <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>
    </springProfile>
    
    <!-- Профиль продакшена -->
    <springProfile name="prod">
        <root level="INFO">
            <appender-ref ref="FILE"/>
            <appender-ref ref="ERROR_FILE"/>
        </root>
        <logger name="org.springframework" level="WARN"/>
        <logger name="org.hibernate" level="WARN"/>
    </springProfile>
    
    <!-- Профиль тестирования -->
    <springProfile name="test">
        <root level="WARN">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>
</configuration>

3. Конфигурация через application.yml

# application.yml
spring:
  profiles:
    active: dev

logging:
  level:
    root: INFO
    com.mycompany: DEBUG
    org.springframework.web: WARN
    org.springframework.security: DEBUG
    org.hibernate: WARN
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} - %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: logs/application.log
    max-size: 10MB
    max-history: 30

# application-prod.yml
logging:
  level:
    root: WARN
    com.mycompany: INFO

4. Использование логирования в коде

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.extern.slf4j.Slf4j;

// Способ 1: Явное инициализация
public class UserService {
    private static final Logger log = LoggerFactory.getLogger(UserService.class);
    
    public void createUser(User user) {
        log.info("Creating user: {} with email: {}", user.getName(), user.getEmail());
        
        try {
            userRepository.save(user);
            log.debug("User created with id: {}", user.getId());
        } catch (Exception e) {
            log.error("Error creating user", e);
            throw new RuntimeException("Cannot create user", e);
        }
    }
}

// Способ 2: Использование Lombok (рекомендуется)
@Slf4j
@Service
public class UserService {
    public void createUser(User user) {
        log.info("Creating user: {} with email: {}", user.getName(), user.getEmail());
        
        try {
            userRepository.save(user);
            log.debug("User created with id: {}", user.getId());
        } catch (Exception e) {
            log.error("Error creating user", e);
            throw new RuntimeException("Cannot create user", e);
        }
    }
}

5. Уровни логирования

@Slf4j
public class LoggingExample {
    public void demonstrateLogging() {
        // TRACE (самый подробный, почти никогда не используется в prod)
        log.trace("Very detailed debug information");
        
        // DEBUG (для разработки, отключаем в prod)
        log.debug("Debug info: user_id={}, action={}", userId, action);
        
        // INFO (основная информация о ходе выполнения)
        log.info("User {} logged in successfully", username);
        
        // WARN (предупреждение о потенциальных проблемах)
        log.warn("Deprecated method used: {}", methodName);
        
        // ERROR (ошибка, приложение работает, но что-то не так)
        log.error("Failed to process request", exception);
        
        // FATAL (критичная ошибка, приложение может сломаться)
        // В SLF4J нет FATAL, используем ERROR вместо него
    }
}

6. Логирование в контроллерах

@Slf4j
@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;
    
    @PostMapping
    public ResponseEntity<UserDTO> createUser(@RequestBody CreateUserRequest request) {
        log.info("Received create user request for email: {}", request.getEmail());
        
        UserDTO user = userService.createUser(request);
        
        log.info("User created successfully with id: {}", user.getId());
        return ResponseEntity.ok(user);
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        log.debug("Fetching user with id: {}", id);
        
        UserDTO user = userService.getUser(id);
        
        log.debug("User found: {}", user.getId());
        return ResponseEntity.ok(user);
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception e) {
        log.error("Unexpected error occurred", e);
        return ResponseEntity.internalServerError()
            .body(new ErrorResponse("Internal server error"));
    }
}

7. Логирование в сервисах

@Slf4j
@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private PaymentService paymentService;
    
    public Order processOrder(Long orderId) {
        log.info("Processing order: {}", orderId);
        
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> {
                log.error("Order not found: {}", orderId);
                return new EntityNotFoundException("Order not found");
            });
        
        log.debug("Order found: status={}, amount={}", order.getStatus(), order.getTotal());
        
        try {
            paymentService.processPayment(order);
            order.setStatus(OrderStatus.PAID);
            log.info("Order {} payment processed successfully", orderId);
        } catch (PaymentException e) {
            order.setStatus(OrderStatus.FAILED);
            log.error("Payment failed for order {}: {}", orderId, e.getMessage(), e);
            throw e;
        }
        
        return orderRepository.save(order);
    }
}

8. Логирование в фильтрах и интерцепторах

@Slf4j
@Component
public class LoggingFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                  FilterChain filterChain) throws ServletException, IOException {
        long startTime = System.currentTimeMillis();
        String requestId = UUID.randomUUID().toString();
        MDC.put("requestId", requestId);
        
        log.info("Incoming request: {} {} from {}",
            request.getMethod(),
            request.getRequestURI(),
            request.getRemoteAddr());
        
        try {
            filterChain.doFilter(request, response);
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            log.info("Request completed: status={}, duration={}ms",
                response.getStatus(),
                duration);
            MDC.remove("requestId");
        }
    }
}

9. Структурированное логирование (MDC)

@Slf4j
@Component
public class RequestInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                           Object handler) throws Exception {
        String userId = extractUserId(request);
        String requestId = UUID.randomUUID().toString();
        
        // Mapped Diagnostic Context - добавляет контекст ко всем логам
        MDC.put("userId", userId);
        MDC.put("requestId", requestId);
        MDC.put("timestamp", LocalDateTime.now().toString());
        
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                               Object handler, Exception ex) throws Exception {
        MDC.clear();
    }
}

// Тогда в логах автоматически будет userId, requestId и timestamp
// 2024-01-15 10:30:45.123 [userId=123, requestId=abc-def] UserService - Creating user

10. Полная конфигурация на практике

@Slf4j
public class ApplicationStartup implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        log.info("\n====================================");
        log.info("Application started successfully");
        log.info("Spring profiles: {}",
            Arrays.toString(event.getApplicationContext().getEnvironment().getActiveProfiles()));
        log.info("====================================");
    }
}

@Slf4j
public class ApplicationShutdown {
    @PreDestroy
    public void onShutdown() {
        log.info("Application is shutting down");
    }
}

Лучшие практики

  1. Используй SLF4J — не Log4j или commons-logging напрямую
  2. Проверяй level перед логированием сложных выражений:
if (log.isDebugEnabled()) {
    log.debug("Expensive operation: {}", expensiveToString());
}
  1. Не логируй чувствительные данные:
// ❌ Не так
log.info("User password: {}", password);

// ✅ Так
log.info("User authentication attempt for: {}", username);
  1. Используй параметрированные сообщения:
// ❌ Не так
log.info("User " + user.getName() + " created");

// ✅ Так
log.info("User {} created", user.getName());
  1. Разные уровни для разных ситуаций:
  • INFO: важные события (пользователь вошел, заказ создан)
  • DEBUG: детали для отладки
  • WARN: потенциальные проблемы
  • ERROR: ошибки с полным стеком
  1. Настраивай ротацию логов — не занимай весь диск
  2. Используй MDC — для контекста запроса
  3. Мониторь логи — отправляй в ELK, CloudWatch или Splunk

Правильное логирование критично для production среды!