切面记录日志

    技术2022-07-11  94

    切面记录日志

    在项目中会出现各种异常,有服务器异常,数据库异常,用户操作异常等各种 问题,在项目运维的时候通常需要根据日志来查问题。有些问题是周期性的,比 如OOM等。使用切面来记录日志不会对代码有侵入性,可以统一管理,方便维护。

    导入依赖

    <!--切面--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!--hutool工具--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>4.5.6</version> </dependency>

    设计日志对象

    @Data public class SysLog { /** * 主键 */ private Integer id; /** * 操作IP */ private String ip; /** * 操作地点 */ private String location; /** * 操作类型 1 操作记录 2异常记录 */ private Integer type; /** * 操作人ID */ private String userName; /** * 操作描述 */ private String description; /** * 请求方法 */ private String actionMethod; /** * 请求url */ private String actionUrl; /** * 请求参数 */ private String params; /** * 操作系统 */ private String os; /** * 浏览器 */ private String browser; /** * 类路径 */ private String classPath; /** * 请求方法 */ private String requestMethod; /** * 开始时间 */ private LocalDateTime startTime; /** * 完成时间 */ private LocalDateTime finishTime; /** * 消耗时间 */ private Long consumingTime; /** * 异常详情信息 堆栈信息 */ private String exDetail; /** * 异常描述 e.getMessage */ private String exDesc; }

    定义切面

    @Slf4j @Component @Aspect public class SysLogAspect { private ThreadLocal<SysLog> sysLogThreadLocal = new ThreadLocal<>(); /** * 事件发布是由ApplicationContext对象管控 * 发布事件只需要注入ApplicationContext */ @Autowired private ApplicationContext applicationContext; /** * 定义切入点拦截规则 */ @Pointcut("@annotation(com.it.springbootdemo.logger.annotation.SysOperaLog)") public void sysLogAspect(){} /** * 前置拦截 * @param joinPoint */ @Before(value = "sysLogAspect()") public void recordLog(JoinPoint joinPoint) throws Exception { SysLog sysLog = new SysLog(); //将当前实体保存到threadLocal sysLogThreadLocal.set(sysLog); // 开始时间 long beginTime = Instant.now().toEpochMilli(); HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); //访问目标方法的参数 可动态改变参数值 Object[] args = joinPoint.getArgs(); //获取执行的方法名 sysLog.setActionMethod(joinPoint.getSignature().getName()); // 类名 sysLog.setClassPath(joinPoint.getTarget().getClass().getName()); sysLog.setActionMethod(joinPoint.getSignature().getName()); sysLog.setFinishTime(LocalDateTime.now()); // 参数 sysLog.setParams(Arrays.toString(args)); sysLog.setDescription(LogUtil.getControllerMethodDescription(joinPoint)); long endTime = Instant.now().toEpochMilli(); sysLog.setConsumingTime(endTime - beginTime); } /** * 返回通知 * @param ret */ @AfterReturning(returning = "ret", pointcut = "sysLogAspect()") public void doAfterReturning(Object ret){ //获得当前线程的日志对象 SysLog sysLog = sysLogThreadLocal.get(); //处理完请求,返回内容 R r = Convert.convert(R.class,ret); if (r.getCode() == 200){ //正常返回 sysLog.setType(1); } else { sysLog.setType(2); sysLog.setExDetail(r.getMsg()); } //发布事件 applicationContext.publishEvent(new SysLogEvent(sysLog)); //移除当前Log实体 sysLogThreadLocal.remove(); } /** * 异常通知 * @param e */ @AfterThrowing(pointcut = "sysLogAspect()",throwing = "e") public void doAfterThrowable(Throwable e){ SysLog sysLog = sysLogThreadLocal.get(); //异常 sysLog.setType(2); //异常对象 sysLog.setExDetail(LogUtil.getStackTrace(e)); //异常信息 sysLog.setExDesc(e.getMessage()); //发布事件 applicationContext.publishEvent(new SysLogEvent(sysLog)); //移除当前log实体 sysLogThreadLocal.remove(); } }

    异步发布事件监听

    public class SysLogEvent extends ApplicationEvent { public SysLogEvent(Object source) { super(source); } } @Slf4j @Component public class SysLogListener { @Async @Order @EventListener(SysLogEvent.class) public void saveSysLog(SysLogEvent enet){ SysLog sysLog = (SysLog)enet.getSource(); //保存日志 System.out.println(sysLog.toString()); } }

    使用日志注解

    @Slf4j @RestController public class TestController { @Autowired OrganizationServiceImp organizationImp; @SysOperaLog(descrption = "方法测试") @RequestMapping(value = "test") public R test(){ organizationImp.testJdbc(); int a = 1/0; return R.ok(); } }

    总结:

    设计日志对象 定义注解对象 定义切面拦截注解所使用的地方,使用前置拦截拦截到对应方法,在方法中使用 ThreadLocal对象添加日志对象,并设置日志对象属性,使用后置通知与异常通 知来设置日志对象的异常信息等属性,并做异步操作 定义监听事件,日志对象异步发布后开始执行

    注意

    使用切面拦截的时候有关ThreadLocal和spring的事件发布可以去查阅相关资料
    Processed: 0.011, SQL: 9