小公司自建前端監控埋點體系,證實可行

前端早早聊大會,前端成長的新起點,與掘金聯合舉辦。 加微信 codingdreamer 進大會專屬內推羣,贏在新的起跑線。前端


第十四屆|前端成長晉升專場,8-29 即將直播,9 位講師(螞蟻金服/稅友等),點我上車👉 (報名地址):node


正文以下

在去年,有過一次文字版的分享,當時系統尚未徹底建設成型,你們能夠結合這兩篇一塊兒看: 技術探索:60 天急速自研-前端埋點監控跟蹤系統大浪子。react

本文爲第五屆 - 前端監控體系建設專場講師 - Jimmy 的分享 - 簡要講稿版(完整版請看錄播視頻):小程序

前言

從宋小菜的第一款 APP 上線至今已經 5 年左右時間,咱們的端應用類型也由 RN APP 逐漸過渡到 RN APP/PC/H5/小程序 等多種類型共存的狀況,咱們從 19 年開始就開始研發本身的 埋點監控系統 並在 19 年年末初步成型上線一直運行到如今,此次分享我將從如下幾個方面去介紹咱們是如何研發出現在的埋點監控系統的:


後端

  • 第一部分:簡單講一下咱們爲何要自研這個系統以及設計和開發這個系統的一些思路;
  • 第二部分:簡單介紹一下部分 SDK 的實現;
  • 第三部分:講一下上報的日誌是如何處理的以及踩過的坑;
  • 第四部分:簡單介紹和展現一下監控看板;
  • 第五部分:介紹一下咱們的報警控制器的設計;
  • 第六部分:簡單講一下任務執行器。

設計思路

首先來說一下咱們設計這個監控系統的設計思路
微信小程序

前端監控的基本目的



前端監控的基本目的在咱們看來是如下幾點:api

  • 開發出來的應用的使用狀況:有沒有用戶使用,有多少用戶使用;
  • 用戶在使用過程當中遇到了什麼樣的問題;
  • 做爲開發者和運營者應該如何追蹤定位到這些問題並及時解決;
  • 同時從中吸收經驗避免再犯;
  • 埋點數據反哺業務:運營和產品負責人能從中獲得哪些東西,從而優化產品質量。

發展史

基於以上咱們對監控體系的思考,小菜前端在這 5 年中逐步完善了本身的監控體系,這差很少就是咱們監控體系的一個簡單的發展史:

 對於研發成本的考慮是形成上面發展史造成的根本緣由:promise

  • 在前期人力不夠的狀況下咱們仍是主要依靠三方工具,即友盟和 Countly,這個時候使用的技術棧主要是 RN 和原生 Android 應用,因爲在 15 年左右 RN 還屬於比較新的技術,市面上沒有較成熟的針對 RN 的埋點監控工具,所以咱們是對三方 SDK 進行了魔改以達到目的的;
  • 在第二階段時咱們已經將全部應用切換到了 RN 開發,因而就放棄了第一階段的畸形方案,使用了比較簡單的半自研方案,這時的埋點和監控是分開的,監控方面使用 Bugsnag ,但其對數據同步有限制,數據上報處理是和後端同窗合做完成的,存儲介質是 MongoDB,因爲數據結構設計較爲靈活且數據存儲量很大,致使後面作數據處理時存在了瓶頸;
  • 第三階段時因爲咱們業務的高速發展,前端應用已經不只僅侷限於 RN APP 了,還包含微信小程序和大量的 PC/H5 應用且 H5 應用的數量和使用頻率已經逐漸超過了 RN 應用,因而咱們開始了當前這個多端的埋點監控系統的研發,目前覆蓋了前端全部應用以及部分後端應用,系統從 19 年 10 月份上線一直使用到如今。

基礎模塊

接下來就應該考慮如何設計這個系統了,系統包含埋點和監控兩部分,因爲本次主題是監控,因此這裏就講監控系統相關的基礎模塊:

瀏覽器

  • 採集模塊:數據應該如何採集,採集哪些端,哪些數據;
  • 存儲:數據應該如何存儲,上報和保存的數據結構應該是怎樣的;
  • 報警:報警系統應該如何設計,如何嗅探錯誤,如何通知到負責人;
  • 異常管理:如何對上報的異常進行歸類,從而進行管理;
  • 展示:總結異常發生狀況,並展示給使用者。

系統架構

如下是系統目前的基礎架構:

緩存

客戶端


客戶端目前覆蓋到了 PC/H五、RN 應用、小程序,因爲 node 應用還比較少應用到業務中,考慮到投入產出比,尚未進行 SDK 的研發。

日誌處理

日誌處理通過三層:

  • 第一層考慮到流量較大采用集羣的方式分散壓力,同時對數據作初次處理,這會在後面的章節中講到;
  • 第二層主要是使用 Kafka 集羣進行 buffer,下降 ES 寫日誌的壓力,同時也具備緩存數據的功能,避免 ES 宕機形成的數據丟失,Filebeat 則是在應付 Kafka 出問題時的 backup;
  • 原始數據則在通過處理後存放在 Elasticsearch 中。

數據處理


這裏即是第三層的日誌處理:

  • 端上的埋點數據通過處理後會存放在數據倉庫內,這是後端同窗的工做了;
  • 從前端蒐集到的錯誤數據則是在監控系統後臺作處理的,這裏後面會講到。

數據展示

  • 一是埋點數據的展示,是在後端同窗處理後保存到數據倉庫中,而後由咱們前端的可視化報表系統進行展示;
  • 二是監控錯誤數據的展示,由監控系統的監控看板處理。

監控系統各模塊間的數據流向

如下則是展現監控系統中從數據上報到展現的整個數據流向全過程

SDK 實現

接下來就簡單講一下 SDK 的實現


採集哪些數據

首先要考慮的是應該採集哪些數據



雖然是監控錯誤數據,可是有時候爲了分析錯誤的造成緣由或者重現錯誤出現的場景,咱們仍是須要用到用戶的行爲數據的,所以須要採集的數據包括兩方面

用戶行爲數據:

  • 用戶頁面操做:點擊,滑動等;
  • 頁面跳轉:SPA/MPA 頁面跳轉, APP/小程序的頁面切換等;
  • 網絡請求;
  • 控制檯輸出: console.log/error 或者 Android log 等;
  • 自定義事件上報。


錯誤數據

  • 後端接口錯誤:
    • 服務不可用致使的前端問題
    • 業務數據錯誤致使前端處理數據出錯而造成的前端錯誤等
  • 前端 JS 錯誤:
    • 語法錯誤
    • 兼容性錯誤等
  • APP Native 錯誤等

SDK 實現

因爲時間緣由,這裏簡單講一下兩個端的 SDK 的簡單實現:

RN SDK



小菜的 RN 應用是比較純粹的 RN 應用,因此 RN SDK 實現能夠簡單地分爲兩端:

JS 端

  • 錯誤捕獲:RN 已經提供了比較方便的 API
    • ErrorUtils.setGlobalHandler 相似於瀏覽器的 window.onerror,用於捕獲 JS 運行時錯誤
    • Promise.rejectionTracking 相似於瀏覽器的 Unhandledrejection 事件
    • 還提供自定義上報錯誤,開發者能夠在開發過程當中 try/catch 而後調用
  • 網絡請求:代理 XMLHttpRequest 的 send/open/onload 等方法
  • 頁面跳轉:小菜 RN 應用的路由組件是對三方組件 react-navigation 的自定義包裝,咱們只須要在 onStateChange 或者在 Redux 集成方式中使用 screenTracking 便可


Native 端

  • iOS 使用 KSCrash 進行日誌採集,能夠本地進行符號化
  • 存儲捕獲到的數據(包括 JS 端和 native 端)統一上報


代碼展現
Promise.rejectionTracking 的代碼展現

const tracking = require("promise/setimmediate/rejection-tracking");
 tracking.disable();  tracking.enable({  allRejections: true,  onHandled: () => {  // We do nothing  },  onUnhandled: (id: any, error: any) => {  const stack = computeStackTrace(error);  stack.mechanism = 'unhandledrejection';  stack.data = {  id  }  // tslint:disable-next-line: strict-type-predicates  if (!stack.stack) {  stack.stack = [];  }  Col.trackError(stringifyMsg(stack))  }  }); 複製代碼


微信小程序 SDK 

這裏簡單講一下微信小程序 SDK 實現的兩個方面

網絡請求
代理全局對象 wx 的 wx.request 方法:

import "miniprogram-api-typings"
 export const wrapRequest = () => {  const originRequest = wx.request;  wx.request = function(...args: any[]): WechatMiniprogram.RequestTask {  // get request data  return originRequest.apply(this, ...args);  //   } } 複製代碼


頁面跳轉
覆寫 Page/Component 對象,代理其生命週期方法:

/* global Page Component */
 function captureOnLoad(args) {  console.log('do what you want to do', args) }  function captureOnShow(args) {  console.log('do what you want to do', args) }  function colProxy(target, method, customMethod) {  const originMethod = target[method]  if(target[method]){  target[method] = function() {  customMethod(…arguments)  originMethod.apply(this, arguments)  }  } }  // Page const originalPage = Page Page = function (opt) {  colProxy(opt.methods, 'onLoad', captureOnLoad)  colProxy(opt.methods, 'onShow', captureOnShow)  originalPage.apply(this, arguments) } // Component const originalComponent = Component Component = function (opt) {  colProxy(opt.methods, 'onLoad', captureOnLoad)  colProxy(opt.methods, 'onShow', captureOnShow)  originalComponent.apply(this, arguments) }  複製代碼

日誌處理

接下來就講一下咱們是如何作日誌處理的:


系統結構以及做用

首先展現一下日誌處理模塊(咱們稱之爲 log-transfer)的基本結構:

結構

數據上報屬於處理原始數據的第一層,包含如下特色

  • 採用多節點模式:考慮到數據流量可能比較大,所以採用多節點分擔壓力(這裏每一個節點都進行了容器化)
  • 每個節點都有做爲 backup 的 Filebeat :考慮到後續處理的 Kafka 的穩定性,添加了 Filebeat 做爲備用

做用

  • 解密上報的數據並驗證其正確性;
  • 處理部分數據字段,避免數據丟失,這會在後面提到;
  • 加入一些客戶端 SDK 沒法拿到的字段:如 IP;
  • 轉發處理事後的數據。

其中的要點

日誌上報處理中有很多須要注意的要點,這裏挑幾點簡單說一下:


  • 因爲數據量較大,因此全部數據並非寫在一個索引裏面的,這個時候就須要按時或者按天創建索引保存數據
  • 因爲上一條的緣故,咱們須要創建一個固定的索引模板,於是某一字段的數據類型必定要統一,不然會形成數據保存失敗的狀況,如下是一個錯誤示例:
  • 創建索引模板的前提是全部端上報的數據有統一結構,可是又因爲不一樣端的特性致使不可能全部端上報的字段徹底統一,這就存在不變的字段和變的字段(即基於端類型的特殊字段),哪些字段變哪些字段不變須要設計系統時進行衡量,即要保持適度的靈活:


  • 若是使用 JSON 類型數據進行上報且在 ES 中依然保存爲 JSON 數據,雖然存在索引模板,可是在模板沒有照顧獲得的字段上報上來時會生成新的字段,於是會形成字段數量爆炸,例如 iOS 的 native 錯誤符號化之後上報會生成不少字段,處理額外字段,這也是這一節講到的 log-transfer 的做用之一。

監控看板

接下來簡單講一下監控看板:

展現

一線是監控看板的一些簡單展現截圖

做用




看板的做用包括:

  • 實時 PU/V 查看;
  • 實時錯誤日誌查看;
  • Issue 管理;
  • 報警任務查看和編輯等。

Issue 處理流程

什麼是 issue? 即對已經上報的同類型錯誤進行概括和總結之後抽象出來的數據,便於咱們對同一種的錯誤進行跟蹤和處理。



Issue 存在明確的生命週期,這裏展現的就是咱們的 issue 生命週期:

  • 對於不須要處理的 issue,能夠將其直接置爲忽略狀態;
  • 若是分配的 issue 本身沒法處理能夠轉派他人;
  • 處理後的 issue 不會立馬關閉,發佈後系統會在線上進行驗證,若是復發則會 reopen。


如下是一個錯誤詳情的示例:



使用 source map 轉換後的堆棧信息也會展示在錯誤詳情中。

報警控制

接下來說一下咱們是如何設計和開發報警控制模塊的:

結構以及做用

結構

首先是系統結構:


  • 能夠看到報警控制模塊(咱們稱之爲 Controller)與其餘模塊之間並非直接交流的,而是經過 Kafka 交流的;
  • 監控看板編輯報警任務,而報警控制模塊消費報警任務;
  • 報警控制模塊經過 Kafka 控制 Inspector (即任務執行器)執行報警任務。


做用


其中要點

錯誤信息特徵

前面解釋了什麼是 issue ,可是沒有說明 issue 是如何抽象出來的,這裏給你們簡單解釋一下,以 JS 錯誤爲例:

JS 錯誤咱們在上報時使用 Tracekit 進行標準化,從而獲得具備統一結構的錯誤信息,而後據此判斷和歸類錯誤,參考字段包括:

  • 錯誤類型:如 TypeError, SyntaxError 等;
  • ErrorMessage:即錯誤信息;
  • 觸發函數: onerror/unhandlerejection;
  • 堆棧信息。

如下是系統 ISSUE 的展現:

ISSUE 的每一次狀態更新都會被記錄下來

Kafka 的做用



Kafka 的做用主要包括:

  • 任務隊列的存儲和分發;
  • 系統各個模塊之間的通訊。


爲何不使用實時通訊(socketIO/RPC)?總的來講是爲了系統穩定性考慮

  • Kafka 具備緩存通訊信息以及緩解系統壓力的做用;
  • 相比於實時通訊,使用 Kakfa 穩定性更高報警任務,到達率更有保障:若是某個單節點服務宕機,如任務控制器,那麼待該服務啓動後依然能夠消費以前緩存在 Kafka 中爲送達的信息。

固然,Kafka 也有替代方案,如 Redis/RabbitMQ 等。

報警任務設計



報警任務設計在報警系統中屬於比較重要的部分,咱們大體將其抽象成兩大部分:

  • 任務執行規則(或者說執行頻率)
  • 任務判斷規則(或者說觸發報警的規則)
    • 判斷細則:便是否多個條件造成一個報警任務,多個條件間的關係如何
    • 查詢類型:是 ES query 仍是 SQL 查詢,或者其餘
    • 查詢結果計算規則

數據結構示例

下面給出一個簡單的報警任務數據結構示例:





報警任務是能夠人爲設置和控制的

任務執行


而後就是系統中最後的一個模塊,任務執行模塊(咱們稱之爲 Inspector):

任務執行器則比較簡單,主要用於執行控制器分發的報警任務,查詢上報的線上數據根據任務的判斷規則生成相應的任務結果並經過 Kafka 返回給任務控制器,因爲報警任務可能較多因此採用集羣的方式部署,多節點分散任務壓力:

總結




最後再放出在以前給出的各模塊之間的 數據流向圖,不知道你們是否清楚一些了:


整個系統的主要模塊和數據流通都在這張圖上。
個人演講就此結束,謝謝你們!

本文使用 mdnice 排版

相關文章
相關標籤/搜索