基于AOP和ThreadLocal實現(xiàn)日志記錄

基于AOP和ThreadLocal實現(xiàn)的一個日志記錄的例子

主要功能實現(xiàn) : 在API每次被請求時,可以在整個方法調(diào)用鏈路中記錄一條唯一的API請求日志,可以記錄請求中絕大部分關(guān)鍵內(nèi)容。并且可以自定義實現(xiàn)對日志收集(直接標準輸出,或?qū)懭氲轿募驍?shù)據(jù)庫)。

比如傳參,響應,請求url,請求方法,clientIp,耗時,請求成功或異常,請求頭等等。

實現(xiàn)的核心為AOP以及ThreadLocal。

  • AOP 會切所有被<font color='bule'>@AopLog</font >注解的方法,會記錄一個線程中唯一一個<font color='bule'>LogData</font>對象,讀取AOP中的方法信息(入?yún)?,方法等?
  • 抓取請求的內(nèi)容和HttpServletRequest中的內(nèi)容,解析入?yún)ⅰ?/li>
  • 日志收集(自定義實現(xiàn),建議該過程異步)
  • 記錄無論目標方法成功或失敗,在執(zhí)行完成后都將對ThreadLocal中的資源進行釋放。

LogData記錄的內(nèi)容

字段 類型 注釋 是否默認記錄
clientIp String 請求客戶端的Ip
reqUrl String 請求地址
headers Object 請求頭部信息(可選擇記錄) 是,默認記錄user-agent,content-type
type String 操作類型 是,默認值undefined
content StringBuilder 步驟內(nèi)容信息 否,方法內(nèi)容,可使用LogData.step進行內(nèi)容步驟記錄

AopLog注解選項說明

字段 類型 注釋 默認
type String 操作類型 默認值"undefined"
method boolean 是否記錄請求的本地java方法 true
costTime boolean 是否記錄整個方法耗時 true
headers String[] 記錄的header信息 默認"User-Agent","content-type"
args boolean 是否記錄請求參數(shù) true
respBody boolean 是否記錄響應參數(shù) true
stackTrace boolean 當目標方法發(fā)生異常時,是否追加異常堆棧信息到content false
costTime boolean 是否記錄整個方法耗時 true
collector Class<? extends LogCollector> 指定日志收集器 默認空的收集器不指定

例子使用說明

@AopLog注解使用

直接在Controller 方法或類上加上注解<font color='bule'>@AopLog</font>,可以對該Controller中所有方法進行日志記錄與收集

例如 :


@AopLog(type = "測試API", stackTrace = true)
@RestController
public class DemoController {
    @Resource
    private DemoService demoService;
    /**
     * JSON數(shù)據(jù)測試
     */
    @PostMapping("/sayHello")
    public ResponseEntity<?> sayHello(@RequestBody Map<String, Object> request) {
        demoService.sayHello(request);
        return ResponseEntity.ok(request);
    }
    /**
     * RequestParam 參數(shù)測試
     */
    @PostMapping("/params")
    public ResponseEntity<?> params(@RequestParam Integer a) {
        return ResponseEntity.ok(a);
    }
    /**
     * 無參測試
     */
    @GetMapping("/noArgs")
    public ResponseEntity<?> noArgs() {
        return ResponseEntity.ok().build();
    }
    /**
     * XML 格式數(shù)據(jù)測試
     */
    @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();
    }
}
LogData.step 記錄詳細步驟內(nèi)容

這里調(diào)用了service方法,LogData.step 方法記錄每一個步驟詳細內(nèi)容

/**
 * @author EalenXie Created on 2020/1/16 10:49.
 */
@Service
@Slf4j
public class DemoService {
    /**
     * 測試方法, 使用LogData.step記錄步驟
     */
    public void sayHello(Map<String, Object> words) {
        LogData.step("1. 請求來了,執(zhí)行業(yè)務動作");
        log.info("do somethings");
        LogData.step("2. 業(yè)務動作執(zhí)行完成");
    }
}
自定義的全局日志收集器

本例中寫了一個最簡單的直接append寫入到文件中,你可以選擇自定義的方式進行日志收集(例如寫入到數(shù)據(jù)庫或者日志文件,或日志收集框架中,這個過程建議異步處理,可在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中獲取到記錄的日志內(nèi)容。

json格式的數(shù)據(jù)記錄(參數(shù)JSON):
{
    "args": {
        "id": 999,
        "value": "content"
    },
    "clientIp": "192.168.1.54",
    "content": "1. 請求來了,執(zhí)行業(yè)務動作\n2. 業(yè)務動作執(zhí)行完成\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格式的數(shù)據(jù)(參數(shù)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參數(shù)格式的數(shù)據(jù)(以參數(shù)鍵值對形式):

{
    "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"
}

特殊參數(shù)格式(目前暫為鍵值對形式,參數(shù)默認取對象的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/aop-log

項目命名為aop-log, 有時間會一直維護和優(yōu)化。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容