Implementing a Dynamic AOP Log Aspect for Unified API Parameter Logging in Spring Boot

The article explains why logging API request and response parameters is essential for debugging, shows how to configure environment‑ and interface‑level switches, defines a simple @ApiLog annotation, implements an AOP aspect that records request details and response results, and demonstrates separating logs via Logback configuration while noting performance considerations.

Shepherd Advanced Notes
Shepherd Advanced Notes
Shepherd Advanced Notes
Implementing a Dynamic AOP Log Aspect for Unified API Parameter Logging in Spring Boot

Background

Incorrect request parameters often cause API failures, especially for third‑party callbacks and open APIs. Developers need a way to capture request and response data for evidence.

Configurable Switch

A Boolean property ptc.apiLog.enable (default false) controls logging. It is bound to ApiLogProperties:

@Data
@ConfigurationProperties(prefix = "ptc.apiLog")
public class ApiLogProperties {
    /** Whether to enable API log printing, default false */
    private Boolean enable = Boolean.FALSE;
}

The bean is injected into the aspect, allowing runtime toggling without restart.

@ApiLog Annotation

A marker annotation without attributes indicates that a controller class or method should have its parameters logged:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface ApiLog {}

AOP Aspect Implementation

The core aspect ApiLogPrintAspect intercepts controller methods, checks the switch, verifies the presence of @ApiLog, records request details, proceeds with execution, then logs the response and execution time.

@Aspect
@Slf4j
@Order(value = OrderConstant.AOP_API_LOG)
public class ApiLogPrintAspect {
    @Resource
    private ApiLogProperties apiLogProperties;

    @Around("execution(* com.plasticene..controller..*(..))")
    public Object timeAround(ProceedingJoinPoint joinPoint) throws Throwable {
        if (!apiLogProperties.getEnable()) {
            return joinPoint.proceed();
        }
        ApiLog apiLog = getApiLog(joinPoint);
        if (Objects.isNull(apiLog)) {
            return joinPoint.proceed();
        }
        long start = System.currentTimeMillis();
        HttpServletRequest request = getRequest();
        RequestInfo requestInfo = new RequestInfo();
        requestInfo.setIp(request.getRemoteAddr());
        requestInfo.setUrl(request.getRequestURL().toString());
        requestInfo.setHttpMethod(request.getMethod());
        requestInfo.setClassMethod(String.format("%s.%s",
                joinPoint.getSignature().getDeclaringTypeName(),
                joinPoint.getSignature().getName()));
        requestInfo.setRequestParams(getRequestParams(joinPoint, request));
        log.info("Request Info : {}", JsonUtils.toJsonString(requestInfo));

        Object result = joinPoint.proceed();

        log.info("Response result:  {}", JsonUtils.toJsonString(result));
        log.info("time cost:  {}", System.currentTimeMillis() - start);
        return result;
    }

    private Object getRequestParams(ProceedingJoinPoint joinPoint, HttpServletRequest request) throws UnsupportedEncodingException {
        Object[] args = joinPoint.getArgs();
        Object params = null;
        String queryString = request.getQueryString();
        String method = request.getMethod();
        if (args.length > 0) {
            if ("POST".equals(method) || "PUT".equals(method) || "DELETE".equals(method)) {
                int length = args.length;
                int index = 0;
                Object object = null;
                while (index < length) {
                    Object o = args[index];
                    index++;
                    if (o instanceof HttpServletRequest || o instanceof HttpServletResponse) {
                        continue;
                    } else {
                        object = o;
                        break;
                    }
                }
                if (object instanceof MultipartFile) {
                    MultipartFile multipartFile = (MultipartFile) object;
                    params = MessageFormat.format("文件名: {0}, 大小: {1}", multipartFile.getOriginalFilename(), multipartFile.getSize());
                } else {
                    params = object;
                }
            } else if ("GET".equals(method) && StrUtil.isNotBlank(queryString)) {
                params = URLDecoder.decode(queryString, "utf-8");
            }
        }
        return params;
    }

    private HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return requestAttributes.getRequest();
    }

    private ApiLog getApiLog(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        ApiLog apiLog = method.getAnnotation(ApiLog.class);
        if (Objects.isNull(apiLog)) {
            apiLog = method.getDeclaringClass().getAnnotation(ApiLog.class);
        }
        return apiLog;
    }
}

The aspect logs a single‑line JSON representation of request information (IP, URL, HTTP method, class method, parsed parameters) and the response object, then records the execution time.

Enabling the Feature

Set the switch to true in application.yml (or application.properties).

ptc:
  api:
    log:
      enable: true

Test Controller

Sample endpoint:

@PostMapping("/test/api/log")
@ApiLog
public User printApiLog(@RequestBody User user) throws InterruptedException {
    log.info("api log打印啦....");
    TimeUnit.SECONDS.sleep(1);
    return user;
}

Console logs include request info, response result, and time cost, e.g.:

[INFO] Request Info : {"ip":"127.0.0.1","url":"http://127.0.0.1:18888/fds/test/test/api/log","httpMethod":"POST","classMethod":"com.plasticene.fast.controller.TestController.printApiLog","requestParams":{...}}
[INFO] Response result:  {"id":123,"name":"she哈哈",...}
[INFO] time cost:  1115

Separating Interface Logs

Change the Lombok logger to a named topic to separate interface logs:

@Slf4j(topic = "ptc.api.log")

Add an appender and logger definition to logback.xml:

<appender name="INTERFACE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${log.path}/app-interface.log</file>
    <encoder>
        <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        <charset>UTF-8</charset>
    </encoder>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>${log.path}/app-interface-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
        <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
            <maxFileSize>100MB</maxFileSize>
        </timeBasedFileNamingAndTriggeringPolicy>
        <maxHistory>15</maxHistory>
    </rollingPolicy>
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
        <level>info</level>
        <onMatch>ACCEPT</onMatch>
        <onMismatch>DENY</onMismatch>
    </filter>
</appender>

<logger name="ptc.api.log" level="info" additivity="false">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="INTERFACE_FILE"/>
</logger>

After configuration, interface parameter logs are written to app-interface.log, while regular business logs continue to go to app-info.log.

Conclusion

The solution provides a dynamically configurable way to capture API request and response data via an AOP aspect, aiding rapid issue location. Enabling extensive logging in production may affect performance and increase load on distributed log collection systems.

Source code of the aspect: https://github.com/plasticene/plasticene-boot-starter-parent/blob/main/plasticene-boot-starter-web/src/main/java/com/plasticene/boot/web/core/aop/ApiLogPrintAspect.java

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.

JavaAOPLoggingSpring BootLogbackAspectJApiLog
Shepherd Advanced Notes
Written by

Shepherd Advanced Notes

Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.

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.