Elegant Design and Implementation of Operation Logging Using AOP and Dynamic Templates
This article explains the differences between system and operation logs, explores various implementation methods such as Canal, file logging, LogUtil, and method annotations, and demonstrates how to achieve clean, dynamic, and decoupled operation logging in Java Spring applications using AOP, SpEL, custom functions, and a well‑structured logging context.
Operation logs are present in almost every system and must be simple and readable, unlike system logs which are mainly for debugging. The article first outlines the use cases for operation logs and then presents four main implementation approaches: using Canal to listen to MySQL binlog, writing logs to files, using a LogUtil helper, and applying method annotations with AOP.
Implementation Details
1. @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface LogRecordAnnotation { String success(); String fail() default ""; String operator() default ""; String bizNo(); String category() default ""; String detail() default ""; String condition() default ""; }
2. The AOP interceptor captures methods annotated with @LogRecordAnnotation , records the log after method execution, and ensures that any exception in logging does not affect business logic.
@Override public Object invoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); return execute(invocation, invocation.getThis(), method, invocation.getArguments()); } private Object execute(MethodInvocation invoker, Object target, Method method, Object[] args) throws Throwable { // ... parse annotations, evaluate SpEL, handle custom functions, record log ... }
3. LogRecordContext uses an InheritableThreadLocal<Stack<Map<String, Object>>> to store variables, ensuring isolation between nested annotated methods.
public class LogRecordContext { private static final InheritableThreadLocal >> variableMapStack = new InheritableThreadLocal<>(); // push, pop, putVariable, getVariables methods ... }
4. SpEL is used to build dynamic log messages. Custom functions can be defined by implementing IParseFunction and registered in ParseFunctionFactory so that expressions like {deliveryUser{#oldDeliveryUserId}} are resolved at runtime.
public interface IParseFunction { default boolean executeBefore() { return false; } String functionName(); String apply(String value); }
5. The logging infrastructure is assembled via the @EnableLogRecord annotation, which imports LogRecordProxyAutoConfiguration . This configuration provides beans for the operation source, interceptor, function service, and default implementations for operator retrieval and log persistence.
@EnableLogRecord(tenant = "com.mzt.test") @SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args); } }
6. Users can customize the operator source by implementing IOperatorGetService and the log persistence by implementing ILogRecordService . The default implementations simply fetch the current user from a static context and write logs using log.info , but they can be replaced with database or Elasticsearch storage.
Overall, the article provides a comprehensive guide to building a clean, extensible, and decoupled operation‑logging solution for backend Java services.
High Availability Architecture
Official account for High Availability Architecture.
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.