1. AOP(面向切面编程)
实现原理 通过拦截方法调用(如Controller层或Service层),在方法执行前后插入日志记录逻辑,实现业务代码与日志逻辑的解耦。
技术实现(以Spring AOP为例):
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void servicePointcut() {}
@Around("servicePointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
// 记录操作日志(方法名、参数、耗时等)
LogUtils.log(
joinPoint.getSignature().getName(),
joinPoint.getArgs(),
result,
endTime - startTime
);
return result;
}
}
优点:
无侵入性:不修改业务代码。集中管理:日志逻辑统一维护。灵活性:支持自定义切面(如仅记录特定注解的方法)。
缺点:
粒度较粗:难以记录数据库变更细节(如字段级变化)。性能损耗:频繁拦截可能影响性能。
适用场景 业务逻辑层的操作记录(如接口调用、方法执行耗时)。
2. Canal中间件监听Binlog
实现原理 通过监听数据库的Binlog日志(如MySQL),解析数据变更事件(增删改),触发日志记录。
技术流程:
部署Canal Server:伪装为MySQL Slave,接收Binlog。客户端订阅变更:解析Binlog事件(如RowChange)。记录变更日志:将变更前后的数据写入日志存储。
示例代码(Canal客户端):
CanalConnector connector = CanalConnectors.newClusterConnector(
"canal-server:11111", "example", "", "");
connector.connect();
connector.subscribe(".*\\..*"); // 订阅所有表
while (true) {
Message message = connector.getWithoutAck(100);
for (CanalEntry.Entry entry : message.getEntries()) {
CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
// 记录变更前后的数据
LogUtils.logDbChange(
entry.getHeader().getTableName(),
rowData.getBeforeColumnsList(),
rowData.getAfterColumnsList()
);
}
}
connector.ack(message.getId());
}
优点:
解耦彻底:完全独立于业务代码。细粒度追踪:支持字段级变更记录。
缺点:
架构复杂:需维护Canal中间件。仅限数据库操作:无法记录非DB操作(如文件上传)。
适用场景 数据库层面的数据变更审计(如订单状态变更、用户信息修改)。
3. 消息队列异步记录
实现原理 将日志事件发送到消息队列(如Kafka、RabbitMQ),由消费者异步处理,避免阻塞主流程。
实现步骤:
生产日志事件:在业务代码中发送日志消息。
kafkaTemplate.send("operation-log", "用户删除操作: ID=123");
消费并持久化:消费者批量处理日志,写入数据库或文件。
@KafkaListener(topics = "operation-log")
public void saveLog(String logMessage) {
logRepository.save(new OperationLog(logMessage));
}
优点:
高性能:异步处理避免主流程阻塞。削峰填谷:应对高并发场景。扩展性:可接入多个消费者处理日志(如分析、报警)。
缺点:
最终一致性:日志可能存在延迟。依赖中间件:需保障消息队列的可用性。
适用场景 高并发系统或需要异步处理的日志(如用户行为埋点)。
4. 直接记录(硬编码)
实现原理 在业务代码中直接调用日志服务,同步写入日志。
示例代码:
public void deleteUser(Long userId) {
try {
userRepository.deleteById(userId);
// 直接记录日志
logService.log("删除用户", "用户ID=" + userId, "SUCCESS");
} catch (Exception e) {
logService.log("删除用户", "用户ID=" + userId, "FAIL: " + e.getMessage());
throw e;
}
}
优点:
简单直接:快速实现,无需额外组件。精准控制:可灵活记录上下文信息。
缺点:
耦合度高:日志逻辑分散在代码各处。维护困难:修改日志格式需改动多处。
适用场景 小型项目或需要精准记录特定操作的场景。
5. 数据库触发器
实现原理 通过数据库触发器(如MySQL Trigger)在数据变更时自动记录日志。
示例SQL:
CREATE TRIGGER log_user_update
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
INSERT INTO user_audit_log
SET user_id = OLD.id,
old_name = OLD.name,
new_name = NEW.name,
operation_time = NOW();
END;
优点:
数据库层面保障:无论通过何种方式修改数据均会触发。无需修改业务代码。
缺点:
性能影响:触发器执行增加数据库负载。维护复杂:难以调试和版本控制。
适用场景 对数据变更的强审计需求,且无法通过应用层实现时。
6. ORM框架事件监听
实现原理 利用ORM框架(如Hibernate、MyBatis)的拦截器监听数据操作事件。
示例(Hibernate Interceptor):
public class AuditLogInterceptor extends EmptyInterceptor {
@Override
public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
logService.log("新增操作", "实体=" + entity.getClass().getName());
return super.onSave(entity, id, state, propertyNames, types);
}
}
优点:
与ORM深度集成:精准捕获数据操作。减少代码侵入。
缺点:
依赖ORM框架:不同框架实现方式差异大。无法捕获原生SQL操作。
适用场景 使用ORM框架且需记录数据变更细节的系统。
方案对比与选型建议
方案
耦合性
性能影响
实现复杂度
适用场景
AOP
低
中
中
业务方法调用追踪
Canal
无
低
高
数据库字段级变更审计
消息队列
低
低
高
高并发异步日志处理
直接记录
高
高
低
简单场景快速实现
数据库触发器
无
高
中
数据库强审计需求
ORM事件监听
中
中
中
ORM框架下的数据操作追踪
选型建议:
全链路审计:结合AOP(业务层)+ Canal(数据库层)。高并发系统:消息队列异步处理 + AOP。简单需求:直接记录或AOP。数据库强审计:Canal或数据库触发器。
根据团队技术栈、性能要求、审计粒度等综合选择,必要时可组合多种方案实现全面覆盖。