1. 程式人生 > 實用技巧 >進階式-日誌列印-構建器模式

進階式-日誌列印-構建器模式

專案內日誌列印,從第一版修改到第四版。每次修改都是進步。

第一版:直接列印

一般日誌列印方法,更直接的會直接在這裡列印字串。這種方式列印的日誌在編碼過程中除錯方便,便於理解。

但是對於已上線的專案,就比較麻煩了。日誌太多,且雜亂。

此示例列印時json格式日誌。第一個引數輸入日誌型別,後面引數都是 Key ,Value , Key , Value ..... 模式輸入資料。

json格式日誌便於日誌收集和日誌的統計分析。

log.info("JSON", "productKey", "ProductKey", "deviceName", "DeviceName", "businessType
", "OTA");

第二版:將這些需要列印的引數抽出來封裝為一個物件列印

這種方式列印日誌,每次在需要列印日誌地方呼叫對應的日誌方法即可。統一了日誌列印引數。

package com.log;

import com.alibaba.fastjson.JSONObject;
import lombok.Data;

/**
 * @version 1.0
 * @description
 * @date 2020/11/12 14:24
 */
@Data
public class ServiceLog<T> {
    private String businessType;
    
private String messageId; private String productKey; private String deviceName; private String operation; private T data; private int status; private ServiceLog(String businessType){ this.messageId = ""; this.productKey = ""; this.deviceName = "";
this.businessType = businessType; this.operation = ""; this.data = (T)new JSONObject(); this.status = 200; } private ServiceLog(String productKey, String deviceName, String businessType){ this.messageId = ""; this.productKey = productKey; this.deviceName = deviceName; this.businessType = businessType; this.operation = ""; this.data = (T)new JSONObject(); this.status = 200; } private ServiceLog(String messageId, String productKey, String deviceName, String businessType, String operation){ this.messageId = messageId; this.productKey = productKey; this.deviceName = deviceName; this.businessType = businessType; this.operation = operation; this.data = (T)new JSONObject(); this.status = 200; } private ServiceLog(String messageId, String productKey, String deviceName, String businessType, String operation, T data, int status){ this.messageId = messageId; this.productKey = productKey; this.deviceName = deviceName; this.businessType = businessType; this.operation = operation; this.data = data; this.status = status; } }

第二版優化:將物件中多個重複程式碼合併。

合併第二版中重複程式碼,將方法合併為this呼叫。

package com.log;

import com.alibaba.fastjson.JSONObject;
import lombok.Data;

/*
 * @version 1.0
 * @description
 * @date 2020/11/12 14:24
 */
@Data
public class ServiceLog<T> {
    private String businessType;
    private String messageId;
    private String productKey;
    private String deviceName;
    private String operation;
    private T data;
    private int status;

    public ServiceLog(String businessType){
        this("","",businessType);
    }

    public ServiceLog(String productKey, String deviceName, String businessType){
        this("", productKey, deviceName, businessType,"");
    }

    public ServiceLog(String messageId, String productKey, String deviceName, String businessType, String operation){
        this(messageId, productKey, deviceName, businessType,operation, (T)new JSONObject(), 200);
    }

    public ServiceLog(String messageId, String productKey, String deviceName, String businessType, String operation, T data, int status){
        this.messageId = messageId;
        this.productKey = productKey;
        this.deviceName = deviceName;
        this.businessType = businessType;
        this.operation = operation;
        this.data = data;
        this.status = status;
    }
}

第三版:構建器(build)模式

構建器模式列印日誌,此時將日誌型別抽象為列舉(BusinessTypeEnum)。

log.info 語句也放在了ServiceLog物件裡,但是此時 log是從外部輸入的,列印日誌也相當於在原呼叫物件中列印。

package com.log;

import com.log.enums.BusinessTypeEnum;
import lombok.Data;
import org.slf4j.Logger;
import java.util.HashMap;
import java.util.Map;

/**
 * @version 1.0
 * @description
 * @date 2020/11/12 14:24
 */
@Data
public class ServiceLog {
    private BusinessTypeEnum businessType;

    private String messageId;
    private String productKey;
    private String deviceName;
    private String operation;
    private Map data;
    private int status;

    private ServiceLog(Builder builder){
        this.messageId = builder.messageId;
        this.productKey = builder.productKey;
        this.deviceName = builder.deviceName;
        this.businessType = builder.businessType;
        this.operation = builder.operation;
        this.data = builder.data;
        this.status = builder.status;
    }

    public static class Builder {
        private BusinessTypeEnum businessType;
        private String messageId;
        private String productKey;
        private String deviceName;
        private String operation;
        private Map data;
        private int status;

        public Builder(BusinessTypeEnum businessType){
            this.businessType = businessType;
            this.data = new HashMap();
        }

        public Builder mid(String messageId){
            this.messageId = messageId;
            return this;
        }

        public Builder pk(String productKey){
            this.productKey = productKey;
            return this;
        }

        public Builder dn(String deviceName){
            this.deviceName = deviceName;
            return this;
        }

        public Builder opt(String operation){
            this.operation = operation;
            return this;
        }

        public Builder params(Object params){
            this.data.put("params", params);
            return this;
        }

        public Builder msg(String message){
            this.data.put("message", message);
            return this;
        }

        public Builder status(int status){
            this.status = status;
            return this;
        }

        public ServiceLog build(Logger log){
            ServiceLog serviceLog = new ServiceLog(this);
            log.info("JSON_OBJECT", serviceLog);
            return serviceLog;
        }
    }
}

第四版:構建器模式優化版,列印日誌

1、用 @getter,省略了內部引數的get方法。由於此類沒有set,故不用 @Data

2、把status資料型別 int 更換為Integer 是為了不讓status 有預設值。int型別會有預設資料 0,會影響狀態碼為 0的資料。

3、初始化status值。status = builder.status == null ? CommonCodeEnum.SUCCESS.getCode() : builder.status;

4、將data初始化,便於下方params 和 msg 插入。這裡不再用new方式初始化Map,改用 Maps.newHashMapWithExpectedSize 方式。

同時初始化了Map的資料。因為在業務中定義Map只有兩個引數。此時給定Map Size,也避免了空間浪費。

package com.log;
import ch.qos.logback.LogbackConstants; import com.log.enums.BusinessTypeEnum; import lombok.Getter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map;
/** * @version 1.0 * @description 構建器構建日誌列印 * @date 2020/11/11 13:06 */ // --這裡用 @getter,省略了內部引數的get方法。由於此類沒有set,故不用 @Data @Getter public class ServiceLog { private final static Logger log = LoggerFactory.getLogger(ServiceLog.class); private BusinessTypeEnum businessType; private String messageId; private String productKey; private String deviceName; private String operation; private Map data; private int status; private ServiceLog(Builder builder){ this.messageId = builder.messageId; this.productKey = builder.productKey; this.deviceName = builder.deviceName; this.businessType = builder.businessType; this.operation = builder.operation; this.data = builder.data;
     // 給定status 資料值,避免 status == null 情況。
this.status = builder.status == null ? CommonCodeEnum.SUCCESS.getCode() : builder.status; } // -- 列印日誌 public void print(){ // LogbackConstants.JSON_OBJECT = "JSON_OBJECT", 將日誌型別做成列舉 log.info(LogbackConstants.JSON_OBJECT, this); } // -- 靜態方法,初始化 Builder public static Builder builder(BusinessTypeEnum businessType){ return new Builder(businessType); } public static class Builder { // -- 必填引數。這個必填,使用了列舉引數 private BusinessTypeEnum businessType; // -- 非必填引數 private String messageId; private String productKey; private String deviceName; private String operation; private final Map<String, Object> data;
     // -- 這裡把 int 更換為Integer 是為了不讓status 有預設值。int型別會有預設資料 0,會影響狀態碼為 0的資料。
private Integer status; public Builder(BusinessTypeEnum businessType){ this.businessType = businessType; // -- 這裡先將data初始化,便於下方params 和 msg 插入。這裡不再用new方式初始化Map,改用 Maps.newHashMapWithExpectedSize 方式。
       // 同時初始化了Map的資料。因為在業務中定義Map只有兩個引數。此時給定Map Size,也避免了空間浪費。
this.data = Maps.newHashMapWithExpectedSize(2); } public Builder mid(String messageId){ this.messageId = messageId; return this; } public Builder pk(String productKey){ this.productKey = productKey; return this; } public Builder dn(String deviceName){ this.deviceName = deviceName; return this; } public Builder opt(String operation){ this.operation = operation; return this; } public Builder params(Object params){ this.data.put("params", params); return this; } public Builder msg(String message){ this.data.put("message", message); return this; } public Builder status(int status){ this.status = status; return this; } public ServiceLog build(){ return new ServiceLog(this); } } }

呼叫示例:

public static void main(String[] args) {
    // -- 第一版呼叫
    log.info("JSON","productKey", "ProductKey", "deviceName", "DeviceName","businessType","OTA");
    
    // -- 第二版呼叫
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("params",new JSONObject());
    jsonObject.put("message","呼叫成功!");
    log.info("JSON_OBJECT", new ServiceLog("msgid", "ProductKey","DeviceName","OTA","operation",jsonObject,200));
    
     // -- 第三版呼叫  
     new ServiceLog.Builder(BusinessTypeEnum.OTA).pk("ProductKey").msg("呼叫成功").build(log);
    
    // -- 第四版呼叫  對於非必填的引數 mid, 可呼叫可不呼叫
    ServiceLog.builder(BusinessTypeEnum.OTA).pk("ProductKey").dn("DeviceName").opt("OTAProgress").msg("韌體升級異常").status(CommonCodeEnum.SYS_ERROR.getCode()).build().print();
    ServiceLog.builder(BusinessTypeEnum.OTA).mid("1001").pk("ProductKey").dn("DeviceName").opt("OTAProgress").params(new JSONObject()).status(200).build().print();

}

列印結果:

// -- msg 內是日誌列印內容,其他如:date \ level \ TRACE_ID 是logback列印內容
// 第一版結果
{"date":"2020-11-12 15:33:00.267","msg":{"productKey":"ProductKey","businessType":"OTA","deviceName":"DeviceName"},"level":"INFO","line":"27","logger":"c.c.i.i.IotServiceApplicationTests","TRACE_ID":"","client":""}
// 第二版結果
{"date":"2020-11-12 15:33:00.360","msg":{"businessType":"OTA","data":{},"deviceName":"DeviceName","messageId":"","operation":"","productKey":"ProductKey","status":200},"level":"INFO","line":"28","logger":"c.c.i.i.IotServiceApplicationTests","TRACE_ID":"","client":""}
// 第三版結果
{"date":"2020-11-12 16:21:26.798","msg":{"businessType":"OTA","data":{"message":"呼叫成功"},"productKey":"ProductKey","status":0},"level":"INFO","line":"87","logger":"c.c.i.i.IotServiceApplicationTests","TRACE_ID":"","client":""}

// 第四版結果
{"date":"2020-11-12 14:17:33.480","msg":{"businessType":"OTA","data":{"message":"韌體升級異常"},"deviceName":"DeviceName","operation":"OTAProgress","productKey":"ProductKey","status":500},"level":"INFO","line":"42","logger":"com.iot.log.dto.ServiceLog","TRACE_ID":"","client":""}
{"date":"2020-11-12 14:17:33.618","msg":{"businessType":"OTA","data":{"params":{}},"deviceName":"DeviceName","messageId":"1001","operation":"OTAProgress","productKey":"ProductKey","status":200},"level":"INFO","line":"42","logger":"com.iot.log.dto.ServiceLog","TRACE_ID":"","client":""}