Implementing Request Logging with Spring AOP: Aspect Code and Best Practices
This article demonstrates how to use Spring AOP to create a request‑logging aspect that captures IP, URL, HTTP method, parameters, results, execution time, and error details, while also addressing high‑concurrency logging issues and integrating trace‑ID tracking for easier debugging.
When a project reaches the integration testing phase, mismatched request parameters often cause debugging failures; using a logging aspect based on Spring AOP provides a non‑intrusive way to capture request details and evidence for troubleshooting.
The core of the solution is an aspect class annotated with @Component and @Aspect . A pointcut is defined to match all controller methods:
@Pointcut("execution(* your_package.controller..*(..))")
public void requestServer() {}A @Before advice logs the caller’s IP, request URL, HTTP method, and the fully qualified controller method name before the method executes:
@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());
}The @Around advice measures execution time, proceeds with the original method, gathers request parameters via a helper, assembles a RequestInfo object, and logs the whole record as JSON:
@Around("requestServer()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
RequestInfo info = new RequestInfo();
HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
info.setIp(req.getRemoteAddr());
info.setUrl(req.getRequestURL().toString());
info.setHttpMethod(req.getMethod());
info.setClassMethod(String.format("%s.%s", pjp.getSignature().getDeclaringTypeName(), pjp.getSignature().getName()));
info.setRequestParams(getRequestParamsByProceedingJoinPoint(pjp));
info.setResult(result);
info.setTimeCost(System.currentTimeMillis() - start);
LOGGER.info("Request Info : {}", JSON.toJSONString(info));
return result;
}An @After advice simply marks the end of the request:
@After("requestServer()")
public void doAfter(JoinPoint joinPoint) {
LOGGER.info("===============================End========================");
}For exceptional cases, an @AfterThrowing advice creates a RequestErrorInfo object that records the same request data plus the thrown exception:
@AfterThrowing(pointcut = "requestServer()", throwing = "e")
public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {
HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
RequestErrorInfo errInfo = new RequestErrorInfo();
errInfo.setIp(req.getRemoteAddr());
errInfo.setUrl(req.getRequestURL().toString());
errInfo.setHttpMethod(req.getMethod());
errInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()));
errInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint));
errInfo.setException(e);
LOGGER.info("Error Request Info : {}", JSON.toJSONString(errInfo));
}Helper methods extract parameter names and values, handling MultipartFile specially to log only the original filename:
private Map
buildRequestParam(String[] paramNames, Object[] paramValues) {
Map
map = new HashMap<>();
for (int i = 0; i < paramNames.length; i++) {
Object value = paramValues[i];
if (value instanceof MultipartFile) {
value = ((MultipartFile) value).getOriginalFilename();
}
map.put(paramNames[i], value);
}
return map;
}To avoid log interleaving under high concurrency, the aspect now logs a single serialized RequestInfo line instead of multiple separate statements, making the output easier to parse.
For traceability, a LogInterceptor adds a unique traceId to the logging context before each request, and the log pattern is updated to include it:
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);
}
} <property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>These techniques together provide a robust, low‑intrusion request‑logging solution for Spring‑based backend services.
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.
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.