切面记录日志
在项目中会出现各种异常,有服务器异常,数据库异常,用户操作异常等各种
问题,在项目运维的时候通常需要根据日志来查问题。有些问题是周期性的,比
如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的事件发布可以去查阅相关资料