本文主要介绍在 Spring Boot 中使用 AOP 实现自定义日志记录并保存在 Mysql 的方法。先阐述记录日志的重要性及传统方式的弊端,提出新方式,即通过创建自定义注解、切面类等,将重要日志存到数据库,还给出了创建日志表、注解类、切面类及在代码中使用的具体步骤和存储结果。
记录日志的重要性
记录日志在开发中起着至关重要的作用。它不仅可以提高系统的可靠性、安全性和性能,还可以为故障排查、业务分析和合规要求提供支持。在Spring Boot中,我们通常使用日志框架如Logback、Log4j等进行日志记录。这些框架提供了灵活的配置选项和丰富的日志级别和输出格式,使得日志记录变得方便且易于管理。
传统记录日志的弊端
然而,传统的日志记录方式通常是将日志记录在text文本中,并且会定期删除。这种方式存在一些局限性,比如无法轻松地获取很久之前的日志、进行日志数据的统计和分析整理、实现数据的关联和追踪等功能。此外,对于一些重要的日志,我们可能希望具备更高的安全性和隐私性,或者需要对日志数据进行备份和恢复,甚至希望能够实现日志数据的共享。这就需要我们思考一种新的记录日志的方式。
在实际开发中,我们通常需要更多的功能来满足日志记录的需求:
- 查询历史日志:我们可能需要获取很久之前的日志数据,以便进行历史记录的查看和分析。
- 统计和分析日志数据:对日志数据进行统计和整理,以便进行业务分析和系统性能优化。
- 数据关联和追踪:需要将日志数据与其他数据进行关联,以实现对系统操作和业务流程的追踪和监控。
- 提高日志的安全性和隐私性:确保敏感信息不被泄露,对日志数据进行安全加密和权限控制。
- 日志数据备份和恢复:对重要的日志数据进行定期备份,以便在系统故障或数据丢失时进行快速恢复。
- 数据共享:需要能够方便地与其他系统或团队共享和交换日志数据,以实现更全面的系统监控和分析。
以上这些功能是我们在日志记录过程中经常会遇到的需求,对于如何实现这些功能,我们需要综合考虑技术、业务和安全等方面的因素,以便为系统的可靠性、安全性和性能提供更全面的支持。
使用AOP将日志存储到Mysql中
一种解决方案是使用AOP(面向切面编程)结合自定义注解来实现日志存储。AOP允许我们在程序执行的特定点织入代码,而自定义注解则可用于标记需要记录日志的方法。通过这种方式,我们可以实现灵活且精准的日志记录,并在需要时将重要的日志存储到数据库中。
为了实现这一目标,我们可以按照以下步骤进行:
首先,创建一个自定义注解,用来标记需要记录日志的方法。该注解可以包含一些额外的信息,如操作描述等,以便于更详细地记录日志。
其次,创建一个切面类,该类包含切入点和通知。在切面类中,我们可以定义通知,在方法执行前后、抛出异常时等特定时刻进行日志记录。在通知中,我们可以获取方法的参数、返回值等信息,并将这些信息记录到数据库中。
在需要记录日志的方法上添加自定义注解。通过在方法上添加自定义注解,我们可以指示AOP在执行该方法时进行日志记录,并将日志存储到数据库中。
通过以上步骤,我们就可以实现对日志存储的优化和扩展。这种基于AOP和自定义注解的日志记录方式具有很多优点,包括灵活性高、精细化的记录方式、易于管理和扩展等。同时,通过将重要的日志存储到数据库中,我们可以轻松地获取历史日志、进行统计分析、实现数据关联和追踪等功能。
SpringBoot 整合 AOP
Spring Boot 整合Aop
我们需要实现的目的
-
- 自定义日志记录,包括记录系统接口的日志以及保存到数据库。 (@Log 注解标注需要记录日志的方法或类)
-
- 创建注解 @Log 来指定需要记录的模块、功能等信息,以便进行日志记录。
- 30 创建切面类 LogAspect,用于记录请求信息、当前用户信息、错误信息等,并将这些信息保存到数据库中
步骤
1、创建一个all_data_log自定义日志表
主要定义了操作日志的一些基本的信息,用来对我们AOP自定义的日志进行存储。
CREATE TABLE `all_data_log` (
`user_id` int DEFAULT NULL COMMENT '用户名',
`url` varchar(512) DEFAULT NULL COMMENT '接口',
`ip` varchar(100) DEFAULT NULL COMMENT 'ip地址',
`param` text COMMENT '接口参数',
`time` datetime DEFAULT NULL COMMENT '时间',
`method` varchar(100) DEFAULT NULL COMMENT '方法',
`project` varchar(100) DEFAULT NULL COMMENT '项目'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='自定义日志表';
2、创建一个注解类 Log
创建一个注解类 Log,定义在类和方法上的注解。注解中可以包括模块、功能、操作人类别、是否保存请求和响应参数等信息。
/**
* 自定义操作日志记录注解
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log
{
/**
* 模块
*/
public String title() default "";
/**
* 功能
*/
public BusinessType businessType() default BusinessType.OTHER;
/**
* 操作人类别
*/
public OperatorType operatorType() default OperatorType.MANAGE;
/**
* 是否保存请求的参数
*/
public boolean isSaveRequestData() default true;
/**
* 是否保存响应的参数
*/
public boolean isSaveResponseData() default true;
}
3. 创建一个切面类
创建一个切面类 LogAspect,用于记录系统接口的日志,并根据需要将日志信息保存到数据库中。在切面类中,可以根据注解中的信息记录请求的URL、方法、IP地址、参数、当前用户信息、错误信息等,并将这些信息保存到数据库中。
@Component
@Aspect
public class LogAspect {
@Autowired
private RedisCache redisCache;
@Autowired
private LogAspect logAspect;
@Value("${token.header}")
private String header;
/**
* 定义一个开关 可以随时关闭这个切面
*/
@Value("${aspect.switch}")
private Boolean aBoolean;
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doBefore(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
handle(joinPoint, jsonResult, null);
}
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
handle(joinPoint, null, e);
}
private void handle(JoinPoint joinPoint, Object jsonResult, Exception e) {
try {
if (aBoolean) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
HashMap<String, Object> map = new HashMap<>();
// 获取请求的URL
map.put("url", request.getRequestURL().toString());
// 获取请求的方法
map.put("method", request.getMethod());
// 获取请求的IP地址
map.put("ip", request.getRemoteAddr());
// 获取请求的参数
Object[] args = joinPoint.getArgs();
String params = "";
try {
if (args != null && args.length > 0) {
for (Object arg : args) {
if (arg instanceof MultipartFile[]) {
params = "files";
}
}
if (!params.equals("files")) {
params = JSON.toJSONString(args[0]);
}
}
} catch (Exception e1) {
params = "参数解析失败";
}
map.put("params", params);
// 获取当前的用户
String token = request.getHeader(header);
String userId = "";
if (ObjectUtils.isNotEmpty(token)) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (ObjectUtils.isNotEmpty(loginUser)) {
userId = loginUser.getUserId().toString();
}
}
map.put("userId", userId);
//记录错误信息
if (e != null) {
map.put("errorMsg", StringUtils.substring(e.getMessage(), 0, 2000));
}
//当前时间
map.put("time", DateUtils.getTime());
// 是否需要保存result,参数和值
if (StringUtils.isNotNull(jsonResult)) {
map.put("result", StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
}
// 异步 保存数据库
AsyncManager.me().execute(AsyncFactory.recordOper(map));
}
} catch (Exception exp) {
exp.printStackTrace();
}
}
}
4. 如何在代码中使用
在需要记录日志的方法上使用 @Log 注解,指定相应的模块、功能等信息,以便进行日志记录。
/**
* 新增商品
*/
@Log(title = "新增商品", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody YdCommAttr ydCommAttr)
{
return toAjax(ydCommAttrService.insertYdCommAttr(ydCommAttr));
}
5. 存储的结果
可以看到我们的切面完美的记录了我们想要记录的接口中的日志数据,以便于我们日后通过关系型数据库Musql去统计和分析这些数据。