当前位置: 代码迷 >> 综合 >> Aop日志记录
  详细解决方案

Aop日志记录

热度:18   发布时间:2024-02-22 23:56:53.0

Web 日志注解

清单 3. Web 日志注解代码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ControllerWebLog {String name();boolean intoDb() default false;}

显示更多

其中 name 为所调用接口的名称, intoDb 则标识该条操作日志是否需要持久化存储,Spring Boot 连接数据库的配置,可以参考 SpringBoot 项目配置多数据源 这篇文章,具体的数据库结构可以 点击这里获取 。现在注解有了,我们接下来需要编写与该注解配套的 AOP 切面。

实现 WebLogAspect 切面

第 1 步,我们定义了一个切面类 WebLogAspect 如清单 4 所示。其中@Aspect 注解是告诉 Spring 将该类作为一个切面管理,@Component 注解是说明该类作为一个 Spring 组件。

清单 4. WebLogAspect

   @Aspect@Component@Order(100)public class WebLogAspect {}

显示更多

第 2 步,接下来我们需要定义一个切点。

清单 5. Web 日志 AOP 切点

   @Pointcut("execution(* cn.itweknow.sbaop.controller..*.*(..))")public void webLog() {}

显示更多

对于 execution 表达式, 官网 的介绍为(翻译后):

清单 6. 官网对 execution 表达式的介绍

   execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)

显示更多

其中除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。这个解释可能有点难理解,下面我们通过一个具体的例子来了解一下。在 WebLogAspect 中我们定义了一个切点,其 execution 表达式为 * cn.itweknow.sbaop.controller..*.*(..) ,下表为该表达式比较通俗的解析:

表 1. execution() 表达式解析

标识符 含义
execution() 表达式的主体
第一个 * 符号 表示返回值的类型, * 代表所有返回类型
cn.itweknow.sbaop.controller AOP 所切的服务的包名,即需要进行横切的业务类
包名后面的 .. 表示当前包及子包
第二个 * 表示类名, * 表示所有类
最后的 .*(..) 第一个 . 表示任何方法名,括号内为参数类型, .. 代表任何类型参数

第 3 步,@Before 修饰的方法中的内容会在进入切点之前执行,在这个部分我们需要打印一个开始执行的日志,并将请求参数和开始调用的时间存储在 ThreadLocal 中,方便在后面结束调用时打印参数和计算接口耗时。

清单 7. @Before 代码

   @Before(value = "webLog()& &  @annotation(controllerWebLog)")public void doBefore(JoinPoint joinPoint, ControllerWebLog controllerWebLog) {// 开始时间。long startTime = System.currentTimeMillis();Map<String, Object> threadInfo = new HashMap<>();threadInfo.put(START_TIME, startTime);// 请求参数。StringBuilder requestStr = new StringBuilder();Object[] args = joinPoint.getArgs();if (args != null && args.length > 0) {for (Object arg : args) {requestStr.append(arg.toString());}}threadInfo.put(REQUEST_PARAMS, requestStr.toString());threadLocal.set(threadInfo);logger.info("{}接口开始调用:requestData={}", controllerWebLog.name(), threadInfo.get(REQUEST_PARAMS));}

显示更多

第 4 步,@AfterReturning ,当程序正常执行有正确的返回时执行,我们在这里打印结束日志,最后不能忘的是清除 ThreadLocal 里的内容。

清单 8. @AfterReturning 代码

   @AfterReturning(value = "webLog()&& @annotation(controllerWebLog)", returning = "res")public void doAfterReturning(ControllerWebLog controllerWebLog, Object res) {Map<String, Object> threadInfo = threadLocal.get();long takeTime = System.currentTimeMillis() - (long) threadInfo.getOrDefault(START_TIME, System.currentTimeMillis());if (controllerWebLog.intoDb()) {insertResult(controllerWebLog.name(), (String) threadInfo.getOrDefault(REQUEST_PARAMS, ""),JSON.toJSONString(res), takeTime);}threadLocal.remove();logger.info("{}接口结束调用:耗时={}ms,result={}", controllerWebLog.name(),takeTime, res);}

显示更多

第 5 步,当程序发生异常时,我们也需要将异常日志打印出来:

清单 9. @AfterThrowing 代码

   @AfterThrowing(value = "webLog()& &  @annotation(controllerWebLog)", throwing = "throwable")public void doAfterThrowing(ControllerWebLog controllerWebLog, Throwable throwable) {Map< String, Object> threadInfo = threadLocal.get();if (controllerWebLog.intoDb()) {insertError(controllerWebLog.name(), (String)threadInfo.getOrDefault(REQUEST_PARAMS, ""),throwable);}threadLocal.remove();logger.error("{}接口调用异常,异常信息{}",controllerWebLog.name(), throwable);}

显示较少

第 6 步,至此,我们的切面已经编写完成了。下面我们需要将 ControllerWebLog 注解使用在我们的测试接口上,接口内部的代码已省略,如有需要的话,请参照 本文源码 。

清单 10. 测试接口代码

   @PostMapping("/post-test")@ApiOperation("接口日志 POST 请求测试")@ControllerWebLog(name = "接口日志 POST 请求测试", intoDb = true)public BaseResponse postTest(@RequestBody BaseRequest baseRequest) {}

显示较少

第 7 步,最后,启动项目,然后打开 Swagger 文档进行测试,调用接口后在控制台就会看到类似图 1 这样的日志。

  相关解决方案