How to Build a Robust Request Logging Aspect in Spring Boot
This article explains how to create a Spring Boot AOP request‑logging aspect that captures request parameters, execution time, results, and errors, while also adding trace‑ID support for easier debugging in high‑concurrency environments.
Introduction
During integration testing, mismatched request parameters often cause failures, so a request logging aspect is introduced to print input parameters and help trace issues.
Aspect Overview
Aspect‑Oriented Programming (AOP) separates cross‑cutting concerns such as logging from core business logic.
Centralized handling of a concern
Easy to add or remove
Low intrusion, improves readability and maintainability
Using Annotations
Pointcut
@Pointcut defines the join points, e.g. all methods in the controller package.
@Pointcut("execution(* your_package.controller..*(..))")
public void requestServer() {}Before Advice
Prints IP, URL, HTTP method, and class method before controller execution.
@Before("requestServer()")
public void doBefore(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
LOGGER.info("===============================Start========================");
LOGGER.info("IP : {}", request.getRemoteAddr());
LOGGER.info("URL : {}", request.getRequestURL().toString());
LOGGER.info("HTTP Method : {}", request.getMethod());
LOGGER.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName());
}Around Advice
Measures execution time, logs request parameters, result, and duration.
@Around("requestServer()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed();
LOGGER.info("Request Params : {}", getRequestParams(proceedingJoinPoint));
LOGGER.info("Result : {}", result);
LOGGER.info("Time Cost : {} ms", System.currentTimeMillis() - start);
return result;
}Helper method extracts parameter names and values, handling MultipartFile specially.
private Map<String, Object> getRequestParams(ProceedingJoinPoint proceedingJoinPoint) {
Map<String, Object> requestParams = new HashMap<>();
String[] paramNames = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterNames();
Object[] paramValues = proceedingJoinPoint.getArgs();
for (int i = 0; i < paramNames.length; i++) {
Object value = paramValues[i];
if (value instanceof MultipartFile) {
MultipartFile file = (MultipartFile) value;
value = file.getOriginalFilename();
}
requestParams.put(paramNames[i], value);
}
return requestParams;
}After Advice
Marks the end of logging.
@After("requestServer()")
public void doAfter(JoinPoint joinPoint) {
LOGGER.info("===============================End========================");
}High‑Concurrency Optimizations
To avoid interleaved log lines under heavy load, a RequestInfo DTO is created and serialized as a single JSON line.
@Data
public class RequestInfo {
private String ip;
private String url;
private String httpMethod;
private String classMethod;
private Object requestParams;
private Object result;
private Long timeCost;
}The around advice now populates RequestInfo and logs it.
@Around("requestServer()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long start = System.currentTimeMillis();
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attrs.getRequest();
Object result = proceedingJoinPoint.proceed();
RequestInfo info = new RequestInfo();
info.setIp(request.getRemoteAddr());
info.setUrl(request.getRequestURL().toString());
info.setHttpMethod(request.getMethod());
info.setClassMethod(String.format("%s.%s",
proceedingJoinPoint.getSignature().getDeclaringTypeName(),
proceedingJoinPoint.getSignature().getName()));
info.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));
info.setResult(result);
info.setTimeCost(System.currentTimeMillis() - start);
LOGGER.info("Request Info : {}", JSON.toJSONString(info));
return result;
}Exception Logging
An @AfterThrowing advice captures request details and the exception into a RequestErrorInfo DTO.
@Data
public class RequestErrorInfo {
private String ip;
private String url;
private String httpMethod;
private String classMethod;
private Object requestParams;
private RuntimeException exception;
} @AfterThrowing(pointcut = "requestServer()", throwing = "e")
public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attrs.getRequest();
RequestErrorInfo errorInfo = new RequestErrorInfo();
errorInfo.setIp(request.getRemoteAddr());
errorInfo.setUrl(request.getRequestURL().toString());
errorInfo.setHttpMethod(request.getMethod());
errorInfo.setClassMethod(String.format("%s.%s",
joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName()));
errorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint));
errorInfo.setException(e);
LOGGER.info("Error Request Info : {}", JSON.toJSONString(errorInfo));
}TraceId Integration
Adding a traceId to each request helps correlate logs across services. An interceptor generates a UUID, stores it in ThreadContext (or MDC), and removes it after completion. The logging pattern is updated to include %X{traceId}.
public class LogInterceptor implements HandlerInterceptor {
private static final String TRACE_ID = "traceId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String traceId = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
ThreadContext.put("traceId", traceId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
ThreadContext.remove(TRACE_ID);
}
}Log4j2 pattern example:
<property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>These techniques provide a low‑intrusion, comprehensive logging solution suitable for both normal and high‑concurrency scenarios.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
