【SpringBoot使用 AOP 实现自定义日志记录并保存在Mysql

2023年 12月 31日 62.5k 0

记录日志的重要性

记录日志在开发中起着至关重要的作用。它不仅可以提高系统的可靠性、安全性和性能,还可以为故障排查、业务分析和合规要求提供支持。在Spring Boot中,我们通常使用日志框架如Logback、Log4j等进行日志记录。这些框架提供了灵活的配置选项和丰富的日志级别和输出格式,使得日志记录变得方便且易于管理。

传统记录日志的弊端

然而,传统的日志记录方式通常是将日志记录在text文本中,并且会定期删除。这种方式存在一些局限性,比如无法轻松地获取很久之前的日志、进行日志数据的统计和分析整理、实现数据的关联和追踪等功能。此外,对于一些重要的日志,我们可能希望具备更高的安全性和隐私性,或者需要对日志数据进行备份和恢复,甚至希望能够实现日志数据的共享。这就需要我们思考一种新的记录日志的方式。

在实际开发中,我们通常需要更多的功能来满足日志记录的需求:

  • 查询历史日志:我们可能需要获取很久之前的日志数据,以便进行历史记录的查看和分析。
  • 统计和分析日志数据:对日志数据进行统计和整理,以便进行业务分析和系统性能优化。
  • 数据关联和追踪:需要将日志数据与其他数据进行关联,以实现对系统操作和业务流程的追踪和监控。
  • 提高日志的安全性和隐私性:确保敏感信息不被泄露,对日志数据进行安全加密和权限控制。
  • 日志数据备份和恢复:对重要的日志数据进行定期备份,以便在系统故障或数据丢失时进行快速恢复。
  • 数据共享:需要能够方便地与其他系统或团队共享和交换日志数据,以实现更全面的系统监控和分析。

以上这些功能是我们在日志记录过程中经常会遇到的需求,对于如何实现这些功能,我们需要综合考虑技术、业务和安全等方面的因素,以便为系统的可靠性、安全性和性能提供更全面的支持。

使用AOP将日志存储到Mysql中

一种解决方案是使用AOP(面向切面编程)结合自定义注解来实现日志存储。AOP允许我们在程序执行的特定点织入代码,而自定义注解则可用于标记需要记录日志的方法。通过这种方式,我们可以实现灵活且精准的日志记录,并在需要时将重要的日志存储到数据库中。

为了实现这一目标,我们可以按照以下步骤进行:

首先,创建一个自定义注解,用来标记需要记录日志的方法。该注解可以包含一些额外的信息,如操作描述等,以便于更详细地记录日志。

其次,创建一个切面类,该类包含切入点和通知。在切面类中,我们可以定义通知,在方法执行前后、抛出异常时等特定时刻进行日志记录。在通知中,我们可以获取方法的参数、返回值等信息,并将这些信息记录到数据库中。

在需要记录日志的方法上添加自定义注解。通过在方法上添加自定义注解,我们可以指示AOP在执行该方法时进行日志记录,并将日志存储到数据库中。

通过以上步骤,我们就可以实现对日志存储的优化和扩展。这种基于AOP和自定义注解的日志记录方式具有很多优点,包括灵活性高、精细化的记录方式、易于管理和扩展等。同时,通过将重要的日志存储到数据库中,我们可以轻松地获取历史日志、进行统计分析、实现数据关联和追踪等功能。

SpringBoot 整合 AOP

可以参考我这一篇

Spring AOP & Aspectj 框架 快速入门~

我们需要实现的目的

  • 自定义日志记录,包括记录系统接口的日志以及保存到数据库。 (@Log 注解标注需要记录日志的方法或类)
  • 创建注解 @Log 来指定需要记录的模块、功能等信息,以便进行日志记录。
  • 创建切面类 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();
            }
        }
    }
    

    3、如何在代码中使用

    在需要记录日志的方法上使用 @Log 注解,指定相应的模块、功能等信息,以便进行日志记录。

    /**
     * 新增商品
     */
    @Log(title = "新增商品", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody YdCommAttr ydCommAttr)
    {
        return toAjax(ydCommAttrService.insertYdCommAttr(ydCommAttr));
    }
    

    4、存储的结果

    可以看到我们的切面完美的记录了我们想要记录的接口中的日志数据,以便于我们日后通过关系型数据库Musql去统计和分析这些数据。

    image.png

    觉得作者写的不错的,值得你们借鉴的话,就请点一个免费的赞吧!这个对我来说真的很重要。૮(˶ᵔ ᵕ ᵔ˶)ა

    相关文章

    Oracle如何使用授予和撤销权限的语法和示例
    Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
    下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
    社区版oceanbase安装
    Oracle 导出CSV工具-sqluldr2
    ETL数据集成丨快速将MySQL数据迁移至Doris数据库

    发布评论