SpringBoot基于AOP的Controller實現(xiàn)統(tǒng)一日志打印的完整方案
在日常后端開發(fā)中,接口問題排查效率低是最常見的痛點之一:很多項目沒有統(tǒng)一的日志規(guī)范,Controller接口缺少請求URL、入?yún)ⅰ㈨憫Y(jié)果、執(zhí)行耗時等核心日志,導致線上報錯、接口異常時無法快速定位問題;如果手動在每個接口中打印日志,又會造成代碼冗余、維護成本高,還容易遺漏。
為了解決這個問題,本文基于Spring AOP實現(xiàn)一套無侵入、可配置、靈活擴展的Controller層統(tǒng)一日志打印方案,自動記錄接口全量信息,同時支持慢接口監(jiān)控告警,完全滿足開發(fā)排查問題的核心需求。
一、方案核心優(yōu)勢
- 無侵入性:無需修改任何Controller業(yè)務代碼,通過切面統(tǒng)一處理日志邏輯
- 全量信息:自動打印接口URL、請求方法、入?yún)?、響應結(jié)果、執(zhí)行耗時
- 安全可靠:過濾Web核心對象,避免序列化異常
- 靈活配置:慢接口閾值可動態(tài)調(diào)整,支持按需開關(guān)/裁剪日志
- 慢接口監(jiān)控:自動識別超時接口,以WARN級別告警,助力性能優(yōu)化
- 統(tǒng)一規(guī)范:所有接口日志格式一致,便于日志檢索和問題排查
二、環(huán)境依賴
項目基于Spring Boot開發(fā),只需引入3個核心依賴(Maven):
<!-- Spring AOP 核心依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- FastJSON 用于參數(shù)序列化 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.32</version>
</dependency>
<!-- Lombok 簡化日志開發(fā) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>三、核心實現(xiàn)代碼
直接創(chuàng)建AOP日志切面類,復制即用,適配絕大多數(shù)Spring Boot項目:
package com.xm.kite.ztc.common.aspect;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
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 javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
/**
* AOP統(tǒng)一日志處理切面
* 自動打印Controller層接口:URL、方法名、入?yún)?、響應結(jié)果、執(zhí)行耗時
* 支持慢接口告警配置
*/
@Slf4j
@Aspect
@Component
public class LoggerHandler {
/**
* 慢接口閾值(單位:毫秒)
* 從配置文件讀取,默認2000ms(2秒)
*/
@Value("${method-logger.elapsed-time:2000}")
private Long loggerHandlerElapsedTimeMs;
/**
* 切點:匹配指定包下所有Controller的所有方法
* 可根據(jù)項目包路徑靈活修改?。。?
*/
@Pointcut("execution(* com.xm.kite.ztc..controller..*.*(..))")
public void allControllerMethods() {
}
/**
* 環(huán)繞通知:包裹目標接口,執(zhí)行前后統(tǒng)一處理日志
*/
@Around("allControllerMethods()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 記錄接口開始時間
long startTime = System.currentTimeMillis();
// 2. 獲取Http請求對象,解析請求URL
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String requestUrl = request.getRequestURL().toString();
// 3. 獲取當前接口的類、方法名
Logger currentLogger = LoggerFactory.getLogger(joinPoint.getSignature().getDeclaringType());
String methodName = joinPoint.getSignature().getName();
// 4. 安全處理入?yún)ⅲ哼^濾HttpServletRequest/Response,避免序列化異常
List<Object> safeArgs = new ArrayList<>();
for (Object arg : joinPoint.getArgs()) {
if (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse) {
safeArgs.add("[" + arg.getClass().getSimpleName() + "]");
} else {
safeArgs.add(arg);
}
}
// 入?yún)⑿蛄谢癁镴SON
String requestArgs = JSONObject.toJSONString(safeArgs);
// 5. 打印【請求入?yún)ⅰ咳罩?
currentLogger.info("請求開始 >>> url: {}, method: {}, args: {}", requestUrl, methodName, requestArgs);
// 6. 執(zhí)行目標接口方法
Object result = joinPoint.proceed();
// 7. 計算接口耗時
long elapsedTime = System.currentTimeMillis() - startTime;
String responseResult = JSONObject.toJSONString(result);
// 8. 分級打印【響應結(jié)果】日志:慢接口WARN告警,正常接口INFO打印
if (elapsedTime > loggerHandlerElapsedTimeMs) {
currentLogger.warn("請求結(jié)束【慢接口告警】>>> 閾值:{}ms, url: {}, method: {}, args: {}, result: {}, 耗時:{}ms",
loggerHandlerElapsedTimeMs, requestUrl, methodName, requestArgs, responseResult, elapsedTime);
} else {
currentLogger.info("請求結(jié)束 >>> url: {}, method: {}, args: {}, result: {}, 耗時:{}ms",
requestUrl, methodName, requestArgs, responseResult, elapsedTime);
}
return result;
}
}
四、靈活配置(核心!支持自定義調(diào)整)
1. 配置慢接口閾值
在application.yml/application.properties中動態(tài)調(diào)整慢接口標準,無需修改代碼:
# 日志配置:慢接口閾值(單位:毫秒) method-logger: elapsed-time: 1500 # 調(diào)整為1.5秒,根據(jù)項目需求修改
2. 靈活調(diào)整打印內(nèi)容(按需改造)
針對不同開發(fā)場景,可快速裁剪/擴展日志打印內(nèi)容,以下是常用擴展方案:
(1)添加請求IP、請求方式
// 獲取請求方式
String requestMethod = request.getMethod();
// 獲取客戶端IP
String clientIp = request.getRemoteAddr();
// 日志中新增打印
currentLogger.info("url: {}, ip: {}, requestType: {}, method: {}, args: {}",
requestUrl, clientIp, requestMethod, methodName, requestArgs);
(2)過濾敏感參數(shù)(密碼、手機號等)
// 改造入?yún)⑦^濾邏輯,隱藏敏感字段
for (Object arg : safeArgs) {
if (arg instanceof UserDTO) {
UserDTO user = (UserDTO) arg;
user.setPassword("***"); // 密碼脫敏
}
}
(3)日志總開關(guān)(按需開啟/關(guān)閉)
// 新增配置開關(guān)
@Value("${method-logger.enabled:true}")
private boolean loggerEnabled;
// 環(huán)繞通知中判斷
@Around("allControllerMethods()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
if (!loggerEnabled) {
return joinPoint.proceed(); // 關(guān)閉日志,直接執(zhí)行接口
}
// 原有日志邏輯...
}
(4)修改切點范圍
// 示例1:只打印指定包下的接口
@Pointcut("execution(* com.xm.kite.ztc.user.controller..*.*(..))")
// 示例2:只打印帶有@GetMapping注解的方法
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
(5)關(guān)閉響應結(jié)果打?。ê喕罩荆?/p>
如果接口返回數(shù)據(jù)過大,可取消響應結(jié)果打?。?/p>
// 直接刪除result參數(shù),簡化日志
currentLogger.info("請求結(jié)束 >>> url: {}, 耗時:{}ms", requestUrl, elapsedTime);
五、代碼核心解析
- 切點定義:
@Pointcut精準匹配項目中所有Controller接口,是AOP日志的生效范圍 - 環(huán)繞通知:
@Around是核心,在接口執(zhí)行前后分別記錄請求、響應信息 - 參數(shù)安全處理:過濾
HttpServletRequest/Response對象,避免JSON序列化失敗 - 日志分級:正常接口用
INFO級別,慢接口用WARN級別告警,便于日志篩選 - 動態(tài)配置:通過
@Value注入配置參數(shù),支持環(huán)境差異化配置
六、日志效果展示
啟動項目后,調(diào)用任意Controller接口,控制臺會打印標準化日志:
# 正常接口
INFO c.x.k.z.t.controller.UserController - 請求開始 >>> url: http://localhost:8080/user/get, method: getUser, args: {"id":1}
INFO c.x.k.z.t.controller.UserController - 請求結(jié)束 >>> url: http://localhost:8080/user/get, method: getUser, args: {"id":1}, result: {"code":200,"data":{"name":"張三"}}, 耗時:12ms
# 慢接口告警
WARN c.x.k.z.t.controller.OrderController - 請求結(jié)束【慢接口告警】>>> 閾值:2000ms, url: http://localhost:8080/order/list, method: getOrderList, args: {"page":1}, result: {...}, 耗時:2560ms
示例:

七、注意事項
- AOP生效:Spring Boot 2.x+引入
spring-boot-starter-aop后,無需額外配置@EnableAspectJAutoProxy,自動開啟AOP - 序列化問題:如果實體類存在循環(huán)引用,F(xiàn)astJSON會自動忽略,無需擔心報錯
- 性能影響:日志打印為輕量級操作,對接口性能無明顯損耗
- 包路徑修改:必須將切點中的包路徑
com.xm.kite.ztc改為自己項目的實際包路徑,否則日志不生效
總結(jié)
這套基于Spring AOP的統(tǒng)一日志方案,徹底解決了接口排查無日志、手動打印日志冗余的問題,同時具備極高的靈活性:開發(fā)人員可根據(jù)需求自由調(diào)整打印內(nèi)容、開關(guān)日志、配置慢接口閾值。
方案無侵入、易擴展、標準化,完全適配企業(yè)級Spring Boot項目,大幅提升線上問題排查效率和接口性能監(jiān)控能力。
到此這篇關(guān)于SpringBoot基于AOP的Controller實現(xiàn)統(tǒng)一日志打印的完整方案的文章就介紹到這了,更多相關(guān)SpringBoot AOP打印日志內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
本地啟動RocketMQ未映射主機名產(chǎn)生的超時問題最新解決方案
這篇文章主要介紹了本地啟動RocketMQ未映射主機名產(chǎn)生的超時問題,本文給大家分享最新解決方案,感興趣的朋友跟隨小編一起看看吧2024-02-02
springboot2如何集成ElasticSearch6.4.3
這篇文章主要介紹了springboot2如何集成ElasticSearch6.4.3問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07
java 使用簡單的demo實例告訴你優(yōu)化算法的強大
本篇文章介紹了,在java中使用簡單的demo實例告訴你優(yōu)化算法的強大。需要的朋友參考下2013-05-05
創(chuàng)建動態(tài)代理對象bean,并動態(tài)注入到spring容器中的操作
這篇文章主要介紹了創(chuàng)建動態(tài)代理對象bean,并動態(tài)注入到spring容器中的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02
用Rational Rose逆向工程(java)生成類圖(教程和錯誤解決)
Rational Rose有個很方便的功能,將項目中的JAVA代碼自動轉(zhuǎn)換成UML類圖2013-02-02
Sharding JDBC讀寫分離實現(xiàn)原理及實例
這篇文章主要介紹了Sharding JDBC讀寫分離實現(xiàn)原理及實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-12-12

