Backend Development 16 min read

Implementing Request Logging with Spring AOP and Annotations

This article demonstrates how to create a request‑logging aspect in Spring Boot using AOP annotations, shows code for pointcuts, advices, high‑concurrency optimizations, exception handling, and trace‑ID propagation, and explains the underlying concepts and practical tips.

Architecture Digest
Architecture Digest
Architecture Digest
Implementing Request Logging with Spring AOP and Annotations

Background

During integration testing, mismatched request parameters and missing logs made debugging difficult, so a request‑logging aspect was introduced to capture input data, execution time, and results for every controller method.

AOP Overview

Aspect‑Oriented Programming (AOP) separates cross‑cutting concerns such as logging, transaction management, and security from core business logic, allowing centralized handling of these concerns.

Annotation‑Based Usage

The aspect is declared with @Component and @Aspect . A pointcut selects all methods in the controller package:

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

Various advices are applied:

@Before logs IP, URL, HTTP method, and the invoked method before execution:

@Before("requestServer()")
public void doBefore(JoinPoint joinPoint) {
    ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attrs.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 records request parameters, result, and execution time:

@Around("requestServer()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
    long start = System.currentTimeMillis();
    Object result = pjp.proceed();
    LOGGER.info("Request Params : {}", getRequestParams(pjp));
    LOGGER.info("Result : {}", result);
    LOGGER.info("Time Cost : {} ms", System.currentTimeMillis() - start);
    return result;
}

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

private Map
getRequestParams(ProceedingJoinPoint pjp) {
    Map
map = new HashMap<>();
    String[] names = ((MethodSignature) pjp.getSignature()).getParameterNames();
    Object[] values = pjp.getArgs();
    for (int i = 0; i < names.length; i++) {
        Object val = values[i];
        if (val instanceof MultipartFile) {
            val = ((MultipartFile) val).getOriginalFilename();
        }
        map.put(names[i], val);
    }
    return map;
}

@After simply marks the end of the request:

@After("requestServer()")
public void doAfter(JoinPoint joinPoint) {
    LOGGER.info("===================End===================");
}

High‑Concurrency Optimization

To avoid interleaved log lines under heavy load, a RequestInfo DTO aggregates all data and is logged as a single JSON string.

@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 this object and logs it:

RequestInfo info = new RequestInfo();
info.setIp(request.getRemoteAddr());
info.setUrl(request.getRequestURL().toString());
info.setHttpMethod(request.getMethod());
info.setClassMethod(String.format("%s.%s", pjp.getSignature().getDeclaringTypeName(), pjp.getSignature().getName()));
info.setRequestParams(getRequestParams(pjp));
info.setResult(result);
info.setTimeCost(System.currentTimeMillis() - start);
LOGGER.info("Request Info : {}", JSON.toJSONString(info));

Exception Handling

An @AfterThrowing advice captures failed requests, builds a RequestErrorInfo object, and logs the exception details.

@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 jp, RuntimeException e) {
    // similar extraction of request data
    RequestErrorInfo err = new RequestErrorInfo();
    // set fields …
    err.setException(e);
    LOGGER.info("Error Request Info : {}", JSON.toJSONString(err));
}

Trace‑ID Propagation

An interceptor adds a unique traceId to the logging context (ThreadContext/MDC) before the request and removes it after completion, enabling end‑to‑end tracing.

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(TRACE_ID, traceId);
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ThreadContext.remove(TRACE_ID);
    }
}

The log pattern is updated to include %X{traceId} , making each log line traceable across services.

Additional Notes

The article also discusses handling collections of MultipartFile (e.g., List ) by casting the list and extracting filenames, and provides a utility method castList for safe conversion.

public static
List
castList(Object obj, Class
clazz) {
    List
result = new ArrayList<>();
    if (obj instanceof List
) {
        for (Object o : (List
) obj) {
            result.add(clazz.cast(o));
        }
        return result;
    }
    return null;
}

Overall, the guide offers a complete, production‑ready implementation for request logging, performance tuning under high concurrency, and error tracing in a Spring Boot backend application.

backendJavaperformanceAOPSpringloggingAspectJ
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

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.