SpringBoot2 集成日誌,複雜業務下的自定義實現

本文源碼:GitHub·點這裡 || GitEE·點這裡

一、日誌體系集成

1、日誌管理

在系統的開發中,最關鍵的一個組件工具就是日誌,日誌打印方便問題排查,或者生產事故回溯,日誌記錄用來監控並分析系統性能點,並以此為依據,不斷對系統進行優化;同時基於用戶的操作日誌,對用戶行為進行分析,開發智能推薦的功能,或者進行營銷投放,這在系統中都是常見且關鍵的業務流程。

2、ELK日誌體系

在大型系統架構中,ELK的日誌管理系統是系統必備功能,ELK-Stack是Elasticsearch、Logstash、Kiban三個開源軟件的組合,通常用來做日誌分析,實時數據檢索。基於Logstash做數據流動通道,使日誌數據不斷的流入搜索組件,基於Elasticsearch做數據實時查詢,基於Kiban的ES可視化界面,以此實現日誌數據的搜集、存儲、分析等核心功能,且該體系方便擴展。

ELK相關文章:

基於ELK體系的核心操作,有關於ElasticSearch其他文章可以自行查閱之前的內容,這裡不在陳列,好像很多東西都是這樣一點點積累出來的。

二、集成環境

1、項目結構

defined-log-api:測試工程;

defined-log-config:日誌核心模塊,依賴之後使用該模塊下注解即可;

2、數據表結構

CREATE TABLE dt_defined_log (
	id INT ( 11 ) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
	class_name VARCHAR ( 200 ) DEFAULT NULL COMMENT '請求類名',
	method_name VARCHAR ( 100 ) DEFAULT NULL COMMENT '請求方法名',
	method_desc VARCHAR ( 100 ) DEFAULT NULL COMMENT '請求方法描述',
	api_type INT ( 1 ) DEFAULT 0 COMMENT 'API類型',
	biz_nature INT ( 1 ) DEFAULT 0 COMMENT '業務性質類型',
	data_flow_type INT ( 1 ) DEFAULT 0 COMMENT '日誌數據流向',
	req_param VARCHAR ( 200 ) DEFAULT NULL COMMENT '請求報文',
	res_param VARCHAR ( 200 ) DEFAULT NULL COMMENT '響應報文',
	PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '日誌記錄表';

這裡完全基於業務需求自定義即可。

三、核心代碼說明

1、註解參數

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DefinedLog {

    /**
     * 操作類型
     */
    ApiTypeEnum apiType () ;

    /**
     * 方法描述
     */
    String methodDesc();

    /**
     * 業務性質
     */
    BizNatureEnum bizNature() ;

    /**
     * 數據流向,與業務性質關聯
     */
    DataFlowEnum dataFlow() ;

    /**
     * 存儲入參
     */
    boolean isSaveReqParam () default true ;

    /**
     * 存儲出參
     */
    boolean isSaveResParam() default true ;

    /**
     * 是否需要異步處理
     */
    boolean isAsync () default false ;
}

這裡描述一下如下幾個參數的意思:

bizNature:業務性質,即該日誌是否有分析,或者營銷推廣操作,例如在在電商業務中,瀏覽系列商品後是否推送廣告;

dataFlow:數據流向,即數據存儲後是否向其他數據源推送,常見可能推送到MQ或者Redis或者分析引擎中,推薦類系統中對關鍵日誌實時性要求極高,可以基於此做用戶行為實時分析;

isAsync:是否異步處理,在一些並發高的接口中,避免日誌記錄成為性能問題的一個因素;

其他相關參數都是十分常見,例如接口類型增刪改查,入參出參報文存儲,方法模塊的描述等等,這些都可以基於業務的需求自定義,然後做相關業務處理開發,思路開闊即可。

2、切面攔截

基於切面編程是方式,做相關日誌處理,獲取相應參數,構建日誌模型即可。

@Component
@Aspect
public class LogAop {

    private static final Logger LOGGER = LoggerFactory.getLogger(LogAop.class);

    @Value("${spring.application.app-id}")
    private String appId ;
    @Resource
    private DefineLogService defineLogService ;

    /**
     * 日誌切入點
     */
    @Pointcut("@annotation(com.defined.log.annotation.DefinedLog)")
    public void logPointCut() {

    }

    /**
     * 環繞切入
     */
    @Around("logPointCut()")
    public Object around (ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = null ;
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        try{
            // 執行方法
            result = proceedingJoinPoint.proceed();
            stopWatch.stop();
        } catch (Exception e){
            stopWatch.stop();
        } finally {
            // 保存日誌
            LOGGER.info(" execute time: {} ms ", stopWatch.getTotalTimeMillis());
            DefineLogModel defineLogModel = buildLogParam (proceedingJoinPoint);
            defineLogModel.setResParam(JSONObject.toJSONString(result));
            defineLogService.saveLog(defineLogModel) ;
        }
        return result ;
    }

    private DefineLogModel buildLogParam (ProceedingJoinPoint point){

        DefineLogModel defineLogModel  = new DefineLogModel() ;

        MethodSignature signature = (MethodSignature) point.getSignature();
        Method reqMethod = signature.getMethod();
        String className = point.getTarget().getClass().getName();
        Object[] reqParam = point.getArgs();

        LOGGER.info("請求方法:"+reqMethod.getName());
        LOGGER.info("請求類名:"+className);
        LOGGER.info("請求參數:"+ JSONObject.toJSONString(reqParam));
        // 獲取方法上註解
        reqMethod.getAnnotation(DefinedLog.class).getClass();
        DefinedLog definedLog = reqMethod.getAnnotation(DefinedLog.class);

        // 構建參數
        String methodName = reqMethod.getName() ;
        Integer apiType = definedLog.apiType().getApiType();
        String apiTypeDesc = definedLog.apiType().getApiTypeDesc();
        String methodDesc = definedLog.methodDesc() ;
        Integer bizNature = definedLog.bizNature().getBizNature() ;
        Integer dataFlowType = definedLog.dataFlow().getDataFlowType();
        boolean isSaveReqParam = definedLog.isSaveReqParam();
        boolean isSaveResParam = definedLog.isSaveResParam();
        boolean isAsync = definedLog.isAsync() ;

        defineLogModel.setAppId(appId);
        defineLogModel.setClassName(className);
        defineLogModel.setMethodName(methodName);
        defineLogModel.setMethodDesc(methodDesc);
        defineLogModel.setApiType(apiType);
        defineLogModel.setApiTypeDesc(apiTypeDesc);
        defineLogModel.setBizNature(bizNature);
        defineLogModel.setDataFlowType(dataFlowType);
        defineLogModel.setSaveReqParam(isSaveReqParam);
        defineLogModel.setSaveResParam(isSaveResParam);
        defineLogModel.setAsync(isAsync);
        defineLogModel.setReqParam(JSONObject.toJSONString(reqParam));

        return defineLogModel ;
    }
}

3、使用方式

DefinedLog註解在接口方法上即可。

@RestController
public class LogController {

    @GetMapping("/logApi")
    @DefinedLog(apiType=ApiTypeEnum.COMPOSITE,
                methodDesc="測試日誌",
                bizNature= BizNatureEnum.DEFAULT,
                dataFlow= DataFlowEnum.DEFAULT)
    public String logApi (@RequestParam("param") String param){
        return "success-re" ;
    }

}

4、記錄參數

這樣自定義日誌流程就完成了。

四、源代碼地址

GitHub·地址
//github.com/cicadasmile/middle-ware-parent
GitEE·地址
//gitee.com/cicadasmile/middle-ware-parent

推薦閱讀:微服務架構系列

標題
微服務架構:項目技術選型簡介,架構圖解說明
微服務架構:業務架構設計,系統分層管理
微服務架構:數據庫選型簡介,業務數據規劃設計
微服務架構:中間件集成,公共服務封裝
微服務架構:SpringCloud 基礎組件應用設計
微服務架構:通過業務、應用、技術、存儲,聊聊架構
微服務技術棧:常見註冊中心組件,對比分析
微服務技術棧:流量整形算法,服務熔斷與降級
微服務技術棧:API網關中心,落地實現方案