Springboot使用@Aspect、自定義注解記錄日志方式
1、前言
日志的作用不言而喻,協(xié)助運維故障排查,問題分析,數(shù)據(jù)統(tǒng)計,記錄查詢等。
- 故障排查:通過日志可對系統(tǒng)進行實時健康度監(jiān)控,系統(tǒng)日志記錄程序 Syslog 就是為這個目的而設(shè)計的。
- 數(shù)據(jù)分析:通過對業(yè)務(wù)系統(tǒng)日志進行關(guān)聯(lián)分析,可以掌握業(yè)務(wù)系統(tǒng)的整體運行情況,并可通過日志進一步掌握用戶畫像、用戶訪問地域、用戶訪問熱點資源等信息,從而為業(yè)務(wù)平臺的市場營銷、銷售策略等提供數(shù)據(jù)支撐。
- 安全合規(guī)審計:根據(jù)國家網(wǎng)絡(luò)安全法等級保護要求,需要對安全設(shè)備日志進行集中存儲和分析。
- 內(nèi)網(wǎng)安全監(jiān)控:很多企業(yè)的信息泄露源于內(nèi)部,使用日志進行用戶行為分析以監(jiān)控內(nèi)網(wǎng)安全,已成為行業(yè)共識。
- 智能運維:隨著大數(shù)據(jù)時代的到來,數(shù)據(jù)管理和分析方案越來越智能,自動化運維已逐漸普及。機器數(shù)據(jù)作為智能運維的基礎(chǔ)數(shù)據(jù),必將發(fā)揮越來越重要的作用。
日志各位大佬記錄都會,但是規(guī)范有效記錄日志的很少,本文為工具類文章,開箱即用,直接導入項目即可,CV大法一鍵搞定
2、切面方法說明
@Aspect
-- 作用是把當前類標識為一個切面供容器讀取@Pointcut
-- (切入點):就是帶有通知的連接點,在程序中主要體現(xiàn)為書寫切入點表達式@Before
-- 標識一個前置增強方法,相當于BeforeAdvice的功能@AfterReturning
-- 后置增強,相當于AfterReturningAdvice,方法退出時執(zhí)行@AfterThrowing
-- 異常拋出增強,相當于ThrowsAdvice@After
-- final增強,不管是拋出異?;蛘哒M顺龆紩?zhí)行@Around
-- 環(huán)繞增強,相當于MethodInterceptor
AOP五種通知工作
- 前置通知:在目標方法調(diào)用之前執(zhí)行,可以獲得切入點信息;
- 后置通知:在目標方法執(zhí)行后執(zhí)行,目標方法有異常不執(zhí)行;
- 異常通知:在目標方法拋出異常時執(zhí)行,可以獲取異常信息;
- 最終通知:在目標方法執(zhí)行后執(zhí)行,無論是否有異常都執(zhí)行;
- 環(huán)繞通知:最強大的通知類型,在目標方法執(zhí)行前后操作,可以阻止目標方法執(zhí)行。
3、AOP日志記錄方式
aop解決的這個辦法有很多種,這里介紹兩種最簡單、最常用的
- 掃描包的方式。傳入的參數(shù)在請求頭里面(企業(yè)常用)
- 自定義注解
3.1、掃描包的方式
1)添加依賴
<--AOP的依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <--JSON的依賴--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.79</version> </dependency> <--mysql-data-jpa的依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <--lombok的依賴--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
/** * Copyright ? 2022 eSunny Info. Tech Ltd. All rights reserved. * 功能描述: * @Title: WebLogAspect.java * @Package com.police.violation.aop * @Description: TODO * @author Administrator * @date 2022年8月31日 上午9:48:11 * @version */ package com.police.violation.aop; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.multipart.MultipartFile; import com.google.gson.Gson; /** * @author www.exception.site (exception 教程網(wǎng)) * @date 2019/2/12 * @time 14:03 * @discription **/ @Aspect @Component public class WebLogAspect { private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class); /** 以 controller 包下定義的所有請求為切入點 */ @Pointcut("execution(public * com.police.violation.controller..*.*Ticket(..))") public void webLog() { } /** * 在切點之前織入 * * @param joinPoint * @throws Throwable */ @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 開始打印請求日志 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 打印請求相關(guān)參數(shù) logger.info("========================================== Start =========================================="); // 打印請求 url logger.info("URL : {}", request.getRequestURL().toString()); // 打印 Http method logger.info("HTTP Method : {}", request.getMethod()); // 打印調(diào)用 controller 的全路徑以及執(zhí)行方法 logger.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); // 打印請求的 IP logger.info("IP : {}", request.getRemoteAddr()); // 打印請求入?yún)? logger.info("Request Args : {}", new Gson().toJson(joinPoint.getArgs())); } /** * 在切點之后織入 * * @throws Throwable */ @After("webLog()") public void doAfter() throws Throwable { logger.info("=========================================== End ==========================================="); // 每個請求之間空一行 logger.info(""); } /** * 環(huán)繞 * * @param proceedingJoinPoint * @return * @throws Throwable */ @Around("webLog()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object result = proceedingJoinPoint.proceed(); // 打印出參 logger.info("Response Args : {}", new Gson().toJson(result)); // 執(zhí)行耗時 logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime); return result; } /** * * @param proceedingJoinPoint 切面 * @return * @throws Throwable */ // @Around("webLog()") // public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // long start = System.currentTimeMillis(); // Object result = proceedingJoinPoint.proceed(); // logger.info("Request Params : {}", getRequestParams(proceedingJoinPoint)); // logger.info("Result : {}", result); // logger.info("Time Cost : {} ms", System.currentTimeMillis() - start); // // return result; // } /** * 獲取入?yún)? * * @param proceedingJoinPoint * * @return */ private Map<String, Object> getRequestParams(ProceedingJoinPoint proceedingJoinPoint) { Map<String, Object> requestParams = new HashMap<>(); // 參數(shù)名 String[] paramNames = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterNames(); // 參數(shù)值 Object[] paramValues = proceedingJoinPoint.getArgs(); for (int i = 0; i < paramNames.length; i++) { Object value = paramValues[i]; // 如果是文件對象 if (value instanceof MultipartFile) { MultipartFile file = (MultipartFile) value; // 獲取文件名 value = file.getOriginalFilename(); } requestParams.put(paramNames[i], value); } return requestParams; } }
3.2、自定義注解方式
3.2.1.Maven依賴
<!--引入AOP依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
3.2.2. 自定義注解
package com.ruoyi.aspect; import java.lang.annotation.*; /** * 統(tǒng)計耗時 */ @Documented //用于描述其它類型的annotation應(yīng)該被作為被標注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化.Documented是一個標記注解,沒有成員. @Target(ElementType.METHOD) //指定被修飾的Annotation可以放置的位置(被修飾的目標)類,方法,屬性 @Retention(RetentionPolicy.RUNTIME) //定義注解的保留策略, RetentionPolicy.RUNTIME:注解會在class字節(jié)碼文件中存在,在運行時可以通過反射獲取到 public @interface TakeTime { String methodName() default ""; }
3.2.3. TakeTimeAspect(使用AOP技術(shù)統(tǒng)計方法執(zhí)行前后消耗時間)
package com.ruoyi.aspect; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Date; /** * 耗時統(tǒng)計 */ @Slf4j @Aspect @Component public class TakeTimeAspect { //統(tǒng)計請求的處理時間 ThreadLocal<Long> startTime = new ThreadLocal<>(); ThreadLocal<Long> endTime = new ThreadLocal<>(); /** * 帶有@TakeTime注解的方法 */ // @Pointcut("within(com.lwx.backend.user.controller.*)") // @Pointcut("execution(* com.lwx.backend.user.controller.*.*(..))") @Pointcut("@annotation(com.ruoyi.aspect.TakeTime)") public void TakeTime() { } // @Before("within(com.lwx.backend.user.controller.*)") @Before("TakeTime()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 獲取方法的名稱 String methodName = joinPoint.getSignature().getName(); // 獲取方法入?yún)? Object[] param = joinPoint.getArgs(); StringBuilder sb = new StringBuilder(); for (Object o : param) { sb.append(o + ";"); } log.info("進入《{}》 方法,參數(shù)為: {}", methodName,sb.toString()); System.out.println("System.currentTimeMillis(): "+System.currentTimeMillis()); System.out.println("new Date(): "+new Date()); startTime.set(System.currentTimeMillis()); log.info("方法開始時間:" +startTime.get()); //接收到請求,記錄請求內(nèi)容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); //記錄請求的內(nèi)容 log.info("請求URL:" + request.getRequestURL().toString()); log.info("請求METHOD:" + request.getMethod()); } // @AfterReturning(returning = "ret", pointcut = "within(com.lwx.backend.user.controller.*)") @AfterReturning(returning = "ret", pointcut = "TakeTime()") public void doAfterReturning(Object ret) { //處理完請求后,返回內(nèi)容 log.info("方法返回值:" + JSON.toJSONString(ret)); endTime.set(System.currentTimeMillis()); log.info("方法結(jié)束時間" +endTime.get()); log.info("方法結(jié)束時間" +new Date()); log.info("方法執(zhí)行時間:" + (endTime.get() - startTime.get())); } }
3.2.4. 在接口方法上加上注解
@RequestMapping("/loadForTestVariableCategories") @TakeTime(methodName = "loadForTestVariableCategories") public void loadForTestVariableCategories(HttpServletRequest req, HttpServletResponse resp) throws Exception { KnowledgeBase knowledgeBase = buildKnowledgeBase(req); List<VariableCategory> vcs=knowledgeBase.getResourceLibrary().getVariableCategories(); httpSessionKnowledgeCache.put(req, RuleConstant.VCS_KEY, vcs); writeObjectToJson(resp, vcs); }
3.2.5. 打印查看接口耗時
2022-07-30 22:40:47.057 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 進入《queryUserList》 方法,參數(shù)為: {pageNum=1, pageSize=10, userName=張三};
System.currentTimeMillis(): 1659192047058
new Date(): Sat Jul 30 22:40:47 CST 2022
2022-07-30 22:40:47.058 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 方法開始時間:1659192047058
2022-07-30 22:40:47.059 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 請求URL:http://localhost:8080/user/queryUserList
2022-07-30 22:40:47.059 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 請求METHOD:POST
com.lwx.common.aspect.TakeTimeAspect : 方法返回值:{"data":{"endRow":1,"hasNextPage":false,"hasPreviousPage":false,"isFirstPage":true,"isLastPage":true,"list":[{"comment":"男","userAddress":"廣東","userAge":20,"userBirth":1640966400000,"userId":1,"userName":"張三","userSex":"男"}],"navigateFirstPage":1,"navigateLastPage":1,"navigatePages":8,"navigatepageNums":[1],"nextPage":0,"pageNum":1,"pageSize":10,"pages":1,"prePage":0,"size":1,"startRow":1,"total":1},"message":"success","status":100,"timestamp":1659192047819}
2022-07-30 22:40:47.846 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 方法結(jié)束時間1659192047846
2022-07-30 22:40:47.846 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 方法結(jié)束時間Sat Jul 30 22:40:47 CST 2022
2022-07-30 22:40:47.846 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 方法執(zhí)行時間:788
注:
- 如果接口引用的注解失效可能是配置注解的類是在別的包下面
- 需要在啟動類上設(shè)置掃描注解類的包
@SpringBootApplication(scanBasePackages {"com.Lwx.backend","com.Lwx.common"})
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
基于newFixedThreadPool實現(xiàn)多線程案例
這篇文章主要介紹了基于newFixedThreadPool實現(xiàn)多線程案例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-11-11Java實現(xiàn)File轉(zhuǎn)換MultipartFile格式的例子
本文主要介紹了Java實現(xiàn)File轉(zhuǎn)換MultipartFile格式的例子,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07