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

Как организуешь логирование тела запроса при приеме его контроллером

2.0 Middle🔥 141 комментариев
#REST API и микросервисы#Spring Framework

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

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

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

# Логирование тела запроса в контроллере

Это важный аспект отладки и мониторинга API приложений. Рассмотрю различные подходы.

1. Встроенная функциональность Spring Boot (самый простой способ)

Включить логирование в application.yaml:

logging:
  level:
    org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor: DEBUG
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter: DEBUG
    org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver: DEBUG
spring:
  mvc:
    log-request-details: true # Логирует параметры и заголовки

В консоли будет видно:

2024-03-22 10:15:30.123 DEBUG [http-nio-8080-exec-1] 
  o.s.w.s.m.m.a.RequestMappingHandlerMapping : 
  Mapped to com.example.UserController#createUser(CreateUserRequest)

2. Пользовательский Interceptor (рекомендуется)

Самый контролируемый подход:

@Component
public class RequestResponseLoggingInterceptor implements HandlerInterceptor {
    
    private static final Logger log = LoggerFactory.getLogger(RequestResponseLoggingInterceptor.class);
    private static final int MAX_PAYLOAD_LENGTH = 10000; // Лимит размера логируемого тела
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
                             Object handler) throws Exception {
        // Обёрнуть request чтобы прочитать тело несколько раз
        if (isLoggable(request)) {
            HttpServletRequest wrappedRequest = new ContentCachingRequestWrapper(request);
            String body = getRequestBody(wrappedRequest);
            
            log.info("""
                [REQUEST] {} {} 
                Headers: {}
                Body: {}
                """,
                request.getMethod(),
                request.getRequestURI(),
                getHeadersMap(request),
                truncatePayload(body)
            );
        }
        
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                                Object handler, Exception ex) throws Exception {
        if (isLoggable(request)) {
            log.info("""
                [RESPONSE] {} {} -> {}
                """,
                request.getMethod(),
                request.getRequestURI(),
                response.getStatus()
            );
        }
    }
    
    private boolean isLoggable(HttpServletRequest request) {
        // Не логировать статические ресурсы и health checks
        String uri = request.getRequestURI();
        return !uri.startsWith("/actuator") && 
               !uri.startsWith("/static") &&
               !uri.endsWith(".css") &&
               !uri.endsWith(".js");
    }
    
    private String getRequestBody(HttpServletRequest request) {
        try {
            ContentCachingRequestWrapper wrapper = 
                (ContentCachingRequestWrapper) request;
            byte[] buf = wrapper.getContentAsByteArray();
            if (buf.length > 0) {
                return new String(buf, StandardCharsets.UTF_8);
            }
        } catch (Exception e) {
            log.warn("Failed to get request body", e);
        }
        return "";
    }
    
    private Map<String, String> getHeadersMap(HttpServletRequest request) {
        Map<String, String> headers = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
            if (isSafeHeader(name)) { // Не логировать sensitive headers
                headers.put(name, request.getHeader(name));
            }
        }
        return headers;
    }
    
    private boolean isSafeHeader(String headerName) {
        String lower = headerName.toLowerCase();
        return !lower.contains("authorization") &&
               !lower.contains("password") &&
               !lower.contains("token") &&
               !lower.contains("secret");
    }
    
    private String truncatePayload(String payload) {
        if (payload.length() > MAX_PAYLOAD_LENGTH) {
            return payload.substring(0, MAX_PAYLOAD_LENGTH) + "... [truncated]";
        }
        return payload;
    }
}

// Зарегистрировать interceptor
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private RequestResponseLoggingInterceptor loggingInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loggingInterceptor);
    }
}

3. HttpFilter подход

@Component
public class RequestBodyFilter extends OncePerRequestFilter {
    
    private static final Logger log = LoggerFactory.getLogger(RequestBodyFilter.class);
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        
        // Обернуть для чтения тела
        ContentCachingRequestWrapper wrappedRequest = 
            new ContentCachingRequestWrapper(request);
        
        // Пропустить через фильтр цепь
        filterChain.doFilter(wrappedRequest, response);
        
        // Логировать после обработки
        String body = new String(
            wrappedRequest.getContentAsByteArray(),
            StandardCharsets.UTF_8
        );
        
        log.info("Request: {} {} - Body: {}",
            request.getMethod(),
            request.getRequestURI(),
            body.substring(0, Math.min(500, body.length()))
        );
    }
}

4. @Aspect для AOP логирования

Более элегантный подход:

@Aspect
@Component
public class RequestLoggingAspect {
    
    private static final Logger log = LoggerFactory.getLogger(RequestLoggingAspect.class);
    
    @Pointcut("@annotation(com.example.annotation.LogRequest)")
    public void logRequestMethods() {}
    
    @Before("logRequestMethods()")
    public void logBefore(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Object[] args = joinPoint.getArgs();
        String[] paramNames = signature.getParameterNames();
        
        Map<String, Object> params = new HashMap<>();
        for (int i = 0; i < paramNames.length; i++) {
            params.put(paramNames[i], sanitizeObject(args[i]));
        }
        
        log.info("[{}] Request parameters: {}",
            signature.getName(),
            params
        );
    }
    
    @After("logRequestMethods()")
    public void logAfter(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        log.info("[{}] Method execution completed", signature.getName());
    }
    
    private Object sanitizeObject(Object obj) {
        if (obj instanceof String) {
            String str = (String) obj;
            if (str.length() > 100) {
                return str.substring(0, 100) + "...";
            }
            return str;
        }
        // Обработать другие типы
        return obj;
    }
}

// Аннотация
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogRequest {
    String value() default "";
}

// Использование
@RestController
public class UserController {
    
    @PostMapping("/api/v1/users")
    @LogRequest("Create new user")
    public ResponseEntity<UserResponse> createUser(@RequestBody CreateUserRequest request) {
        // ...
    }
}

5. Логирование в контроллере (явное)

@RestController
@RequestMapping("/api/v1/users")
public class UserController {
    
    private static final Logger log = LoggerFactory.getLogger(UserController.class);
    
    @PostMapping
    public ResponseEntity<UserResponse> createUser(
            @RequestBody @Valid CreateUserRequest request,
            HttpServletRequest httpRequest) {
        
        // Логировать детали запроса
        log.info("""
            Creating new user
            - Name: {}
            - Email: {}
            - Remote IP: {}
            - User-Agent: {}
            """,
            request.getName(),
            request.getEmail(),
            httpRequest.getRemoteAddr(),
            httpRequest.getHeader("User-Agent")
        );
        
        try {
            User user = userService.createUser(request);
            log.info("User created successfully: {}", user.getId());
            return ResponseEntity.status(HttpStatus.CREATED)
                .body(UserResponse.from(user));
        } catch (Exception e) {
            log.error("Failed to create user", e);
            throw e;
        }
    }
}

6. Использование ContentCachingRequestWrapper и ResponseWrapper

@Component
public class RequestResponseLoggingFilter extends OncePerRequestFilter {
    
    private static final Logger log = LoggerFactory.getLogger(RequestResponseLoggingFilter.class);
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        
        ContentCachingRequestWrapper requestWrapper = 
            new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper responseWrapper = 
            new ContentCachingResponseWrapper(response);
        
        long startTime = System.currentTimeMillis();
        
        try {
            filterChain.doFilter(requestWrapper, responseWrapper);
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            
            String requestBody = new String(
                requestWrapper.getContentAsByteArray(),
                StandardCharsets.UTF_8
            );
            
            String responseBody = new String(
                responseWrapper.getContentAsByteArray(),
                StandardCharsets.UTF_8
            );
            
            log.info("""
                HTTP Request/Response Log
                - Method: {}
                - URI: {}
                - Status: {}
                - Duration: {} ms
                - Request Body: {}
                - Response Body: {}
                """,
                request.getMethod(),
                request.getRequestURI(),
                response.getStatus(),
                duration,
                truncate(requestBody, 500),
                truncate(responseBody, 500)
            );
            
            responseWrapper.copyToResponse();
        }
    }
    
    private String truncate(String str, int maxLength) {
        if (str == null || str.isEmpty()) return "<empty>";
        return str.length() > maxLength ? 
            str.substring(0, maxLength) + "..." : str;
    }
}

7. Маскирование чувствительных данных

@Component
public class SensitiveDataMasker {
    
    public String maskSensitiveData(String json) {
        return json
            .replaceAll("(\"password\"\s*:\s*\")(.*?)(\")", "$1***$3")
            .replaceAll("(\"creditCard\"\s*:\s*\")(\d{12})(\d{4})(\")", "$1****$3$4")
            .replaceAll("(\"ssn\"\s*:\s*\")(\d{3})-(\d{2})-(\d{4})(\")", "$1***-**-$4$5")
            .replaceAll("(\"token\"\s*:\s*\")(.*?)(\")", "$1[REDACTED]$3");
    }
}

8. Configuration для логирования

logback-spring.xml:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}"/>
    
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>
    
    <logger name="com.example" level="DEBUG"/>
    <logger name="org.springframework.web" level="INFO"/>
    
    <root level="INFO">
        <appender-ref ref="FILE"/>
    </root>
</configuration>

Best Practices

  1. ✅ Используй Interceptor или Filter для централизованного логирования
  2. ✅ Логируй метод, URI, и status код ВСЕГДА
  3. ✅ Ограничивай размер логируемого тела (truncate)
  4. ✅ Маскируй чувствительные данные (пароли, токены, номера карт)
  5. ✅ Не логируй authorization заголовки
  6. ✅ Включай timestamp и duration для performance анализа
  7. ✅ Используй разные уровни (DEBUG для деталей, INFO для основного)
  8. ✅ Логируй ошибки с full stack trace
  9. ✅ Настройки логирования должны быть конфигурируемы (не hardcoded)
  10. ✅ Используй structured logging (JSON формат) для production

Правильное логирование помогает быстро диагностировать проблемы и отслеживать аномалии в production.

Как организуешь логирование тела запроса при приеме его контроллером | PrepBro