1. 程式人生 > 實用技巧 >基於AOP和ThreadLocal實現日誌記錄

基於AOP和ThreadLocal實現日誌記錄

基於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": "[email protected]",
"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), 有時間會一直維護和優化。