Комментарии (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");
}
}
Лучшие практики
- Используй SLF4J — не Log4j или commons-logging напрямую
- Проверяй level перед логированием сложных выражений:
if (log.isDebugEnabled()) {
log.debug("Expensive operation: {}", expensiveToString());
}
- Не логируй чувствительные данные:
// ❌ Не так
log.info("User password: {}", password);
// ✅ Так
log.info("User authentication attempt for: {}", username);
- Используй параметрированные сообщения:
// ❌ Не так
log.info("User " + user.getName() + " created");
// ✅ Так
log.info("User {} created", user.getName());
- Разные уровни для разных ситуаций:
- INFO: важные события (пользователь вошел, заказ создан)
- DEBUG: детали для отладки
- WARN: потенциальные проблемы
- ERROR: ошибки с полным стеком
- Настраивай ротацию логов — не занимай весь диск
- Используй MDC — для контекста запроса
- Мониторь логи — отправляй в ELK, CloudWatch или Splunk
Правильное логирование критично для production среды!