在上一章内容中——使用logback管理日志,咱们详细讲述了如何将日志生成文件进行存储。可是在实际开发中,使用文件存储日志用来快速查询问题并非最方便的,一个优秀系统除了日志文件还须要将操做日志进行持久化,来监控平台的操做记录。今天咱们一块儿来学习一下如何经过apo来记录日志。java
为了让记录日志更加灵活,咱们将使用自定义的注解来实现重要操做的日志记录功能。git
一 日志记录表
日志记录表主要包含几个字段,业务模块,操做类型,接口地址,处理状态,错误信息以及操做时间。数据库设计以下:github
CREATE TABLE `sys_oper_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键', `title` varchar(50) CHARACTER SET utf8 DEFAULT '' COMMENT '模块标题', `business_type` int(2) DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)', `method` varchar(255) CHARACTER SET utf8 DEFAULT '' COMMENT '方法名称', `status` int(1) DEFAULT '0' COMMENT '操做状态(0正常 1异常)', `error_msg` varchar(2000) CHARACTER SET utf8 DEFAULT '' COMMENT '错误消息', `oper_time` datetime DEFAULT NULL COMMENT '操做时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB CHARSET=utf8mb4 CHECKSUM=1 COMMENT='操做日志记录'
对应的实体类以下:redis
@Data @NoArgsConstructor @AllArgsConstructor public class SysOperLog implements Serializable { private static final long serialVersionUID = 1L; /** 日志主键 */ private Long id; /** 操做模块 */ private String title; /** 业务类型(0其它 1新增 2修改 3删除) */ private Integer businessType; /** 请求方法 */ private String method; /** 错误消息 */ private String errorMsg; private Integer status; /** 操做时间 */ private Date operTime; }
二 自定义注解及处理
自定义注解包含两个属性,一个是业务模块title
,另外一个是操做类型businessType
。spring
@Target({ ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log { /** * 模块 */ String title() default ""; /** * 功能 */ BusinessType businessType() default BusinessType.OTHER; }
使用aop对自定义的注解进行处理sql
@Aspect @Component @Slf4j public class LogAspect { @Autowired private AsyncLogService asyncLogService; // 配置织入点 @Pointcut("@annotation(com.javatrip.aop.annotation.Log)") public void logPointCut() {} /** * 处理完请求后执行 * * @param joinPoint 切点 */ @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult") public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) { handleLog(joinPoint, null, jsonResult); } /** * 拦截异常操做 * * @param joinPoint 切点 * @param e 异常 */ @AfterThrowing(value = "logPointCut()", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint, Exception e) { handleLog(joinPoint, e, null); } protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) { try { // 得到注解 Log controllerLog = getAnnotationLog(joinPoint); if (controllerLog == null) { return; } SysOperLog operLog = new SysOperLog(); operLog.setStatus(0); if (e != null) { operLog.setStatus(1); operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); } // 设置方法名称 String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); operLog.setMethod(className + "." + methodName + "()"); // 处理设置注解上的参数 getControllerMethodDescription(joinPoint, controllerLog, operLog); // 保存数据库 asyncLogService.saveSysLog(operLog); } catch (Exception exp) { log.error("==前置通知异常=="); log.error("日志异常信息 {}", exp); } } /** * 获取注解中对方法的描述信息 用于Controller层注解 * * @param log 日志 * @param operLog 操做日志 * @throws Exception */ public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) { // 设置action动做 operLog.setBusinessType(log.businessType().ordinal()); // 设置标题 operLog.setTitle(log.title()); } /** * 是否存在注解,若是存在就获取 */ private Log getAnnotationLog(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method != null) { return method.getAnnotation(Log.class); } return null; } }
操做类型的枚举类:数据库
public enum BusinessType { /** * 其它 */ OTHER, /** * 新增 */ INSERT, /** * 修改 */ UPDATE, /** * 删除 */ DELETE, }
使用异步方法将操做日志存库,为了方便我直接使用jdbcTemplate在service中进行存库操做。json
@Service public class AsyncLogService { @Autowired private JdbcTemplate jdbcTemplate; /** * 保存系统日志记录 */ @Async public void saveSysLog(SysOperLog log) { String sql = "INSERT INTO sys_oper_log(title,business_type,method,STATUS,error_msg,oper_time) VALUES(?,?,?,?,?,?)"; jdbcTemplate.update(sql,new Object[]{log.getTitle(),log.getBusinessType(),log.getMethod(),log.getStatus(),log.getErrorMsg(),new Date()}); } }
三 编写接口测试
将自定义注解写在业务方法上,测试效果缓存
@RestController @RequestMapping("person") public class PersonController { @GetMapping("/{name}") @Log(title = "system",businessType = BusinessType.OTHER) public Person getPerson(@PathVariable("name") String name, @RequestParam int age){ return new Person(name,age); } @PostMapping("add") @Log(title = "system",businessType = BusinessType.INSERT) public int addPerson(@RequestBody Person person){ if(StringUtils.isEmpty(person)){ return -1; } return 1; } @PutMapping("update") @Log(title = "system",businessType = BusinessType.UPDATE) public int updatePerson(@RequestBody Person person){ if(StringUtils.isEmpty(person)){ return -1; } return 1; } @DeleteMapping("/{name}") @Log(title = "system",businessType = BusinessType.DELETE) public int deletePerson(@PathVariable(name = "name") String name){ if(StringUtils.isEmpty(name)){ return -1; } return 1; } }
固然,还能够在数据库中将请求参数和响应结果也进行存储,这样就能看出具体接口的操做记录了。微信
本文示例代码已上传至github,点个star
支持一下!
Spring Boot系列教程目录
spring-boot-route(一)Controller接收参数的几种方式
spring-boot-route(二)读取配置文件的几种方式
spring-boot-route(五)整合Swagger生成接口文档
spring-boot-route(六)整合JApiDocs生成接口文档
spring-boot-route(七)整合jdbcTemplate操做数据库
spring-boot-route(八)整合mybatis操做数据库
spring-boot-route(九)整合JPA操做数据库
spring-boot-route(十一)数据库配置信息加密
spring-boot-route(十二)整合redis作为缓存
spring-boot-route(十三)整合RabbitMQ
spring-boot-route(十五)整合RocketMQ
spring-boot-route(十六)使用logback生产日志文件
spring-boot-route(十七)使用aop记录操做日志
spring-boot-route(十八)spring-boot-adtuator监控应用
spring-boot-route(十九)spring-boot-admin监控服务
spring-boot-route(二十)Spring Task实现简单定时任务
spring-boot-route(二十一)quartz实现动态定时任务
spring-boot-route(二十二)实现邮件发送功能
这个系列的文章都是工做中频繁用到的知识,学完这个系列,应付平常开发绰绰有余。若是还想了解其余内容,扫面下方二维码告诉我,我会进一步完善这个系列的文章!