How to Build a Pluggable Operation Log System with AOP and Annotations in Java

This article explains a complete, reusable solution for recording operation logs in Java backend applications using Spring AOP, custom annotations, callback interfaces, and a user‑adapter to bridge third‑party logging libraries with the main program, enabling flexible storage and auditing.

Lin is Dream
Lin is Dream
Lin is Dream
How to Build a Pluggable Operation Log System with AOP and Annotations in Java

In common backend admin systems, operation logs are required for quick troubleshooting, regular audit, and to record sensitive actions.

Log Content Requirements

User information (username, role, IP, request path) Operation type Operation content (brief description) Operation time Current menu Method parameters Method response Operation duration

Solution Overview

We can use AOP proxy + annotation to create a generic operation‑log collection scheme. The proxy gathers log data and passes it to a user‑defined saver; it is recommended to store logs in a database using multithreading and then synchronize them to a queue such as Elasticsearch.

Define Annotation and Aspect

The aspect converts the collected runtime parameters into a log object.

package com.su4j.logger;
import com.su4j.util.ExpressionParserUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Arrays;
/**
 * Created on 2025/2/11 13:56
 * @author linshimeng
 * @version 1.0.0
 * @description
 */
@Slf4j
@Aspect
public class OperationLogAspect {
    private final LogCallbackManager logCallbackManager;
    private final LogUserInfoAdapter logUserInfoAdapter;
    public OperationLogAspect(LogCallbackManager logCallbackManager, LogUserInfoAdapter logUserInfoAdapter) {
        this.logCallbackManager = logCallbackManager;
        this.logUserInfoAdapter = logUserInfoAdapter;
    }
    @Around("@annotation(logOperation)")
    public Object logOperation(ProceedingJoinPoint joinPoint, LogOperation logOperation) throws Throwable {
        // Execute target method
        LocalDateTime startTime = LocalDateTime.now();
        Object result = null;
        try {
            result = joinPoint.proceed();
        } finally {
            // Build log object
            LocalDateTime endTime = LocalDateTime.now();
            OperationLog log = new OperationLog();
            log.setOperationUser(logUserInfoAdapter.getCurrentUser());
            log.setMenu(logOperation.menu());
            log.setType(logOperation.type());
            log.setContent(ExpressionParserUtil.resolveKey(logOperation.content(), joinPoint));
            log.setOperationTime(startTime);
            log.setParams(Arrays.toString(joinPoint.getArgs()));
            log.setResult(result);
            log.setSpendTime(Duration.between(startTime, endTime));
            // Execute callbacks in order
            logCallbackManager.executeCallbacks(log);
        }
        return result;
    }
}

Define Callback Interface

After the aspect creates the log object, the callback method receives the operation log.

package com.su4j.logger;
import java.util.List;
/**
 * Constructed via dependency injection with a List of LogCallback implementations
 */
public class LogCallbackManager {
    private final List<LogCallbackInterface> logCallbackInterfaces;
    public LogCallbackManager(List<LogCallbackInterface> logCallbackInterfaces) {
        this.logCallbackInterfaces = logCallbackInterfaces;
    }
    /**
     * Execute callbacks in the order of the List
     */
    public void executeCallbacks(OperationLog log) {
        if (logCallbackInterfaces != null && logCallbackInterfaces.size() > 0) {
            for (LogCallbackInterface logCallbackInterface : logCallbackInterfaces) {
                // Execute callback
                logCallbackInterface.resolveLog(log);
            }
        }
    }
}
package com.su4j.logger;
/**
 * Parse log
 */
public interface LogCallbackInterface {
    void resolveLog(OperationLog operationLog);
}

Login User Adapter

Because the logging aspect may reside in a third‑party library and cannot directly obtain the main program’s user context, we define a user adapter to pass external user information to the aspect.

package com.su4j.logger;
/**
 * Get current operation user
 */
public interface LogUserInfoAdapter {
    OperationUser getCurrentUser();
}

public class DefaultLogUserInfoAdapter implements LogUserInfoAdapter {
    @Override
    public OperationUser getCurrentUser() {
        return null;
    }
}

Custom Log Parsing

Implement the user adapter and callback in the main program.

@Slf4j
@Component
public class MyLogCallbackImpl implements LogCallbackInterface {
    @Override
    public void resolveLog(OperationLog operationLog) {
        // todo custom save
        log.info("operationLog:{},{}", operationLog, operationLog.getSpendTime().toMillis());
    }
}

@Component
public class MyLoginLogUserImpl implements LogUserInfoAdapter {
    @Override
    public OperationUser getCurrentUser() {
        // todo simulate user
        OperationUser user = new OperationUser();
        user.setUserId(UUID.randomUUID().toString());
        user.setUsername("test");
        user.setRelo("root");
        user.setIpAddress("127.0.0.1");
        user.setPath("/test");
        return user;
    }
}

Test Usage

Write a controller where the operation content supports Spring EL expressions.

@Slf4j
@RestController
@RequestMapping("/test")
public class LoggerTestController {
    @LogOperation(type = "更新用户", menu = "用户管理-更新用户", content = "'发起请求:'+{#user.username}")
    @PostMapping("/update")
    public String update(@RequestBody OperationUser user) {
        // todo simulate operation
        return user.getUsername();
    }
}

Summary

The solution fully exploits Spring AOP and annotations to implement a pluggable, decoupled operation‑log mechanism. Log collection is expressed as annotation + aspect, and the resulting log is handed to business code via callbacks, allowing flexible persistence such as database storage or asynchronous sync to Elasticsearch. Because the aspect resides in an independent third‑party package, a user‑adapter is introduced to pass user context from the main application, breaking runtime boundaries and achieving true separation between logging logic and business logic.

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.

BackendJavaAnnotationspring-aopcallbackOperation Log
Lin is Dream
Written by

Lin is Dream

Sharing Java developer knowledge, practical articles, and continuous insights into computer engineering.

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.