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.

Java Backend Technology
Java Backend Technology
Java Backend Technology
How to Build a Robust Request Logging Aspect in Spring Boot

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Backendloggingtraceidrequest
Java Backend Technology
Written by

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!

0 followers
Reader feedback

How this landed with the community

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.