Springboot使用@Aspect、自定義注解記錄日志方式
1、前言
日志的作用不言而喻,協(xié)助運(yùn)維故障排查,問(wèn)題分析,數(shù)據(jù)統(tǒng)計(jì),記錄查詢等。
- 故障排查:通過(guò)日志可對(duì)系統(tǒng)進(jìn)行實(shí)時(shí)健康度監(jiān)控,系統(tǒng)日志記錄程序 Syslog 就是為這個(gè)目的而設(shè)計(jì)的。
- 數(shù)據(jù)分析:通過(guò)對(duì)業(yè)務(wù)系統(tǒng)日志進(jìn)行關(guān)聯(lián)分析,可以掌握業(yè)務(wù)系統(tǒng)的整體運(yùn)行情況,并可通過(guò)日志進(jìn)一步掌握用戶畫像、用戶訪問(wèn)地域、用戶訪問(wèn)熱點(diǎn)資源等信息,從而為業(yè)務(wù)平臺(tái)的市場(chǎng)營(yíng)銷、銷售策略等提供數(shù)據(jù)支撐。
- 安全合規(guī)審計(jì):根據(jù)國(guó)家網(wǎng)絡(luò)安全法等級(jí)保護(hù)要求,需要對(duì)安全設(shè)備日志進(jìn)行集中存儲(chǔ)和分析。
- 內(nèi)網(wǎng)安全監(jiān)控:很多企業(yè)的信息泄露源于內(nèi)部,使用日志進(jìn)行用戶行為分析以監(jiān)控內(nèi)網(wǎng)安全,已成為行業(yè)共識(shí)。
- 智能運(yùn)維:隨著大數(shù)據(jù)時(shí)代的到來(lái),數(shù)據(jù)管理和分析方案越來(lái)越智能,自動(dòng)化運(yùn)維已逐漸普及。機(jī)器數(shù)據(jù)作為智能運(yùn)維的基礎(chǔ)數(shù)據(jù),必將發(fā)揮越來(lái)越重要的作用。
日志各位大佬記錄都會(huì),但是規(guī)范有效記錄日志的很少,本文為工具類文章,開(kāi)箱即用,直接導(dǎo)入項(xiàng)目即可,CV大法一鍵搞定
2、切面方法說(shuō)明
@Aspect-- 作用是把當(dāng)前類標(biāo)識(shí)為一個(gè)切面供容器讀取@Pointcut-- (切入點(diǎn)):就是帶有通知的連接點(diǎn),在程序中主要體現(xiàn)為書寫切入點(diǎn)表達(dá)式@Before-- 標(biāo)識(shí)一個(gè)前置增強(qiáng)方法,相當(dāng)于BeforeAdvice的功能@AfterReturning-- 后置增強(qiáng),相當(dāng)于AfterReturningAdvice,方法退出時(shí)執(zhí)行@AfterThrowing-- 異常拋出增強(qiáng),相當(dāng)于ThrowsAdvice@After-- final增強(qiáng),不管是拋出異?;蛘哒M顺龆紩?huì)執(zhí)行@Around-- 環(huán)繞增強(qiáng),相當(dāng)于MethodInterceptor
AOP五種通知工作
- 前置通知:在目標(biāo)方法調(diào)用之前執(zhí)行,可以獲得切入點(diǎn)信息;
- 后置通知:在目標(biāo)方法執(zhí)行后執(zhí)行,目標(biāo)方法有異常不執(zhí)行;
- 異常通知:在目標(biāo)方法拋出異常時(shí)執(zhí)行,可以獲取異常信息;
- 最終通知:在目標(biāo)方法執(zhí)行后執(zhí)行,無(wú)論是否有異常都執(zhí)行;
- 環(huán)繞通知:最強(qiáng)大的通知類型,在目標(biāo)方法執(zhí)行前后操作,可以阻止目標(biāo)方法執(zhí)行。
3、AOP日志記錄方式
aop解決的這個(gè)辦法有很多種,這里介紹兩種最簡(jiǎn)單、最常用的
- 掃描包的方式。傳入的參數(shù)在請(qǐng)求頭里面(企業(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 包下定義的所有請(qǐng)求為切入點(diǎn) */
@Pointcut("execution(public * com.police.violation.controller..*.*Ticket(..))")
public void webLog() {
}
/**
* 在切點(diǎn)之前織入
*
* @param joinPoint
* @throws Throwable
*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 開(kāi)始打印請(qǐng)求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 打印請(qǐng)求相關(guān)參數(shù)
logger.info("========================================== Start ==========================================");
// 打印請(qǐng)求 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());
// 打印請(qǐng)求的 IP
logger.info("IP : {}", request.getRemoteAddr());
// 打印請(qǐng)求入?yún)?
logger.info("Request Args : {}", new Gson().toJson(joinPoint.getArgs()));
}
/**
* 在切點(diǎn)之后織入
*
* @throws Throwable
*/
@After("webLog()")
public void doAfter() throws Throwable {
logger.info("=========================================== End ===========================================");
// 每個(gè)請(qǐng)求之間空一行
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í)行耗時(shí)
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];
// 如果是文件對(duì)象
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)計(jì)耗時(shí)
*/
@Documented //用于描述其它類型的annotation應(yīng)該被作為被標(biāo)注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化.Documented是一個(gè)標(biāo)記注解,沒(méi)有成員.
@Target(ElementType.METHOD) //指定被修飾的Annotation可以放置的位置(被修飾的目標(biāo))類,方法,屬性
@Retention(RetentionPolicy.RUNTIME) //定義注解的保留策略, RetentionPolicy.RUNTIME:注解會(huì)在class字節(jié)碼文件中存在,在運(yùn)行時(shí)可以通過(guò)反射獲取到
public @interface TakeTime {
String methodName() default "";
}3.2.3. TakeTimeAspect(使用AOP技術(shù)統(tǒng)計(jì)方法執(zhí)行前后消耗時(shí)間)
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;
/**
* 耗時(shí)統(tǒng)計(jì)
*/
@Slf4j
@Aspect
@Component
public class TakeTimeAspect {
//統(tǒng)計(jì)請(qǐng)求的處理時(shí)間
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("進(jìn)入《{}》 方法,參數(shù)為: {}", methodName,sb.toString());
System.out.println("System.currentTimeMillis(): "+System.currentTimeMillis());
System.out.println("new Date(): "+new Date());
startTime.set(System.currentTimeMillis());
log.info("方法開(kāi)始時(shí)間:" +startTime.get());
//接收到請(qǐng)求,記錄請(qǐng)求內(nèi)容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//記錄請(qǐng)求的內(nèi)容
log.info("請(qǐng)求URL:" + request.getRequestURL().toString());
log.info("請(qǐng)求METHOD:" + request.getMethod());
}
// @AfterReturning(returning = "ret", pointcut = "within(com.lwx.backend.user.controller.*)")
@AfterReturning(returning = "ret", pointcut = "TakeTime()")
public void doAfterReturning(Object ret) {
//處理完請(qǐng)求后,返回內(nèi)容
log.info("方法返回值:" + JSON.toJSONString(ret));
endTime.set(System.currentTimeMillis());
log.info("方法結(jié)束時(shí)間" +endTime.get());
log.info("方法結(jié)束時(shí)間" +new Date());
log.info("方法執(zhí)行時(shí)間:" + (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. 打印查看接口耗時(shí)
2022-07-30 22:40:47.057 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 進(jìn)入《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 : 方法開(kāi)始時(shí)間:1659192047058
2022-07-30 22:40:47.059 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 請(qǐng)求URL:http://localhost:8080/user/queryUserList
2022-07-30 22:40:47.059 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 請(qǐng)求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é)束時(shí)間1659192047846
2022-07-30 22:40:47.846 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 方法結(jié)束時(shí)間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í)行時(shí)間:788
注:
- 如果接口引用的注解失效可能是配置注解的類是在別的包下面
- 需要在啟動(dòng)類上設(shè)置掃描注解類的包
@SpringBootApplication(scanBasePackages {"com.Lwx.backend","com.Lwx.common"})總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- springboot使用logback自定義日志的詳細(xì)過(guò)程
- SpringBoot使用TraceId進(jìn)行日志鏈路追蹤的實(shí)現(xiàn)步驟
- springboot項(xiàng)目使用nohup將日志指定輸出文件過(guò)大問(wèn)題及解決辦法
- Spring Boot項(xiàng)目中如何對(duì)接口請(qǐng)求參數(shù)打印日志
- SpringBoot使用@Slf4j注解實(shí)現(xiàn)日志輸出的示例代碼
- SpringBoot多環(huán)境日志配置方式
- Springboot日志配置的實(shí)現(xiàn)示例
- Spring Boot 日志功能深度解析與實(shí)踐指南
相關(guān)文章
基于newFixedThreadPool實(shí)現(xiàn)多線程案例
這篇文章主要介紹了基于newFixedThreadPool實(shí)現(xiàn)多線程案例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11
Java實(shí)現(xiàn)File轉(zhuǎn)換MultipartFile格式的例子
本文主要介紹了Java實(shí)現(xiàn)File轉(zhuǎn)換MultipartFile格式的例子,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
詳解Java?二叉樹(shù)的實(shí)現(xiàn)和遍歷
二叉樹(shù)可以簡(jiǎn)單理解為對(duì)于一個(gè)節(jié)點(diǎn)來(lái)說(shuō),最多擁有一個(gè)上級(jí)節(jié)點(diǎn),同時(shí)最多具備左右兩個(gè)下級(jí)節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)。本文將詳細(xì)介紹一下Java中二叉樹(shù)的實(shí)現(xiàn)和遍歷,需要的可以參考一下2022-01-01
Java中實(shí)現(xiàn)日志記錄的方案總結(jié)
在平時(shí)使用到一些軟件中,比如某寶或者某書,通過(guò)記錄用戶的行為來(lái)構(gòu)建和分析用戶的行為數(shù)據(jù),這就需要使用到日志系統(tǒng)來(lái)存儲(chǔ)或者記錄數(shù)據(jù),小編為大家整理了幾種Java日志方案,希望對(duì)大家有所幫助2024-12-12
Spring實(shí)例化對(duì)象的幾種常見(jiàn)方式
Spring框架作為一個(gè)輕量級(jí)的控制反轉(zhuǎn)容器,為開(kāi)發(fā)者提供了多種對(duì)象實(shí)例化的策略,通過(guò)這些策略,開(kāi)發(fā)者可以更加靈活地控制對(duì)象的生命周期和依賴關(guān)系,無(wú)論是通過(guò)XML配置、注解配置還是Java配置,Spring都能輕松地實(shí)現(xiàn)對(duì)象的實(shí)例化,本文將介紹Spring實(shí)例化對(duì)象的幾種常見(jiàn)方式2024-12-12
Java中反射動(dòng)態(tài)代理接口的詳解及實(shí)例
這篇文章主要介紹了Java中反射動(dòng)態(tài)代理接口的詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-04-04

