Implementing a Simple Log Management System with Annotations, AOP, and RabbitMQ in Spring Cloud
This tutorial explains how to build a lightweight log management module by defining a custom @SystemLog annotation, using Spring AOP to capture method execution details, and sending the log data through RabbitMQ via Spring Cloud Stream for asynchronous persistence in a database.
Introduction: In any system, log management is crucial; this article demonstrates building a simple log management module using annotations, AOP, and a message queue.
Design
Three components work together: a custom annotation marks methods, an AOP aspect intercepts those methods to create log objects, and RabbitMQ (integrated through Spring Cloud Stream) transports the logs to a dedicated service.
Database schema
CREATE TABLE `sys_log` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '唯一ID',
`opt_id` int(11) DEFAULT NULL COMMENT '操作用户id',
`opt_name` varchar(50) DEFAULT NULL COMMENT '操作用户名',
`log_type` varchar(20) DEFAULT NULL COMMENT '日志类型',
`log_message` varchar(255) DEFAULT NULL COMMENT '日志信息(具体方法名)',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8 COMMENT='系统日志表';Entity class
@Data
public class SysLog {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private Integer optId; // 操作用户id
private String optName; // 操作用户名
private String logType; // 日志类型
private String logMessage; // 日志信息(具体方法名)
private Date createTime; // 创建时间
}Custom annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemLog {
SystemLogEnum type();
}Enum for log types
public enum SystemLogEnum {
SAVE_LOG("保存"),
DELETE_LOG("删除"),
REGISTER_LOG("注册"),
LOGIN_LOG("登录"),
LAUD_LOG("点赞"),
COLLECT_LOG("收藏"),
THROW_LOG("异常");
private String type;
SystemLogEnum(String type) { this.type = type; }
public String getType() { return type; }
}AOP aspect
@Component
@Aspect
@Slf4j
public class SysLogAspect {
@Autowired
MqStream stream;
@Pointcut("@annotation(cn.zdxh.commons.utils.SystemLog)")
public void logPointcut() {}
@After("logPointcut()")
public void afterLog(JoinPoint joinPoint) {
SysLog sysLog = wrapSysLog(joinPoint);
log.info("Log值:" + sysLog);
stream.logOutput().send(MessageBuilder.withPayload(sysLog).build());
}
@AfterThrowing(value = "logPointcut()", throwing = "e")
public void throwingLog(JoinPoint joinPoint, Exception e) {
SysLog sysLog = wrapSysLog(joinPoint);
sysLog.setLogType(SystemLogEnum.THROW_LOG.getType());
sysLog.setLogMessage(sysLog.getLogMessage() + "===" + e);
log.info("异常Log值:" + sysLog);
stream.logOutput().send(MessageBuilder.withPayload(sysLog).build());
}
public SysLog wrapSysLog(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
SysLog sysLog = new SysLog();
String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
SystemLog systemLog = signature.getMethod().getAnnotation(SystemLog.class);
String token = request.getHeader("token");
if (!StringUtils.isEmpty(token)) {
Integer userId = JwtUtils.getUserId(token);
String username = JwtUtils.getUsername(token);
sysLog.setOptId(userId);
sysLog.setOptName(username);
}
if (!StringUtils.isEmpty(systemLog.type())) {
sysLog.setLogType(systemLog.type().getType());
}
sysLog.setLogMessage(methodName);
sysLog.setCreateTime(new Date());
return sysLog;
}
}RabbitMQ integration
Define an Input/Output interface (MqStream) for Spring Cloud Stream, configure bindings in application.yml , and enable the bindings with @EnableBinding(MqStream.class) . The producer sends logs via logOutput() , while the consumer listens on logInput() and persists the logs.
@Component
public interface MqStream {
String LOG_INPUT = "log_input";
String LOG_OUTPUT = "log_output";
@Input(LOG_INPUT)
SubscribableChannel logInput();
@Output(LOG_OUTPUT)
MessageChannel logOutput();
}Producer configuration (excerpt):
spring:
cloud:
stream:
bindings:
log_output:
destination: log.exchange
content-type: application/json
group: log.queue
binder: youqu_rabbit
binders:
youqu_rabbit:
type: rabbit
environment:
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: 25802580Consumer configuration mirrors the producer but binds to log_input and saves the received SysLog objects.
Application usage
Simply annotate a method with @SystemLog(type = SystemLogEnum.REGISTER_LOG) . When the method is invoked, the AOP aspect automatically creates a log entry, sends it to RabbitMQ, and the consumer persists it to the sys_log table.
Conclusion
The overall flow is: annotation → AOP interception → log object creation → asynchronous transmission via MQ → consumer service inserts the log into the database.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
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.