Backend Development 12 min read

Implementing a Request Logging Aspect with Spring AOP

This article demonstrates how to create a Spring AOP request‑logging aspect that captures request details, parameters, results, execution time, and error information, and discusses enhancements for high‑concurrency environments by aggregating log data into a single JSON object.

Top Architect
Top Architect
Top Architect
Implementing a Request Logging Aspect with Spring AOP

In modern Java backend projects, logging request information is essential for debugging and tracing, especially during integration testing where mismatched request parameters often cause failures. This guide shows how to use Spring AOP to implement a non‑intrusive request‑logging aspect.

Aspect‑Oriented Programming (AOP) separates cross‑cutting concerns such as transaction management, permission checks, caching, and logging from core business logic. By defining pointcuts and advices, we can centralize logging without modifying controller code.

Define a pointcut that matches all methods in the controller package:

@Pointcut("execution(* your_package.controller..*(..))")
public void requestServer() {}

Before advice logs the client IP, request URL, HTTP method, and the invoked controller method:

@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, captures the method result, and logs request parameters obtained via a helper method:

@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;
}

The getRequestParams method extracts parameter names and values, handling MultipartFile specially to log only the original filename:

private Map
getRequestParams(ProceedingJoinPoint proceedingJoinPoint) {
    Map
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;
}

To avoid log interleaving under high concurrency, the article introduces a RequestInfo DTO that aggregates all log fields and serializes the object 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 updated doAround creates a RequestInfo instance, fills it, and logs it with JSON.toJSONString(requestInfo) :

@Around("requestServer()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();
    Object result = proceedingJoinPoint.proceed();
    RequestInfo requestInfo = new RequestInfo();
    requestInfo.setIp(request.getRemoteAddr());
    requestInfo.setUrl(request.getRequestURL().toString());
    requestInfo.setHttpMethod(request.getMethod());
    requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName()));
    requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));
    requestInfo.setResult(result);
    requestInfo.setTimeCost(System.currentTimeMillis() - start);
    LOGGER.info("Request Info : {}", JSON.toJSONString(requestInfo));
    return result;
}

For exception scenarios, a RequestErrorInfo DTO captures the same request data plus the thrown exception, and an @AfterThrowing advice logs it:

@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 attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();
    RequestErrorInfo requestErrorInfo = new RequestErrorInfo();
    requestErrorInfo.setIp(request.getRemoteAddr());
    requestErrorInfo.setUrl(request.getRequestURL().toString());
    requestErrorInfo.setHttpMethod(request.getMethod());
    requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()));
    requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint));
    requestErrorInfo.setException(e);
    LOGGER.info("Error Request Info : {}", JSON.toJSONString(requestErrorInfo));
}

Utility methods getRequestParamsByProceedingJoinPoint , getRequestParamsByJoinPoint , and buildRequestParam share the same parameter‑extraction logic.

Finally, the complete aspect class combines all advices and helper methods, providing a ready‑to‑use solution for detailed request logging in Spring‑based backend services.

BackendJavaAOPSpringAspectJRequest Logging
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.