在上一章內容中——使用logback管理日誌,咱們詳細講述瞭如何將日誌生成文件進行存儲。可是在實際開發中,使用文件存儲日誌用來快速查詢問題並非最方便的,一個優秀系統除了日誌文件還須要將操做日誌進行持久化,來監控平臺的操做記錄。今天咱們一塊兒來學習一下如何經過apo來記錄日誌。html
爲了讓記錄日誌更加靈活,咱們將使用自定義的註解來實現重要操做的日誌記錄功能。java
日誌記錄表主要包含幾個字段,業務模塊,操做類型,接口地址,處理狀態,錯誤信息以及操做時間。數據庫設計以下:git
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='操做日誌記錄'
對應的實體類以下:github
@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
。redis
@Target({ ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log { /** * 模塊 */ String title() default ""; /** * 功能 */ BusinessType businessType() default BusinessType.OTHER; }
使用aop對自定義的註解進行處理spring
@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; } }
操做類型的枚舉類:sql
public enum BusinessType { /** * 其它 */ OTHER, /** * 新增 */ INSERT, /** * 修改 */ UPDATE, /** * 刪除 */ DELETE, }
使用異步方法將操做日誌存庫,爲了方便我直接使用jdbcTemplate在service中進行存庫操做。數據庫
@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()}); } }
將自定義註解寫在業務方法上,測試效果json
@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; } }
固然,還能夠在數據庫中將請求參數和響應結果也進行存儲,這樣就能看出具體接口的操做記錄了。緩存
star
支持一下!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(二十二)實現郵件發送功能
這個系列的文章都是工做中頻繁用到的知識,學完這個系列,應付平常開發綽綽有餘。若是還想了解其餘內容,掃面下方二維碼告訴我,我會進一步完善這個系列的文章!