首页 > 其他分享 >对SpringBoot接口进行操作日志记录

对SpringBoot接口进行操作日志记录

时间:2023-09-13 14:45:57浏览次数:49  
标签:return SpringBoot args request param public 接口 日志 annotation

最近业务有需求要对所有的用户操作进行日志记录,方便管理员查询不同权限级别的用户对系统的操作记录,现有的日志只是记录了异常信息、业务出错、重要功能的执行进行了记录,并不能满足需求要求,最直接的解决方法是在每个接口上去添加log.info之类的代码,但是这种方式对业务代码的切入性太强,记录日志的代码和业务代码耦合性太强,对于代码的可读性和可维护性来说是一个灾难。那么通过在接口上添加一个注解的方式来实现则要优雅的多。

实现原理就是利用了Spring的切面技术AOP,在接口执行的切面上获取接口方法的参数和执行结果,将要记录的信息记录到数据库(或者是日志文件或者是其他方式)。

1、定义注解:

 1 @Target(ElementType.METHOD)
 2  @Retention(RetentionPolicy.RUNTIME)
 3  @Inherited
 4  @Documented
 5  public @interface LogBook {
 6  ​
 7      /**
 8       * 模块
 9       *
10       * @return
11       */
12      String module();
13  ​
14      /**
15       * 跟踪标识(业务标识)
16       *
17       * @return
18       */
19      String traceId();
20  ​
21      /**
22       * 跟踪标签(业务标识标签)
23       *
24       * @return
25       */
26      String traceTag() default "";
27  ​
28      /**
29       * 操作内容
30       *
31       * @return
32       */
33      String[] content();
34  ​
35      /**
36       * 操作类型
37       * 为null时,框架根据request-method进行匹配
38       *
39       * @return
40       */
41      String operateType() default "";
42  ​
43      /**
44       * 操作员 
45       *
46       * @return
47       */
48      String operator() default "_header";
49  ​
50      /**
51       * 启停
52       *
53       * @return
54       */
55      boolean enable() default true;
56  }

2、定义切面类:

定义切面类使用Aop的注解@Aspect来定义,对制定的注解进行环切,定义如下:

  1 @Aspect
  2  public class GenericRestLogBookAspect {
  3  ​
  4      //切面 包含注解
  5      @Pointcut("@annotation(com.xxx.annotation.LogBook)")
  6      public void intercept() {
  7  ​
  8      }
  9  ​
 10  ​
 11      /**
 12       * 对方法进行环切
 13       *
 14       * @param joinPoint
 15       * @return
 16       * @throws Throwable
 17       */
 18      @Around(value = "intercept()")
 19      public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
 20  ​
 21  ​
 22          /**
 23           * 当前注解实例
 24           */
 25          MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
 26          LogBook annotation = AnnotationUtils.findAnnotation(methodSignature.getMethod(), LogBook.class);
 27          if (annotation == null || !annotation.enable()) {
 28              return joinPoint.proceed();
 29          }
 30  ​
 31          /**
 32           * 基础参数
 33           */
 34          Object[] args = joinPoint.getArgs();
 35          HttpServletRequest request = this.getRequest(args);
 36  ​
 37          LogBookRecord init = new LogBookRecord();
 38          init.setCreateDate(new Date());         //标准时间
 39  ​
 40          EvaluationContext evaluationContext = expressionParser.initContext(joinPoint);
 41  ​
 42  ​
 43          /**
 44           * 执行
 45           */
 46          Object out = null;
 47          Exception err = null;
 48          try {
 49              //前置
 50              this.beforeResolving(request, evaluationContext, annotation, args, init);
 51              //业务执行
 52              out = joinPoint.proceed();
 53          } catch (Exception e) {
 54              err = e;
 55              throw e;
 56          } finally {
 57              //后置
 58              this.afterResolving(request, evaluationContext, annotation, args, init, out, err);
 59              //释放
 60              expressionParser.removeContext();
 61          }
 62          return out;
 63      }
 64  ​
 65      /**
 66       * 前置处理
 67       *
 68       * @param handler
 69       * @param request
 70       * @param evaluationContext
 71       * @param annotation
 72       * @param args
 73       * @param init
 74       */
 75      private void beforeResolving(HttpServletRequest request,
 76                                   EvaluationContext evaluationContext,
 77                                   LogBook annotation,
 78                                   Object[] args,
 79                                   LogBookRecord init) {       
 80          //这里的resolving方法从request中解析出相关参数信息到LogBookRecord对象中
 81          resolving(request, evaluationContext, annotation, args, init);
 82      }
 83  ​
 84  ​
 85      /**
 86       * 后置处理
 87       *
 88       * @param handler
 89       * @param request
 90       * @param evaluationContext
 91       * @param annotation
 92       * @param args
 93       * @param record
 94       * @param result
 95       * @param err
 96       */
 97      private void afterResolving(HttpServletRequest request,
 98                                  EvaluationContext evaluationContext,
 99                                  LogBook annotation,
100                                  Object[] args,
101                                  LogBookRecord record,
102                                  Object result, Exception err) {        
103          /**
104           * 异步处理
105           */
106          try {
107              //重新构建 EvaluationContext
108              EvaluationContext evaluationAfter = expressionParser.setContextResult(evaluationContext, result, err);
109              threadPool.getTaskExecutor().execute(() -> {
110                  //解析 - 根据业务接口返回结果信息解析到LogBookRecord中
111                  List<LogBookRecord> records = resolvingAfter(request, evaluationAfter, annotation, args, result, err, record);
112                  //存储(可以存储到数据库,也可以存储到日志文件或者其他地方)
113                  persistHandler.saveBatch(records);
114              });
115          } catch (Exception e) {
116              logger.error(e.getMessage(), e);
117          }
118      }
119  ​
120  ​
121      /**
122       * 默认从参数中获取
123       *
124       * @param args
125       * @return
126       */
127      private HttpServletRequest getRequest(Object[] args) {
128          Optional<HttpServletRequest> ops = Stream.of(args).filter(e -> e.getClass().isAssignableFrom(HttpServletRequest.class)).map(e -> (HttpServletRequest) e).findFirst();
129          return ops.orElseGet(() -> ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest());
130      }
131  }

3、定义开关属性:

通过这个开关属性,控制开启还是关闭日志记录。

 1 @Configuration
 2  @ConfigurationProperties(prefix = "xxx.logbook")
 3  public class LogBookProperties implements Serializable {
 4  ​
 5      private boolean enable;
 6  ​
 7      public boolean isEnable() {
 8          return enable;
 9      }
10  ​
11      public void setEnable(boolean enable) {
12          this.enable = enable;
13      }
14  }

4、定义配置类:

在配置类中定义切面类的Bean,并通过开关属性进行开关控制;

 1  @Configuration
 2  @ConditionalOnProperty(name = "xxx.logbook.enable", havingValue = "true")
 3  @ComponentScan("com.xxx.logbook")
 4  public class LogBookAutoConfiguration {    
 5  ​
 6      /**
 7       * 切面
 8       *
 9       * @param factory
10       * @param expressionParser
11       * @return
12       */
13      @Bean
14      public GenericRestLogBookAspect logBookAspect(LogBookExpressionParser expressionParser,
15                                                    LogBookPersistHandler persistHandler, LogBookThreadPool threadPool) {
16          return new GenericRestLogBookAspect(expressionParser, persistHandler, threadPool);
17      }
18  }

5、应用示例:

1 @LogBook(module = "custom", 
2          traceId = "{{#dto.id}}",
3          content = {"用户 :提交了新数据:{{#dto.filed1}} {{#dto.filed2}} {{#dto.filed3}} "}, 
4          operateType = OperateTypes.ADD)

6、参考资料:

https://blog.csdn.net/Cr1556648487/article/details/126777903

https://blog.csdn.net/yyhgo_/article/details/128724938

https://blog.csdn.net/weixin_38860401/article/details/124908507

标签:return,SpringBoot,args,request,param,public,接口,日志,annotation
From: https://www.cnblogs.com/laoxia/p/17699641.html

相关文章

  • flask 简单设置日志文件配置
    最近做了几个模型,需要配置接口提供使用,这时候就用到了日志系统首先创建一个logs.py文件,在文件中配置日志等级、保存路径、日志文件大小、日志输出格式importosimportloggingfromlogging.handlersimportRotatingFileHandler#获取当前绝对路径defget_cwd():r......
  • Ego微商小程序 - 接口测试
    接口测试设计流程1.需求分析与评审2.接口文档解析分析接口文档的核心内容API文档:Ego:Ego商城小程序,只存放项目前后端源码文件(gitee.com)3.设计测试用例与评审设计思路介绍一下你简历项目中的XXX项目的接口用例如何设计?答:下面是单接口-正向功能用例 ......
  • 日志记录处理程序¶
    Rich提供了一个日志记录处理程序,它将格式化和着色由Python的日志记录模块编写的文本。下面是如何设置丰富记录器的示例:importloggingfromrich.loggingimportRichHandlerFORMAT="%(message)s"logging.basicConfig(level="NOTSET",format=FORMAT,datefmt="[......
  • IDEA 接口方法不能跳转到实体类实现方法的问题
    没有跳入到实体类实现方法的I+向下的箭头图标。极大可能是因为编辑器自带的代码高亮工具(Syntaxhighlighte)失效。解决方案第一种:清除缓存第二种:快捷键ctrl+alt+shift+h选择Syntax即可......
  • 控制台接口¶
    控制台接口¶为了完全控制终端格式,Rich提供了一个类。大多数应用程序都需要单个控制台实例,因此您可能希望在模块级别创建一个实例或作为顶级对象的属性。例如,您可以将一个名为“console.py”的文件添加到您的项目中:fromrich.consoleimportConsoleconsole=Console()......
  • SpringBoot入门(一) springBoot框架搭建和启动
    1.创建maven工程MavenProject      //CODE    <projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xs......
  • SpringBoot教程(二)springboot的配置文件
    一.springboot配置文件的类型application.propertiesapplication.yml项目结构,因为不可以同时使用这两种文件启动时任选一个放到resources下即可 二.properties配置文件的使用packagecom.lpinfo.shop.lpinfoshop;importorg.springframework.beans.factory.annotation.Autowi......
  • 基于注解的AOP日志切面控制SpringAOP
    1.配置注解(作用于方法上,相当于要告诉aop对哪些方法做切面植入)importjavax.jdo.annotations.Element;importjava.lang.annotation.*;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceAspectPointCutTag{Stringnam......
  • PHP中接口interface的作用
    对象接口使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。由于接口(interface)和类(class)、trait共享了命名空间,所以它们不能重名。接口就像定义一个标准的类一样,通过interface关键字替换掉class关键字来定义,但其中所有的方法都是空的。接......
  • springboot发布部署web jar包
    1.在idea中生成jar包文件 2.我这个项目使用的是JavaJDK20,所以要在官网下载这个版本在服务器上安装。https://www.oracle.com/java/technologies/downloads/   有些系统需要重启下服务器才会生效。 3.把第一步生成的 demo-0.0.1-SNAPSHOT.jar文件复制到服务器......