前言
在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等分布式日志解決方法,好吧,這些還有待於自己部署環境去嘗試。
