How to Elegantly Implement Decoupled Operation Logging with AOP in Spring

This article explains the difference between system and operation logs, presents common log formats, and provides step‑by‑step implementations—including Canal binlog listening, log‑file recording, LogUtil helpers, and a full AOP‑based @LogRecord solution—while detailing template parsing, context handling, custom functions, and persistence in a Spring Boot environment.

dbaplus Community
dbaplus Community
dbaplus Community
How to Elegantly Implement Decoupled Operation Logging with AOP in Spring

What is an operation log and how it differs from a system log

System logs are mainly for developers to troubleshoot issues and often contain low‑level details such as class names and line numbers. Operation logs, on the other hand, record user‑visible events like order creation or status changes and must be highly readable.

Typical operation‑log formats

Simple text, e.g., "2021‑09‑16 10:00 Order created".

Dynamic text with variables, e.g., "2021‑09‑16 10:00 Order created, orderNo: NO.11089999".

Change‑type text showing before/after values, e.g., "User XiaoMing changed delivery address from \"Gold Community\" to \"Silver Community\"".

Form‑style updates that modify multiple fields at once.

Implementation methods

Canal binlog listener Canal parses MySQL binlog entries, allowing the system to capture every data change without touching business code. This cleanly separates logging from business logic but only works for database‑driven changes; RPC‑based actions still need manual logging.

Log‑file recording Directly write log statements using SLF4J/Logback. Three key problems must be solved:

Who performed the operation Put the user identifier into MDC in an interceptor and reference it in the log pattern:

@Component
public class UserInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String userNo = getUserNo(request);
        MDC.put("userId", userNo);
        return super.preHandle(request, response, handler);
    }
    private String getUserNo(HttpServletRequest request) { return null; }
}

Logback pattern example:

%d{yyyy-MM-dd HH:mm:ss.SSS} %t %-5level %X{userId} %logger{30}.%method:%L - %msg%n

Separate operation logs from system logs Define a dedicated appender and logger for business logs:

<appender name="businessLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <File>logs/business.log</File>
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
        <level>INFO</level>
        <onMatch>ACCEPT</onMatch>
        <onMismatch>DENY</onMismatch>
    </filter>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>logs/business.%d.%i.log</fileNamePattern>
        <maxHistory>90</maxHistory>
        <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
            <maxFileSize>10MB</maxFileSize>
        </timeBasedFileNamingAndTriggeringPolicy>
    </rollingPolicy>
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %t %-5level %X{userId} %logger{30}.%method:%L - %msg%n</pattern>
        <charset>UTF-8</charset>
    </encoder>
</appender>
<logger name="businessLog" additivity="false" level="INFO">
    <appender-ref ref="businessLogAppender"/>
</logger>

Business code then uses the dedicated logger:

private final Logger businessLog = LoggerFactory.getLogger("businessLog");
businessLog.info("Modified delivery address");

Generating readable log messages Wrap log creation in a utility (LogUtil) or use AOP to build templates automatically.

AOP‑based @LogRecord Define an annotation to describe the log content, operator, and business identifier. Example:

@LogRecord(content="User %s created order %s", operator="#request.userName", bizNo="#request.orderNo")
public void createOrder(CreateOrderRequest request) { /* business logic */ }

The AOP interceptor parses the annotation, evaluates SpEL expressions, and records the log after method execution. Core classes include LogRecordAnnotation , LogRecordPointcut , LogRecordInterceptor , LogRecordExpressionEvaluator , and LogRecordContext . Key points:

SpEL is used for dynamic placeholders; custom functions can be added via IParseFunction implementations. LogRecordContext stores variables in an

InheritableThreadLocal<Stack<Map<String,Object>>>

to avoid variable leakage across nested method calls.

The operator is resolved from the annotation or, if empty, from an IOperatorGetService implementation that reads the current user from thread‑local context.

Component structure

The library consists of four modules:

AOP module – intercepts methods annotated with @LogRecord.

Log parsing module – evaluates templates, SpEL, and custom functions.

Log persistence module – defines ILogRecordService for storing logs (file, DB, Elasticsearch, etc.).

Starter module – auto‑configures beans and provides @EnableLogRecord(tenant="com.example") for easy integration.

Summary

By combining Canal, log‑file recording, LogUtil helpers, and a full AOP‑based @LogRecord solution, developers can achieve a clean, decoupled, and easily readable operation‑logging system that works across complex business scenarios while keeping the logging code out of core business logic.

Operation log example
Operation log example
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.

javaaopspringSpELCanaloperation loggingLogRecord
dbaplus Community
Written by

dbaplus Community

Enterprise-level professional community for Database, BigData, and AIOps. Daily original articles, weekly online tech talks, monthly offline salons, and quarterly XCOPS&DAMS conferences—delivered by industry experts.

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.