從零到日誌採集索引可視化、監控報警、rpc trace跟蹤-日誌事件、埋點設計及對接

github開源  歡迎一起維護~~

這篇主要來介紹下如何設計不同業務含義的日誌的抽象設計、如何埋點、以及我們寫的這個項目如何對接。
  1. 爲了處理不同事件類型的日誌,我們將不同業務含義的日誌抽象成了日誌事件,具體如下:
    public enum EventType {
    
        normal(Constants.EVENT_TYPE_NORMAL, "正常入庫日誌"),
        invoke_interface(Constants.EVENT_TYPE_INVOKE_INTERFACE, "api調用"),
        middleware_opt(Constants.EVENT_TYPE_MIDDLEWARE_OPT, "中間件操作"),
        job_execute(Constants.EVENT_TYPE_JOB_EXECUTE, "job執行狀態"),
        custom_log(Constants.EVENT_TYPE_CUSTOM_LOG, "自定義埋點日誌"),
        thirdparty_call(Constants.EVENT_TYPE_THIRDPARTY_CALL, "第三方系統調用");
    
        private String symbol;
    
        private String label;
    
        private EventType(String symbol, String label) {
            this.symbol = symbol;
            this.label = label;
        }
    
        public String symbol() {
            return this.symbol;
        }
    
        public String label() {
            return this.label;
        }
    
    }
    正常入庫日誌:沒有監控報警業務含義的日誌,僅僅是每個系統自己LOGGER.info(msg)打印出來的日誌
    api調用:調用接口產生的日誌
    中間件操作:操作hbase或者mongo的日誌
    job執行狀態:mr和spark等任務的執行結果狀態日誌
    自定義埋點日誌:對接方自己想要後期批量或者實時流處理的日誌,協助存儲,方便各個業務系統自己處理
    第三方系統調用:調用客戶系統產生的日誌

    具體抽象出來的對象如下:
    public class EventLog {
    
        // 事件日誌成功還是失敗
        public static final String MONITOR_STATUS_SUCCESS = "success";
        public static final String MONITOR_STATUS_FAILED = "failed";
    
        // 日誌事件類型
        protected EventType eventType;
        // 日誌事件名稱
        protected String uniqueName;
        // 需要計算耗時的日誌設置耗時, 毫秒
        protected long cost;
        // 狀態
        protected String status;
        // 具體日誌內容
        protected String log;
    
        /**
         * 不可主動new
         */
        protected EventLog() {
    
        }
    
        /**
         * 創建eventlog
         * @param eventType
         * @param log
         * @return
         */
        public static EventLog buildEventLog(EventType eventType, String uniqueName, long cost, String status, String log) {
            EventLog eventLog = new EventLog();
            eventLog.setEventType(eventType);
            eventLog.setUniqueName(uniqueName);
            eventLog.setCost(cost);
            eventLog.setStatus(status);
            eventLog.setLog(log);
            return eventLog;
        }
    
        /**
         * 根據一條日誌的內容解析出該條日誌
         * @param line
         * @return
         */
        public static EventType parseEventType(String line) {
            if (line.indexOf(Constants.VERTICAL_LINE) == -1) {
                // log中不包含|, 說明肯定是normal日誌
                return EventType.normal;
            } else {
                // 首先判斷是否是用戶自己的日誌中包含|
                String[] detail = line.split(Constants.VERTICAL_LINE_SPLIT);
                try {
                    return EventType.valueOf(detail[0]);
                } catch (Exception e) {
                    return EventType.normal;
                }
            }
        }
    
        /**
         * 根據字符串解析成EventLog
         * @param line
         * @return
         */
        public static EventLog parseEventLog(String line) {
            String[] detail = line.split(Constants.VERTICAL_LINE_SPLIT);
            return buildEventLog(EventType.valueOf(detail[0]), detail[1], Long.parseLong(detail[2]), detail[3], detail[4]);
        }
    
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append(this.eventType.symbol());
            sb.append(Constants.VERTICAL_LINE);
            sb.append(this.uniqueName);
            sb.append(Constants.VERTICAL_LINE);
            sb.append(this.cost);
            sb.append(Constants.VERTICAL_LINE);
            sb.append(this.status);
            sb.append(Constants.VERTICAL_LINE);
            sb.append(this.log);
            return sb.toString();
        }
    
        public EventType getEventType() {
            return eventType;
        }
    
        public void setEventType(EventType eventType) {
            this.eventType = eventType;
        }
    
        public String getUniqueName() {
            return uniqueName;
        }
    
        public void setUniqueName(String uniqueName) {
            this.uniqueName = uniqueName;
        }
    
        public long getCost() {
            return cost;
        }
    
        public void setCost(long cost) {
            this.cost = cost;
        }
    
        public String getStatus() {
            return status;
        }
    
        public void setStatus(String status) {
            this.status = status;
        }
    
        public String getLog() {
            return log;
        }
    
        public void setLog(String log) {
            this.log = log;
        }
    }
    以上是大部分日誌事件能夠抽象出來的dto,特殊的如下:
    public class ApiLog extends EventLog {
    
        // 具體請求api的賬戶
        private String account;
    
        /**
         * 不可主動new
         */
        private ApiLog() {
    
        }
    
        public static ApiLog buildApiLog(EventType eventType, String uniqueName, String account, long cost, String status, String log) {
            ApiLog apiLog = new ApiLog();
            apiLog.setEventType(eventType);
            apiLog.setUniqueName(uniqueName);
            apiLog.setCost(cost);
            apiLog.setStatus(status);
            apiLog.setLog(log);
            apiLog.setAccount(account);
            return apiLog;
        }
    
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append(this.eventType.symbol());
            sb.append(Constants.VERTICAL_LINE);
            sb.append(this.uniqueName);
            sb.append(Constants.VERTICAL_LINE);
            sb.append(this.account);
            sb.append(Constants.VERTICAL_LINE);
            sb.append(this.cost);
            sb.append(Constants.VERTICAL_LINE);
            sb.append(this.status);
            sb.append(Constants.VERTICAL_LINE);
            sb.append(this.log);
            return sb.toString();
        }
    
        /**
         * 根據字符串解析成EventLog
         * @param line
         * @return
         */
        public static ApiLog parseEventLog(String line) {
            String[] detail = line.split(Constants.VERTICAL_LINE_SPLIT);
            return buildApiLog(EventType.valueOf(detail[0]), detail[1], detail[2], Long.parseLong(detail[3]), detail[4], detail[5]);
        }
    
        public String getAccount() {
            return account;
        }
    
        public void setAccount(String account) {
            this.account = account;
        }
    }
    

  2. 埋點設計
    對於我們的業務,我們抽象了上一章的幾個日誌事件即可,自己可以根據自己的業務需求再定義;當我們的業務系統發生了特定的事件,我們可以構造相應的日誌調用LOGGER.info(xxx)來傳遞到下游處理
    如:
    LOGGER.info(ApiLog.buildApiLog(EventType.invoke_interface, "/app/status", "800001", 100, 
                    EventLog.MONITOR_STATUS_SUCCESS, "我是mock api成功日誌").toString());

  3. 如何對接
    1. logback
      1. 依賴
        compile ("monitor-center:pugna:0.0.1") {
          exclude group: 'log4j', module: 'log4j'
        }

      2. 配置
        在logback.xml中加入一個kafkaAppender,並在properties文件中設置好相關的值,如下:
        <property name="APP_NAME" value="your-app-name" />
        <!-- kafka appender -->
        <appender name="kafkaAppender" class="com.unionpaysmart.pugna.kafka.logback.KafkaAppender">
          <encoder class="com.unionpaysmart.pugna.kafka.logback.encoder.KafkaLayoutEncoder">
            <layout class="ch.qos.logback.classic.PatternLayout">
              <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS};${CONTEXT_NAME};${HOSTNAME};%thread;%-5level;%logger{96};%line;%msg%n</pattern>
            </layout>
          </encoder>
          <topic>${kafka.topic}</topic>
          <zkServers>${zookeeper.servers}</zkServers>
          <mail>${mail}</mail>
          <keyBuilder class="com.unionpaysmart.pugna.kafka.partitioner.AppHostKeyBuilder" />
        
          <config>bootstrap.servers=${kafka.bootstrap.servers}</config>
          <config>acks=0</config>
          <config>linger.ms=100</config>
          <config>max.block.ms=5000</config>
          <config>client.id=${CONTEXT_NAME}-${HOSTNAME}-logback</config>
        </appender>
    2. log4j
      1. 依賴
        compile ("monitor-center:pugna:0.0.1") {
          exclude group: 'ch.qos.logback', module: 'logback-classic'
        }

      2. 配置
        在log4j.xml中加入一個kafkaAppender,如下:
        <appender name="kafkaAppender" class="com.unionpaysmart.pugna.kafka.log4j.KafkaAppender">
          <param name="topic" value="${kafka.topic}"/>
          <param name="zkServers" value="${zookeeper.servers}"/>
          <param name="app" value="apollo-generater"/>
          <param name="mail" value="${mail}"/>
          <param name="bootstrapServers" value="${kafka.bootstrap.servers}"/>
          <param name="acks" value="0"/>
          <param name="maxBlockMs" value="5000"/>
          <param name="lingerMs" value="100"/>
        
          <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS};APP_NAME;HOSTNAME;%t;%p;%c;%L;%m%n"/>
          </layout>
        </appender>

    3. 注意點
      公司大部分使用的logback版本爲1.1.7,該版本結合kafka有bug,具體見:http://jira.qos.ch/browse/LOGBACK-1158
  4. 使用
    1. 正常日誌
      LOGGER.info("我是測試日誌打印")
    2. api日誌
      // 參數依次爲EventType(事件類型)api、賬號、請求耗時、成功還是失敗、具體自定義的日誌內容
      LOGGER.info(ApiLog.buildApiLog(EventType.invoke_interface, "/app/status", "800001", 100, EventLog.MONITOR_STATUS_SUCCESS, "我是mock api成功日誌").toString());
      LOGGER.info(ApiLog.buildApiLog(EventType.invoke_interface, "/app/status", "800001", 10, EventLog.MONITOR_STATUS_FAILED, "我是mock api失敗日誌").toString());
    3. 中間件日誌
      // 參數依次爲EventType(事件類型)MiddleWare(中間件名稱)、操作耗時、成功還是失敗、具體自定義的日誌內容
      LOGGER.info(EventLog.buildEventLog(EventType.middleware_opt, MiddleWare.HBASE.symbol(), 100, EventLog.MONITOR_STATUS_SUCCESS, "我是mock middle ware成功日誌").toString());
      LOGGER.info(EventLog.buildEventLog(EventType.middleware_opt, MiddleWare.MONGO.symbol(), 10, EventLog.MONITOR_STATUS_FAILED, "我是mock middle ware失敗日誌").toString());
    4. job執行日誌
      // job執行僅僅處理失敗的日誌(成功的不做處理,所以只需要構造失敗的日誌), 參數依次爲EventType(事件類型)job id號、操作耗時、失敗、具體自定義的日誌內容
      LOGGER.info(EventLog.buildEventLog(EventType.job_execute, "application_1477705439920_0544", 10, EventLog.MONITOR_STATUS_FAILED, "我是mock job exec失敗日誌").toString());

    5. 第三方請求日誌
      // 參數依次爲EventType(事件類型)、第三方名稱、操作耗時、成功還是失敗、具體自定義的日誌內容
      LOGGER.info(EventLog.buildEventLog(EventType.thirdparty_call, "name1", 100, EventLog.MONITOR_STATUS_FAILED, "我是mock third 失敗日誌").toString());
      LOGGER.info(EventLog.buildEventLog(EventType.thirdparty_call, "name1", 100, EventLog.MONITOR_STATUS_SUCCESS, "我是mock third 成功日誌").toString());
      LOGGER.info(EventLog.buildEventLog(EventType.thirdparty_call, "name2", 100, EventLog.MONITOR_STATUS_SUCCESS, "我是mock third 成功日誌").toString());
      LOGGER.info(EventLog.buildEventLog(EventType.thirdparty_call, "name2", 100, EventLog.MONITOR_STATUS_FAILED, "我是mock third 失敗日誌").toString());


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章