前言
在SpringBoot中使用自定義註解、aop切面列印web請求日誌。主要是想把controller的每個request請求日誌收集起來,呼叫介面、執行時間、返回值這幾個重要的資訊儲存到資料庫裡,然後可以使用火焰圖統計介面呼叫時長,平均響應時長,以便於我們對介面的呼叫和執行情況及時掌握。新增依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.1.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.1.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.1.4.RELEASE</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.5</version> </dependency>
自定義註解
import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented public @interface WebLogger { String value() default ""; }
AOP定義切面、切點
在實現切面之前先要了解幾個aop的註解:
- @Aspect、
- @Pointcut 定義切點,後面跟隨一個表示式,表示式可以是一個註解,也可以具體到方法級別。
- @Before
- @After
- @Around
實現切面,在before方法裡統計request請求相關引數,在around方法裡計算介面呼叫時間。這裡統計請求的引數時有個bug,joinpoint.getArgs返回值是一個Object陣列,但是陣列裡只有引數值,沒有引數名。還沒找到解決辦法。
@Aspect @Component public class WebLoggerAspect { @Pointcut("@annotation(com.zhangfei.anno.WebLogger)" public void log() {} @Around("log()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { long startTime=System.currentTimeMillis(); Object result=joinPoint.proceed(); System.out.println("Response:"+new Gson().toJson(result)); System.out.println("耗時:"+(System.currentTimeMillis()-startTime)); return result; } @Before("log()") public void doBefore(JoinPoint joinPoint) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); System.out.println("==================Start================="); System.out.println("URL:" + request.getRequestURL().toString()); System.out.println("Description:" + getLogValue(joinPoint)); System.out.println("Method:" + request.getMethod().toString()); //列印controller全路徑及method System.out.println("Class Method:" + joinPoint.getSignature().getDeclaringTypeName() + "," + joinPoint.getSignature().getName()); System.out.println("客戶端IP:" + request.getRemoteAddr()); System.out.println("請求引數:" + new Gson().toJson(joinPoint.getArgs())); } private String getLogValue(JoinPoint joinPoint) { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); WebLogger webLogger = method.getAnnotation(WebLogger.class); return webLogger.value(); } @After("log()") public void doAfter() { System.out.println("==================End================="); } }
怎麼使用註解?
這裡就直接在你的web請求方法上直接新增WebLogger註解
@Controller @RequestMapping("/student") public class StudentController { private final Logger logger= LoggerFactory.getLogger(StudentController.class); @GetMapping("/list") @WebLogger("學生列表") public @ResponseBody List<Student> list(){ ArrayList<Student> list=new ArrayList<>(); Student student0=new Student(1,"kobe",30); Student student1=new Student(2,"james",30); Student student2=new Student(3,"rose",30); list.add(student0); list.add(student1); list.add(student2); return list; } @WebLogger("學生實體") @RequestMapping("/detail") public @ResponseBody Student student(int id){ return new Student(1,"kobe",30); } }
切面日誌輸出效果
總結
從頭條上一位朋友文章評論上看到有人提出,在分散式部署的環境中,出現高併發時日誌列印會出現錯亂的情況,這裡還需要把執行緒id或者宣告一個guid, 存入ThreadLoal中統計使用。當然更多的人簡歷還是使用ELK等分散式日誌解決方法,好吧,這些還有待於自己部署環境去嘗試。