基於AOP和ThreadLocal實現的一個日誌記錄的例子
主要功能實現 : 在API每次被請求時,可以在整個方法呼叫鏈路中記錄一條唯一的API請求日誌,可以記錄請求中絕大部分關鍵內容。並且可以自定義實現對日誌收集(直接標準輸出,或寫入到檔案或資料庫)。
比如傳參,響應,請求url,請求方法,clientIp,耗時,請求成功或異常,請求頭等等。
實現的核心為AOP以及ThreadLocal。
- AOP 會切所有被
@Log4a
註解的方法,會記錄一個執行緒中唯一一個Log4物件,讀取AOP中的方法資訊(入參,方法等等) - 抓取請求的內容和HttpServletRequest中的內容,解析入參。
- 日誌收集(自定義實現,建議該過程非同步)
- 記錄無論目標方法成功或失敗,在執行完成後都將對ThreadLocal中的資源進行釋放。
Log4 記錄的內容
欄位 | 型別 | 註釋 | 是否預設記錄 |
---|---|---|---|
clientIp | String | 請求客戶端的Ip | 是 |
reqUrl | String | 請求地址 | 是 |
headers | Object | 請求頭部資訊(可選擇記錄) | 是,預設記錄user-agent,content-type |
type | String | 操作型別 | 是,預設值undefined |
content | StringBuilder | 步驟內容資訊 | 否,方法內容,可使用Log4.step進行內容步驟記錄 |
Log4a 註解選項說明
欄位 | 型別 | 註釋 | 預設 |
---|---|---|---|
type | String | 操作型別 | 預設值"undefined" |
method | boolean | 是否記錄請求的本地java方法 | true |
costTime | boolean | 是否記錄整個方法耗時 | true |
headers | String[] | 記錄的header資訊 | 預設"User-Agent","content-type" |
args | boolean | 是否記錄請求引數 | true |
respBody | boolean | 是否記錄響應引數 | true |
stackTrace | boolean | 當目標方法發生異常時,是否追加異常堆疊資訊到content | false |
costTime | boolean | 是否記錄整個方法耗時 | true |
collector | Class<? extends LogCollector> | 指定日誌收集器 | 預設空的收集器不指定 |
例子使用說明
@Log4a註解使用
直接在Controller 方法或類上加上註解@Log4a
,可以對該Controller中所有方法進行日誌記錄與收集
例如 :
@Log4a(type = "測試API", stackTrace = true)
@RestController
public class DemoController {
@Resource
private DemoService demoService;
/**
* JSON資料測試
*/
@PostMapping("/sayHello")
public ResponseEntity<?> sayHello(@RequestBody Map<String, Object> request) {
demoService.sayHello(request);
return ResponseEntity.ok(request);
}
/**
* RequestParam 引數測試
*/
@PostMapping("/params")
public ResponseEntity<?> params(@RequestParam Integer a) {
return ResponseEntity.ok(a);
}
/**
* 無參測試
*/
@GetMapping("/noArgs")
public ResponseEntity<?> noArgs() {
return ResponseEntity.ok().build();
}
/**
* XML 格式資料測試
*/
@PostMapping(value = "/callXml", consumes = {MediaType.APPLICATION_XML_VALUE})
public XmlDataDTO callXml(@RequestBody XmlDataDTO dataDTO) {
return dataDTO;
}
/**
* 特殊物件測試
*/
@GetMapping("/callHttpServletRequest")
public ResponseEntity<?> callHttpServletRequest(HttpServletRequest request) {
return ResponseEntity.ok().build();
}
}
Log4.step 記錄詳細步驟內容
這裡呼叫了service方法,Log4.step 方法記錄每一個步驟詳細內容
/**
* @author EalenXie Created on 2020/1/16 10:49.
*/
@Service
@Slf4j
public class DemoService {
/**
* 測試方法, 使用Log4.step記錄步驟
*/
public void sayHello(Map<String, Object> words) {
Log4.step("1. 請求來了,執行業務動作");
log.info("do somethings");
Log4.step("2. 業務動作執行完成");
}
}
自定義的全域性日誌收集器
本例中寫了一個最簡單的直接append寫入到檔案中,你可以選擇自定義的方式進行日誌收集(例如寫入到資料庫或者日誌檔案,或日誌收集框架中,這個過程建議非同步處理,可在collect方法上面加入註解@Async
)
@Component
public class DemoLogCollector implements LogCollector {
@Override
public void collect(Log4 log4) throws LogCollectException {
try {
File file = new File("D:\\home\\temp\\日誌.txt");
if (!file.getParentFile().exists()) {
FileUtils.forceMkdir(file.getParentFile());
}
try (FileWriter fw = new FileWriter(file, true)) {
fw.append(log4.toString());
}
} catch (IOException e) {
throw new LogCollectException(e);
}
}
}
測試後 , 可以從 D:\home\temp\日誌.txt中獲取到記錄的日誌內容。
json格式的資料記錄(引數JSON):
{
"args": {
"id": 999,
"value": "content"
},
"clientIp": "192.168.1.54",
"content": "1. 請求來了,執行業務動作\n2. 業務動作執行完成\n",
"costTime": 2,
"headers": {
"User-Agent": "Apache-HttpClient/4.5.10 (Java/11.0.5)",
"Content-Type": "application/json"
},
"logDate": 1593341797293,
"method": "name.ealen.demo.controller.DemoController#sayHello",
"reqUrl": "http://localhost:9527/sayHello",
"respBody": {
"headers": {},
"statusCodeValue": 200,
"body": {
"id": 999,
"value": "content"
},
"statusCode": "OK"
},
"success": true,
"type": "測試API"
}
XML格式的資料(引數XML):
{
"args": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><xml><message>1111 </message><username>zhangsan</username></xml>",
"clientIp": "192.168.1.54",
"content": "",
"costTime": 4,
"headers": {
"User-Agent": "Apache-HttpClient/4.5.10 (Java/11.0.5)",
"Content-Type": "application/xml"
},
"logDate": 1593394523000,
"method": "name.ealen.demo.controller.DemoController#callXml",
"reqUrl": "http://localhost:9527/callXml",
"respBody": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><xml><message>1111 </message><username>zhangsan</username></xml>",
"success": true,
"type": "測試API"
}
form引數格式的資料(以引數鍵值對形式):
{
"args": "z=11&a=1",
"clientIp": "192.168.1.54",
"content": "",
"costTime": 1,
"headers": {
"User-Agent": "Apache-HttpClient/4.5.10 (Java/11.0.5)",
"Content-Type": "application/x-www-form-urlencoded"
},
"logDate": 1593342114342,
"method": "name.ealen.demo.controller.DemoController#params",
"reqUrl": "http://localhost:9527/params",
"respBody": {
"headers": {},
"statusCodeValue": 200,
"body": 1,
"statusCode": "OK"
},
"success": true,
"type": "測試API"
}
特殊引數格式(目前暫為鍵值對形式,引數預設取物件的toString()方法):
{
"args": "request=org.apache.catalina.connector.RequestFacade@754f30c3",
"clientIp": "192.168.1.54",
"content": "",
"costTime": 1,
"headers": {
"User-Agent": "Apache-HttpClient/4.5.10 (Java/11.0.5)"
},
"logDate": 1593342220880,
"method": "name.ealen.demo.controller.DemoController#callHttpServletRequest",
"reqUrl": "http://localhost:9527/callHttpServletRequest",
"respBody": {
"headers": {},
"statusCodeValue": 200,
"body": null,
"statusCode": "OK"
},
"success": true,
"type": "測試API"
}
Github專案地址 :https://github.com/EalenXie/Log4a
目前暫時專案命名為Log4a(Log for API), 有時間會一直維護和優化。