騰訊CDC團隊:前端異常監控解決方案
- 2019 年 11 月 10 日
- 筆記

本文出自 Tencent CDC,轉載時請註明出處 https://cdc.tencent.com/2018/09/13/frontend-exception-monitor-research/
前端監控包括行為監控、異常監控、性能監控等,本文主要討論異常監控。對於前端而言,和後端處於同一個監控系統中,前端有自己的監控方案,後端也有自己等監控方案,但兩者並不分離,因為一個用戶在操作應用過程中如果出現異常,有可能是前端引起,也有可能是後端引起,需要有一個機制,將前後端串聯起來,使監控本身統一於監控系統。因此,即使只討論前端異常監控,其實也不能嚴格區分前後端界限,而要根據實際系統的設計,在最終的報表中體現出監控對開發和業務的幫助。
一般而言,一個監控系統,大致可以分為四個階段:日誌採集、日誌存儲、統計與分析、報告和警告。
收集階段:收集異常日誌,先在本地做一定的處理,採取一定的方案上報到伺服器。
存儲階段:後端接收前端上報的異常日誌,經過一定處理,按照一定的存儲方案存儲。
分析階段:分為機器自動分析和人工分析。機器自動分析,通過預設的條件和演算法,對存儲的日誌資訊進行統計和篩選,發現問題,觸發報警。人工分析,通過提供一個可視化的數據面板,讓系統用戶可以看到具體的日誌數據,根據資訊,發現異常問題根源。
報警階段:分為告警和預警。告警按照一定的級別自動報警,通過設定的渠道,按照一定的觸發規則進行。預警則在異常發生前,提前預判,給出警告。
1 前端異常
前端異常是指在用戶使用Web應用時無法快速得到符合預期結果的情況,不同的異常帶來的後果程度不同,輕則引起用戶使用不悅,重則導致產品無法使用,使用戶喪失對產品的認可。
1.1 前端異常分類
根據異常程式碼的後果的程度,對前端異常的表現分為如下幾類

a. 出錯
介面呈現的內容與用戶預期的內容不符,例如點擊進入非目標介面,數據不準確,出現的錯誤提示不可理解,介面錯位,提交後跳轉到錯誤介面等情況。這類異常出現時,雖然產品本身功能還能正常使用,但用戶無法達成自己目標。
b. 獃滯
介面出現操作後沒有反應的現象,例如點擊按鈕無法提交,提示成功後無法繼續操作。這類異常出現時,產品已經存在介面級局部不可用現象。
c. 損壞
介面出現無法實現操作目的的現象,例如點擊無法進入目標介面,點擊無法查看詳情內容等。這類異常出現時,應用部分功能無法被正常使用。
d. 假死
介面出現卡頓,無法對任何功能進行使用的現象。例如用戶無法登陸導致無法使用應用內功能,由於某個遮罩層阻擋且不可關閉導致無法進行任何後續操作。這類異常出現時,用戶很可能殺死應用。
e. 崩潰
應用出現經常性自動退出或無法操作的現象。例如間歇性crash,網頁無法正常載入或載入後無法進行任何操作。這類異常持續出現,將直接導致用戶流失,影響產品生命力。
1.2 異常錯誤原因分類
前端產生異常的原因主要分5類:
原因 |
案例 |
頻率 |
---|---|---|
邏輯錯誤 |
1) 業務邏輯判斷條件錯誤2) 事件綁定順序錯誤3) 調用棧時序錯誤4) 錯誤的操作js對象 |
經常 |
數據類型錯誤 |
1) 將null視作對象讀取property2) 將undefined視作數組進行遍歷3) 將字元串形式的數字直接用於加運算4) 函數參數未傳 |
經常 |
語法句法錯誤 |
|
較少 |
網路錯誤 |
1) 慢2) 服務端未返回數據但仍200,前端按正常進行數據遍歷3) 提交數據時網路中斷4) 服務端500錯誤時前端未做任何錯誤處理 |
偶爾 |
系統錯誤 |
1) 記憶體不夠用2) 磁碟塞滿3) 殼不支援API4) 不兼容 |
較少 |
2 異常採集
2.1 採集內容
當異常出現的時候,我們需要知道異常的具體資訊,根據異常的具體資訊來決定採用什麼樣的解決方案。在採集異常資訊時,可以遵循4W原則:
WHO did WHAT and get WHICH exception in WHICH environment?
a. 用戶資訊
出現異常時該用戶的資訊,例如該用戶在當前時刻的狀態、許可權等,以及需要區分用戶可多終端登錄時,異常對應的是哪一個終端。
b. 行為資訊
用戶進行什麼操作時產生了異常:所在的介面路徑;執行了什麼操作;操作時使用了哪些數據;當時的API吐了什麼數據給客戶端;如果是提交操作,提交了什麼數據;上一個路徑;上一個行為日誌記錄ID等。
c. 異常資訊
產生異常的程式碼資訊:用戶操作的DOM元素節點;異常級別;異常類型;異常描述;程式碼stack資訊等。
d. 環境資訊
網路環境;設備型號和標識碼;作業系統版本;客戶端版本;API介面版本等。
欄位 |
類型 |
解釋 |
---|---|---|
requestId |
String |
一個介面產生一個requestId |
traceId |
String |
一個階段產生一個traceId,用於追蹤和一個異常相關的所有日誌記錄 |
hash |
String |
這條log的唯一標識碼,相當於logId,但它是根據當前日誌記錄的具體內容而生成的 |
time |
Number |
當前日誌產生的時間(保存時刻) |
userId |
String |
|
userStatus |
Number |
當時,用戶狀態資訊(是否可用/禁用) |
userRoles |
Array |
當時,前用戶的角色列表 |
userGroups |
Array |
當時,用戶當前所在組,組別許可權可能影響結果 |
userLicenses |
Array |
當時,許可證,可能過期 |
path |
String |
所在路徑,URL |
action |
String |
進行了什麼操作 |
referer |
String |
上一個路徑,來源URL |
prevAction |
String |
上一個操作 |
data |
Object |
當前介面的state、data |
dataSources |
Array<Object> |
上游api給了什麼數據 |
dataSend |
Object |
提交了什麼數據 |
targetElement |
HTMLElement |
用戶操作的DOM元素 |
targetDOMPath |
Array<HTMLElement> |
該DOM元素的節點路徑 |
targetCSS |
Object |
該元素的自定義樣式表 |
targetAttrs |
Object |
該元素當前的屬性及值 |
errorType |
String |
錯誤類型 |
errorLevel |
String |
異常級別 |
errorStack |
String |
錯誤stack資訊 |
errorFilename |
String |
出錯文件 |
errorLineNo |
Number |
出錯行 |
errorColNo |
Number |
出錯列位置 |
errorMessage |
String |
錯誤描述(開發者定義) |
errorTimeStamp |
Number |
時間戳 |
eventType |
String |
事件類型 |
pageX |
Number |
事件x軸坐標 |
pageY |
Number |
事件y軸坐標 |
screenX |
Number |
事件x軸坐標 |
screenY |
Number |
事件y軸坐標 |
pageW |
Number |
頁面寬度 |
pageH |
Number |
頁面高度 |
screenW |
Number |
螢幕寬度 |
screenH |
Number |
螢幕高度 |
eventKey |
String |
觸發事件的鍵 |
network |
String |
網路環境描述 |
userAgent |
String |
客戶端描述 |
device |
String |
設備描述 |
system |
String |
作業系統描述 |
appVersion |
String |
應用版本 |
apiVersion |
String |
介面版本 |
這是一份非常龐大的日誌欄位表,它幾乎囊括了一個異常發生時,能夠對異常周遭環境進行詳細描述的所有資訊。不同情況下,這些欄位並不一定都會收集,由於我們會採用文檔資料庫存儲日誌,因此,並不影響它的實際存儲結果。
2.2 異常捕獲
前端捕獲異常分為全局捕獲和單點捕獲。全局捕獲程式碼集中,易於管理;單點捕獲作為補充,對某些特殊情況進行捕獲,但分散,不利於管理。
a、全局捕獲
通過全局的介面,將捕獲程式碼集中寫在一個地方,可以利用的介面有:
- window.addEventListener(『error』) / window.addEventListener(「unhandledrejection」) / document.addEventListener(『click』) 等
- 框架級別的全局監聽,例如aixos中使用interceptor進行攔截,vue、react都有自己的錯誤採集介面
- 通過對全局函數進行封裝包裹,實現在在調用該函數時自動捕獲異常
- 對實例方法重寫(Patch),在原有功能基礎上包裹一層,例如對console.error進行重寫,在使用方法不變的情況下也可以異常捕獲
b、單點捕獲
在業務程式碼中對單個程式碼塊進行包裹,或在邏輯流程中打點,實現有針對性的異常捕獲:
- try…catch
- 專門寫一個函數來收集異常資訊,在異常發生時,調用該函數
- 專門寫一個函數來包裹其他函數,得到一個新函數,該新函數運行結果和原函數一模一樣,只是在發生異常時可以捕獲異常
2.3 跨域腳本異常
由於瀏覽器安全策略限制,跨域腳本報錯時,無法直接獲取錯誤的詳細資訊,只能得到一個Script Error。例如,我們會引入第三方依賴,或者將自己的腳本放在CDN時。
解決Script Error的方法:
方案一:
- 將js內聯到HTML中
- 將js文件與HTML放在同域下
方案二:
- 為頁面上script標籤添加crossorigin屬性
- 被引入腳本所在服務端響應頭中,增加 Access-Control-Allow-Origin 來支援跨域資源共享
2.4 異常錄製
對於一個異常,僅僅擁有該異常的資訊還不足以完全抓住問題的本質,因為異常發生的位置,並不一定是異常根源所在的位置。我們需要對異常現場進行還原,才能復原問題全貌,甚至避免類似問題在其他介面中發生。這裡需要引進一個概念,就是「異常錄製」。錄製通過「時間」「空間」兩個維度記錄異常發生前到發生的整個過程,對於找到異常根源更有幫助。

表示,當異常發生時,異常的根源可能離我們很遠,我們需要回到異常發生的現場,找到異常根源。就像現實生活中破案一樣,如果有監控攝影機對案發過程的錄影,對破案來說更加容易。如果僅僅關注異常本身,要找到異常的根源,需要憑藉運氣,但有了異常錄製的幫助,找到根源就更加容易。
所謂的「異常錄製」,實際上就是通過技術手段,收集用戶的操作過程,對用戶的每一個操作都進行記錄,在發生異常時,把一定時間區間內的記錄重新運行,形成影像進行播放,讓調試者無需向用戶詢問,就能看到用戶當時的操作過程。

是來自阿里的一套異常錄製還原方案示意圖,用戶在介面上的操作產生的events和mutation被產品收集起來,上傳到伺服器,經過隊列處理按順序存放到資料庫中。當需要進行異常重現的時候,將這些記錄從資料庫中取出,採用一定的技術方案,順序播放這些記錄,即可實現異常還原。
2.5 異常級別
一般而言,我們會將收集資訊的級別分為info,warn,error等,並在此基礎上進行擴展。
當我們監控到異常發生時,可以將該異常劃分到「重要——緊急」模型中分為A、B、C、D四個等級。有些異常,雖然發生了,但是並不影響用戶的正常使用,用戶其實並沒有感知到,雖然理論上應該修復,但是實際上相對於其他異常而言,可以放在後面進行處理。

文會討論告警策略,一般而言,越靠近右上角的異常會越快通知,保證相關人員能最快接收到資訊,並進行處理。A級異常需要快速響應,甚至需要相關負責人知悉。
在收集異常階段,可根據第一節劃分的異常後果來判斷異常的嚴重程度,在發生異常時選擇對應的上報方案進行上報。
3 整理與上報方案
前文已經提到,除了異常報錯資訊本身,我們還需要記錄用戶操作日誌,以實現場景復原。這就涉及到上報的量和頻率問題。如果任何日誌都立即上報,這無異於自造的DDOS攻擊。因此,我們需要合理的上報方案。下文會介紹4種上報方案,但實際我們不會僅限於其中一種,而是經常同時使用,對不同級別的日誌選擇不同的上報方案。
3.1 前端存儲日誌
我們前面提到,我們並不單單採集異常本身日誌,而且還會採集與異常相關的用戶行為日誌。單純一條異常日誌並不能幫助我們快速定位問題根源,找到解決方案。但如果要收集用戶的行為日誌,又要採取一定的技巧,而不能用戶每一個操作後,就立即將該行為日誌傳到伺服器,對於具有大量用戶同時在線的應用,如果用戶一操作就立即上傳日誌,無異於對日誌伺服器進行DDOS攻擊。因此,我們先將這些日誌存儲在用戶客戶端本地,達到一定條件之後,再同時打包上傳一組日誌。
那麼,如何進行前端日誌存儲呢?我們不可能直接將這些日誌用一個變數保存起來,這樣會擠爆記憶體,而且一旦用戶進行刷新操作,這些日誌就丟失了,因此,我們自然而然想到前端數據持久化方案。
目前,可用的持久化方案可選項也比較多了,主要有:Cookie、localStorage、sessionStorage、IndexedDB、webSQL 、FileSystem 等等。那麼該如何選擇呢?我們通過一個表來進行對比:
存儲方式 |
cookie |
localStorage |
sessionStorage |
IndexedDB |
webSQL |
FileSystem |
---|---|---|---|---|---|---|
類型 |
|
key-value |
key-value |
NoSQL |
SQL |
|
數據格式 |
string |
string |
string |
object |
|
|
容量 |
4k |
5M |
5M |
500M |
60M |
|
進程 |
同步 |
同步 |
同步 |
非同步 |
非同步 |
|
檢索 |
|
key |
key |
key, index |
field |
|
性能 |
|
讀快寫慢 |
|
讀慢寫快 |
|
|
綜合之後,IndexedDB是最好的選擇,它具有容量大、非同步的優勢,非同步的特性保證它不會對介面的渲染產生阻塞。而且IndexedDB是分庫的,每個庫又分store,還能按照索引進行查詢,具有完整的資料庫管理思維,比localStorage更適合做結構化數據管理。但是它有一個缺點,就是api非常複雜,不像localStorage那麼簡單直接。針對這一點,我們可以使用hello-indexeddb這個工具,它用Promise對複雜api進行來封裝,簡化操作,使IndexedDB的使用也能做到localStorage一樣便捷。另外,IndexedDB是被廣泛支援的HTML5標準,兼容大部分瀏覽器,因此不用擔心它的發展前景。
接下來,我們究竟應該怎麼合理使用IndexedDB,保證我們前端存儲的合理性呢?

上圖展示了前端存儲日誌的流程和資料庫布局。當一個事件、變動、異常被捕獲之後,形成一條初始日誌,被立即放入暫存區(indexedDB的一個store),之後主程式就結束了收集過程,後續的事只在webworker中發生。在一個webworker中,一個循環任務不斷從暫存區中取出日誌,對日誌進行分類,將分類結果存儲到索引區中,並對日誌記錄的資訊進行豐富,將最終將會上報到服務端的日誌記錄轉存到歸檔區。而當一條日誌在歸檔區中存在的時間超過一定天數之後,它就已經沒有價值了,但是為了防止特殊情況,它被轉存到回收區,再經歷一段時間後,就會被從回收區中清除。
3.2 前端整理日誌
上文講到,在一個webworker中對日誌進行整理後存到索引區和歸檔區,那麼這個整理過程是怎樣的呢?
由於我們下文要講的上報,是按照索引進行的,因此,我們在前端的日誌整理工作,主要就是根據日誌特徵,整理出不同的索引。我們在收集日誌時,會給每一條日誌打上一個type,以此進行分類,並創建索引,同時通過object-hashcode計算每個log對象的hash值,作為這個log的唯一標誌。
- 將所有日誌記錄按時序存放在歸檔區,並將新入庫的日誌加入索引
- BatchIndexes:批量上報索引(包含性能等其他日誌),可一次批量上報100條
- MomentIndexes:即時上報索引,一次全部上報
- FeedbackIndexes:用戶回饋索引,一次上報一條
- BlockIndexes:區塊上報索引,按異常/錯誤(traceId,requestId)分塊,一次上報一塊
- 上報完成後,被上報過的日誌對應的索引刪除
- 3天以上日誌進入回收區
- 7天以上的日誌從回收區清除
rquestId:同時追蹤前後端日誌。由於後端也會記錄自己的日誌,因此,在前端請求api的時候,默認帶上requestId,後端記錄的日誌就可以和前端日誌對應起來。
traceId:追蹤一個異常發生前後的相關日誌。當應用啟動時,創建一個traceId,直到一個異常發生時,刷新traceId。把一個traceId相關的requestId收集起來,把這些requestId相關的日誌組合起來,就是最終這個異常相關的所有日誌,用來對異常進行復盤。

圖舉例展示了如何利用traceId和requestId找出和一個異常相關的所有日誌。在上圖中,hash4是一條異常日誌,我們找到hash4對應的traceId為traceId2,在日誌列表中,有兩條記錄具有該traceId,但是hash3這條記錄並不是一個動作的開始,因為hash3對應的requestId為reqId2,而reqId2開始於hash2,因此,我們實際上要把hash2也加入到該異常發生的整個復盤備選記錄中。總結起來就是,我們要找出同一個traceId對應的所有requestId對應的日誌記錄,雖然有點繞,但稍理解就可以明白其中的道理。
我們把這些和一個異常相關的所有日誌集合起來,稱為一個block,再利用日誌的hash集合,得出這個block的hash,並在索引區中建立索引,等待上報。
3.3 上報日誌
上報日誌也在webworker中進行,為了和整理區分,可以分兩個worker。上報的流程大致為:在每一個循環中,從索引區取出對應條數的索引,通過索引中的hash,到歸檔區取出完整的日誌記錄,再上傳到伺服器。
按照上報的頻率(重要緊急度)可將上報分為四種:
a. 即時上報
收集到日誌後,立即觸發上報函數。僅用於A類異常。而且由於受到網路不確定因素影響,A類日誌上報需要有一個確認機制,只有確認服務端已經成功接收到該上報資訊之後,才算完成。否則需要有一個循環機制,確保上報成功。
b. 批量上報
將收集到的日誌存儲在本地,當收集到一定數量之後再打包一次性上報,或者按照一定的頻率(時間間隔)打包上傳。這相當於把多次合併為一次上報,以降低對伺服器的壓力。
c. 區塊上報
將一次異常的場景打包為一個區塊後進行上報。它和批量上報不同,批量上報保證了日誌的完整性,全面性,但會有無用資訊。而區塊上報則是針對異常本身的,確保單個異常相關的日誌被全部上報。
d. 用戶主動提交
在介面上提供一個按鈕,用戶主動回饋bug。這有利於加強與用戶的互動。
或者當異常發生時,雖然對用戶沒有任何影響,但是應用監控到了,彈出一個提示框,讓用戶選擇是否願意上傳日誌。這種方案適合涉及用戶隱私數據時。
|
即時上報 |
批量上報 |
區塊上報 |
用戶回饋 |
---|---|---|---|---|
時效 |
立即 |
定時 |
稍延時 |
延時 |
條數 |
一次全部上報 |
一次100條 |
單次上報相關條目 |
一次1條 |
容量 |
小 |
中 |
– |
– |
緊急 |
緊急重要 |
不緊急 |
不緊急但重要 |
不緊急 |
即時上報雖然叫即時,但是其實也是通過類似隊列的循環任務去完成的,它主要是儘快把一些重要的異常提交給監控系統,好讓運維人員發現問題,因此,它對應的緊急程度比較高。
批量上報和區塊上報的區別:批量上報是一次上報一定條數,比如每2分鐘上報1000條,直到上報完成。而區塊上報是在異常發生之後,馬上收集和異常相關的所有日誌,查詢出哪些日誌已經由批量上報上報過了,剔除掉,把其他相關日誌上傳,和異常相關的這些日誌相對而言更重要一些,它們可以幫助儘快復原異常現場,找出發生異常的根源。
用戶提交的回饋資訊,則可以慢悠悠上報上去。
為了確保上報是成功的,在上報時需要有一個確認機制,由於在服務端接收到上報日誌之後,並不會立即存入資料庫,而是放到一個隊列中,因此,前後端在確保日誌確實已經記錄進資料庫這一點上需要再做一些處理。

圖展示了上報的一個大致流程,在上報時,先通過hash查詢,讓客戶端知道準備要上報的日誌集合中,是否存在已經被服務端保存好的日誌,如果已經存在,就將這些日誌去除,避免重複上報,浪費流量。
3.4 壓縮上報數據
一次性上傳批量數據時,必然遇到數據量大,浪費流量,或者傳輸慢等情況,網路不好的狀態下,可能導致上報失敗。因此,在上報之前進行數據壓縮也是一種方案。
對於合併上報這種情況,一次的數據量可能要十幾k,對於日 pv 大的站點來說,產生的流量還是很可觀的。所以有必要對數據進行壓縮上報。lz-string是一個非常優秀的字元串壓縮類庫,兼容性好,程式碼量少,壓縮比高,壓縮時間短,壓縮率達到驚人的60%。但它基於LZ78壓縮,如果後端不支援解壓,可選擇gzip壓縮,一般而言後端會默認預裝gzip,因此,選擇gzip壓縮數據也可以,工具包pako中自帶了gzip壓縮,可以嘗試使用。
4 日誌接收與存儲
4.1 接入層與消息隊列
一般通過提供獨立的日誌伺服器接收客戶端日誌,接收過程中,要對客戶端日誌內容的合法性、安全性等進行甄別,防止被人攻擊。而且由於日誌提交一般都比較頻繁,多客戶端同時並發的情況也常見。通過消息隊列將日誌資訊逐一處理後寫入到資料庫進行保存也是比較常用的方案。

圖為騰訊BetterJS的架構圖,其中「接入層」和「推送中心」就是這裡提到的接入層和消息隊列。BetterJS將整個前端監控的各個模組進行拆分,推送中心承擔了將日誌推送到存儲中心進行存儲和推送給其他系統(例如告警系統)的角色,但我們可以把接收日誌階段的隊列獨立出來看,在接入層和存儲層之間做一個過渡。
4.2 日誌存儲系統
存儲日誌是一個臟活累活,但是不得不做。對於小應用,單庫單表加優化就可以應付。一個成規模的應用,如果要提供更標準高效的日誌監控服務,常常需要在日誌存儲架構上下一些功夫。目前業界已經有比較完備的日誌存儲方案,主要有:Hbase系,Dremel系,Lucene系等。總體而言,日誌存儲系統主要面對的問題是數據量大,數據結構不規律,寫入並發高,查詢需求大等。一般一套日誌存儲系統,要解決上面這些問題,就要解決寫入的緩衝,存儲介質按日誌時間選擇,為方便快速讀取而設計合理的索引系統等等。
由於日誌存儲系統方案比較成熟,這裡就不再做更多討論。
4.3 搜索
日誌的最終目的是要使用,由於一般日誌的體量都非常大,因此,要在龐大的數據中找到需要的日誌記錄,需要依賴比較好的搜索引擎。Splunk是一套成熟的日誌存儲系統,但它是付費使用的。按照Splunk的框架,Elk是Splunk的開源實現,Elk是ElasticSearch、Logstash、Kibana的結合,ES基於Lucene的存儲、索引的搜索引擎;logstash是提供輸入輸出及轉化處理插件的日誌標準化管道;Kibana提供可視化和查詢統計的用戶介面。
5 日誌統計與分析
一個完善的日誌統計分析工具需要提供各方面方便的面板,以可視化的方式給日誌管理員和開發者回饋資訊。
5.1 用戶緯度
同一個用戶的不同請求實際上會形成不同的story線,因此,針對用戶的一系列操作設計唯一的request id是有必要的。同一個用戶在不同終端進行操作時,也能進行區分。用戶在進行某個操作時的狀態、許可權等資訊,也需要在日誌系統中予以反應。
5.2 時間維度
一個異常是怎麼發生的,需要將異常操作的前後story線串聯起來觀察。它不單單涉及一個用戶的一次操作,甚至不限於某一個頁面,而是一連串事件的最終結果。
5.3 性能維度
應用運行過程中的性能情況,例如,介面載入時間,api請求時長統計,單元計算的消耗,用戶獃滯時間。
5.4 運行環境維度
應用及服務所運行的環境情況,例如應用所在的網路環境,作業系統,設備硬體資訊等,伺服器cpu、記憶體狀況,網路、寬頻使用情況等。
5.4 細粒度程式碼追蹤
異常的程式碼stack資訊,定位到發生異常的程式碼位置和異常堆棧。
5.6 場景回溯
通過將異常相關的用戶日誌連接起來,以動態的效果輸出發生異常的過程。
6 監控與通知
對異常進行統計和分析只是基礎,而在發現異常時可以推送和告警,甚至做到自動處理,才是一個異常監控系統應該具備的能力。
6.1 自定義觸發條件的告警
a. 監控實現
當日誌資訊進入接入層時,就可以觸發監控邏輯。當日誌資訊中存在較為高級別的異常時,也可以立即出發告警。告警消息隊列和日誌入庫隊列可以分開來管理,實現並行。
對入庫日誌資訊進行統計,對異常資訊進行告警。對監控異常進行響應。所謂監控異常,是指:有規律的異常一般而言都比較讓人放心,比較麻煩的是突然之間的異常。例如在某一時段突然頻繁接收到D級異常,雖然D級異常是不緊急一般重要,但是當監控本身發生異常時,就要提高警惕。
b. 自定義觸發條件
除了系統開發時配置的默認告警條件,還應該提供給日誌管理員可配置的自定義觸發條件。
- 日誌內含有什麼內容時
- 日誌統計達到什麼度、量時
- 向符合什麼條件的用戶告警
6.2 推送渠道
可選擇的途徑有很多,例如郵件、簡訊、微信、電話。
6.3 推送頻率
針對不同級別的告警,推送的頻率也可以進行設定。低風險告警可以以報告的形式一天推送一次,高風險告警10分鐘循環推送,直到處理人手動關閉告警開關。
6.4 自動報表
對於日誌統計資訊的推送,可以做到自動生成日報、周報、月報、年報並郵件發送給相關群組。
6.5 自動產生bug工單
當異常發生時,系統可以調用工單系統API實現自動生成bug單,工單關閉後回饋給監控系統,形成對異常處理的追蹤資訊進行記錄,在報告中予以展示。
7 修復異常
7.1 sourcemap
前端程式碼大部分情況都是經過壓縮後發布的,上報的stack資訊需要還原為源碼資訊,才能快速定位源碼進行修改。
發布時,只部署js腳本到伺服器上,將sourcemap文件上傳到監控系統,在監控系統中展示stack資訊時,利用sourcemap文件對stack資訊進行解碼,得到源碼中的具體資訊。
但是這裡有一個問題,就是sourcemap必須和正式環境的版本對應,還必須和git中的某個commit節點對應,這樣才能保證在查異常的時候可以正確利用stack資訊,找到出問題所在版本的程式碼。這些可以通過建立CI任務,在集成化部署中增加一個部署流程,以實現這一環節。
7.2 從告警到預警
預警的本質是,預設可能出現異常的條件,當觸發該條件時異常並沒有真實發生,因此,可以趕在異常發生之前對用戶行為進行檢查,及時修復,避免異常或異常擴大。
怎麼做呢?其實就是一個統計聚類的過程。將歷史中發生異常的情況進行統計,從時間、地域、用戶等不同維度加以統計,找出規律,並將這些規律通過演算法自動加入到預警條件中,當下次觸發時,及時預警。
7.3 智慧修復
自動修復錯誤。例如,前端要求介面返回數值,但介面返回了數值型的字元串,那麼可以有一種機制,監控系統發送正確數據類型模型給後端,後端在返回數據時,根據該模型控制每個欄位的類型。
8 異常測試
8.1 主動異常測試
撰寫異常用例,在自動化測試系統中,加入異常測試用戶。在測試或運行過程中,每發現一個異常,就將它加入到原有的異常用例列表中。
8.2 隨機異常測試
模擬真實環境,在模擬器中模擬真實用戶的隨機操作,利用自動化腳本產生隨機操作動作程式碼,並執行。
定義異常,例如彈出某個彈出框,包含特定內容時,就是異常。將這些測試結果記錄下來,再聚類統計分析,對防禦異常也很有幫助。
9 部署
9.1 多客戶端
一個用戶在不同終端上登錄,或者一個用戶在登錄前和登錄後的狀態。通過特定演算法生成requestID,通過該requestId可以確定某個用戶在獨立客戶端上的一系列操作,根據日誌時序,可以梳理出用戶產生異常的具體路徑。
9.2 集成便捷性
前端寫成包,全局引用即可完成大部分日誌記錄、存儲和上報。在特殊邏輯裡面,可以調用特定方法記錄日誌。
後端與應用本身的業務程式碼解耦,可以做成獨立的服務,通過介面和第三方應用交互。利用集成部署,可以將系統隨時進行擴容、移植等操作。
9.3 管理系統的可擴展
整套系統可擴展,不僅服務單應用,可支援多個應用同時運行。同一個團隊下的所有應用都可以利用同一個平台進行管理。
9.4 日誌系統許可權
不同的人在訪問日誌系統時許可權不同,一個訪問者只能查看自己相關的應用,有些統計數據如果比較敏感,可以單獨設置許可權,敏感數據可脫敏。
10 其他
10.1 性能監控
異常監控主要針對程式碼級別的報錯,但也應該關注性能異常。性能監控主要包括:
- 運行時性能:文件級、模組級、函數級、演算法級
- 網路請求速率
- 系統性能
10.2 API Monitor
後端API對前端的影響也非常大,雖然前端程式碼也控制邏輯,但是後端返回的數據是基礎,因此對API的監控可以分為:
- 穩定性監控
- 數據格式和類型
- 報錯監控
- 數據準確性監控
10.3 數據脫敏
敏感數據不被日誌系統採集。由於日誌系統的保存是比較開放的,雖然裡面的數據很重要,但是在存儲上大部分日誌系統都不是保密級,因此,如果應用涉及了敏感數據,最好做到:
- 獨立部署,不和其他應用共享監控系統
- 不採集具體數據,只採集用戶操作數據,在重現時,通過日誌資訊可以取出數據api結果來展示
- 日誌加密,做到軟硬體層面的加密防護
- 必要時,可採集具體數據的ID用於調試,場景重現時,用mock數據替代,mock數據可由後端採用假的數據源生成
- 對敏感數據進行混淆
結語
本文主要是對前端異常監控的整體框架進行了研究,沒有涉及到具體的技術實現,涉及前端部分和後台部分以及與整個問題相關的一些知識點,主要關注前端部分,它和後端的監控有重疊部分也有分支部分,需要在一個項目中不斷實踐,總結出項目本身的監控需求和策略。
從這些嚴格模式規則,你就可以一窺當中的奧秘,今天開嚴格,他日Bug秒甩鍋,噢耶。
也可以來我的GitHub
部落格里拿所有文章的源文件:
前端勸退指南:https://github.com/roger-hiro/BlogFN