← Назад к вопросам
Как организуешь логирование тела запроса при приеме его контроллером
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
- ✅ Используй Interceptor или Filter для централизованного логирования
- ✅ Логируй метод, URI, и status код ВСЕГДА
- ✅ Ограничивай размер логируемого тела (truncate)
- ✅ Маскируй чувствительные данные (пароли, токены, номера карт)
- ✅ Не логируй authorization заголовки
- ✅ Включай timestamp и duration для performance анализа
- ✅ Используй разные уровни (DEBUG для деталей, INFO для основного)
- ✅ Логируй ошибки с full stack trace
- ✅ Настройки логирования должны быть конфигурируемы (не hardcoded)
- ✅ Используй structured logging (JSON формат) для production
Правильное логирование помогает быстро диагностировать проблемы и отслеживать аномалии в production.