Elegant Operation Log Recording in Backend Systems Using AOP and Annotations
The article demonstrates how to implement elegant, business‑logic‑independent operation logging in Java back‑ends by using AOP‑driven annotations, SpEL dynamic templates, custom parse functions, thread‑local context, and a Spring Boot starter, offering flexible persistence and automatic operator retrieval.
Operation logs exist in almost every system and must be simple, readable, and independent of business logic. This article explains how to record operation logs elegantly in Java backend applications.
1. Usage Scenarios
Operation logs differ from system logs: they are user‑facing records of actions such as order creation, status changes, or customer service handling. Typical formats include plain text, dynamic text with variables, and change‑type records that show before/after values.
2. Implementation Methods
2.1 Using Canal to Listen to Database Binlog
Canal parses MySQL binlog to capture data changes, allowing operation logs to be generated without coupling to business code. The drawback is that only database changes can be captured; RPC‑based actions must be logged manually.
2.2 Logging via Log Files
log.info("订单创建");
log.info("订单已经创建,订单编号:{}", orderNo);
log.info("修改了订单的配送地址:从“{}”修改到“{}”,", oldAddress, newAddress);Key issues include recording the operator (using SLF4J MDC), separating operation logs from system logs (different appender), and generating readable log messages.
@Component
public class UserInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String userNo = getUserNo(request);
MDC.put("userId", userNo);
return super.preHandle(request, response, handler);
}
private String getUserNo(HttpServletRequest request) { return null; }
} <pattern>"%d{yyyy-MM-dd HH:mm:ss.SSS} %t %-5level %X{userId} %logger{30}.%method:%L - %msg%n"</pattern>2.3 Using LogUtil
LogUtil.log(orderNo, "订单创建", "小明");
LogUtil.log(orderNo, "订单创建,订单号" + "NO.11089999", "小明");
String template = "用户%s修改了订单的配送地址:从“%s”修改到“%s";
LogUtil.log(orderNo, String.format(template, "小明", "金灿灿小区", "银盏盏小区"), "小明");2.4 Method Annotation with AOP
@LogRecord(content = "修改了配送地址")
public void modifyAddress(UpdateDeliveryRequest request) {
doUpdate(request);
}Static annotation text is insufficient for dynamic data; therefore, SpEL and custom functions are introduced to build dynamic templates.
3. AOP‑Driven Dynamic Templates
SpEL expressions (e.g., #request.address) and custom functions (e.g., {deliveryUser{#oldDeliveryUserId}}) allow placeholders to be resolved at runtime. The operator can be automatically fetched from a user context when not supplied in the annotation.
@LogRecord(content = "修改了订单的配送地址:从“#oldAddress”, 修改到“#request.address"", bizNo = "#request.deliveryOrderNo")
public void modifyAddress(UpdateDeliveryRequest request) {
LogRecordContext.putVariable("oldAddress", DeliveryService.queryOldAddress(request.getDeliveryOrderNo()));
doUpdate(request);
}4. Core Implementation Details
4.1 Code Structure
The component consists of four modules: AOP interceptor, log parsing, log persistence, and a Spring Boot starter. Extension points include custom functions, default operator retrieval, business persistence, and query logic.
4.2 Parsing Logic
SpEL is used via LogRecordExpressionEvaluator, which caches expressions for performance. An EvaluationContext combines method parameters, variables from LogRecordContext, the method return value, and error messages.
EvaluationContext evaluationContext = expressionEvaluator.createEvaluationContext(method, args, targetClass, ret, errorMsg, beanFactory);4.3 LogRecordContext
A thread‑local stack (
InheritableThreadLocal<Stack<Map<String, Object>>>) stores variables per method invocation, preventing variable leakage when nested annotated methods are executed.
public class LogRecordContext {
private static final InheritableThreadLocal<Stack<Map<String, Object>>> variableMapStack = new InheritableThreadLocal<>();
// putVariable, getVariables, clear, etc.
}4.4 Default Operator Retrieval
If the operator attribute is empty, the framework calls IOperatorGetService.getUser() (e.g., from UserContext.getCurrentUser()) to obtain the current user.
public class DefaultOperatorGetServiceImpl implements IOperatorGetService {
@Override
public Operator getUser() {
return Optional.ofNullable(UserUtils.getUser())
.map(u -> new Operator(u.getName(), u.getLogin()))
.orElseThrow(() -> new IllegalArgumentException("user is null"));
}
}4.5 Custom Functions
Implement IParseFunction to provide additional processing (e.g., converting an ID to a name). Functions can be marked as executeBefore() to run prior to business logic.
public interface IParseFunction {
default boolean executeBefore() { return false; }
String functionName();
String apply(String value);
}4.6 Persistence
The ILogRecordService interface abstracts log storage. Implementations may write to files, databases, Elasticsearch, etc.
public class DefaultLogRecordServiceImpl implements ILogRecordService {
@Override
public void record(LogRecord logRecord) {
log.info("【logRecord】log={}", logRecord);
}
}4.7 Spring Boot Starter
Adding @EnableLogRecord(tenant = "com.mzt.test") to the application bootstraps all components. The starter configures beans such as LogRecordOperationSource, LogRecordInterceptor, custom function factories, and default operator/log services.
@SpringBootApplication
@EnableLogRecord(tenant = "com.mzt.test")
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}5. Summary
The article presents a comprehensive solution for operation‑log recording that separates logging concerns from business logic, supports dynamic templates via SpEL and custom functions, and provides a pluggable Spring Boot starter for easy integration.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Meituan Technology Team
Over 10,000 engineers powering China’s leading lifestyle services e‑commerce platform. Supporting hundreds of millions of consumers, millions of merchants across 2,000+ industries. This is the public channel for the tech teams behind Meituan, Dianping, Meituan Waimai, Meituan Select, and related services.
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.
