應用實時監控 ARMS 上線用戶行爲回溯功能

隨着前端技術突飛猛進迅猛發展,爲了實現更好的前端性能,最大程度提升用戶體驗,支持單頁應用的框架逐漸佔領市場,如衆所周知的React,Vue等等。可是在單頁應用的趨勢下,快速定位並解決JS錯誤卻成爲一大難題。在當下的互聯網行業,對前端性能要求愈來愈高,前端性能監控的產品層出不窮,javascript錯誤診斷更是其中舉足輕重的一個環節。幫助開發者排查線上bug,實現快速定位問題,高效解決問題,是咱們努力的方向。javascript

1、JS錯誤診斷

目前已經有了許多諸如Arms,Sentry等前端性能監控框架,都在必定程度上對JS錯誤診斷提供了相應的 支持,整體來講,你們的思路比較類似,能夠總結爲如下幾個步驟:html

1. 統計JS錯誤

每當上線一個新的產品或新的業務功能,每每伴隨着一些不可避免的線上bug,這些bug發生的頻率有多高、發生在什麼頁面、影響了多少用戶等等,對於判斷解決問題的優先級、幫助排查問題和優化性能來講是很是關鍵的。前端

圖1是Arms前端性能監控的JS錯誤診斷診斷頁面,能夠清晰地看到錯誤發生的次數、影響用戶數以及錯誤分佈狀況等信息,開發者能夠根據這些統計數據更好地決策問題的輕重緩急,使性能優化有條不紊進行。java

 

圖1. JS錯誤總覽web

2. 精準定位錯誤

前端報錯的緣由有不少,網絡因素、瀏覽器兼容性、用戶操做邏輯、業務代碼自己的問題等等均可能致使故障發生,在JS錯誤統計中咱們已經知道了錯誤發生的頁面,這在必定程度上縮小了排查範圍,但這樣仍是不夠的,咱們還想要知道更多錯誤詳情,好比:ajax

1)現場信息

包括報錯的設備、操做系統、瀏覽器等等,這些信息無疑能夠幫助開發者更好地復現問題,進而修復bug。後端

圖2. JS錯誤概要api

2)代碼追蹤

利用error stack和source map來精準定位發生錯誤的代碼位置。跨域

a. 上傳source map文件瀏覽器

b. 在代碼中定位錯誤

圖3. source map錯誤定位

3. 還原報錯現場

還原錯誤發生時用戶行爲上下文是JS錯誤診斷的最後一步,也是最關鍵和最困難的一步。

假設如下場景:咱們收到用戶反饋說點了某按鈕後沒有反應……

面對用戶反饋,咱們不禁得會想,沒反應是什麼緣由呢,是點擊事件的handler出錯了致使接口請求沒發出?仍是接口掛了?或者是接口返回數據渲染失敗了?

相似以上的困惑應該不少開發者都會時常遇到,咱們不可能去指揮用戶打開開發者工具再一步步debug給咱們看,這就須要咱們的前端監控平臺具有還原報錯現場的能力,幫助開發者瞭解錯誤發生時候的用戶行爲上下文,進而能夠預想一下剛纔的場景——

根據用戶的uid等信息,咱們能夠回溯到該用戶報錯先後的一系列操做以及前端行爲:
進入頁面->點擊按鈕->發出api請求成功-> js error……

因而咱們能夠知道報錯是發生在api請求成功後的數據處理環節,再依據步驟2中提供的錯誤詳情快速解決問題。

2、用戶行爲回溯

已經有一些前端性能監控平臺接入了用戶行爲監控,實現的方式也各有千秋,主要流程能夠分爲三個步驟:行爲採集、行爲上報、行爲回溯。

1. 行爲採集

1)哪些行爲須要採集?

咱們站在用戶的立場去考慮一個單頁應用的瀏覽週期內的可能流程:進入應用首頁——加載頁面內容——瀏覽頁面內容——用戶交互(鼠標交互/鍵盤交互等)——跳轉到新頁面……

要將用戶行爲串聯成完整的行爲鏈來爲js error提供上下文,咱們須要知道什麼時間,什麼位置,發生了什麼事情。由此,以上用戶瀏覽過程當中的全部的頁面行爲(包括但不只限於用戶交互)能夠用如下幾類來大體歸納:

Api請求,鼠標事件,鍵盤事件,路由跳轉,error 等。

2)如何採集?

在肯定了哪些行爲須要上報之後,咱們再在來看如何完成行爲打點。

在過往的時代,咱們有傳統的手動埋點方法,它的缺點也是不言而喻的:手動埋點是容易混亂的,有時可能會出現錯埋、漏埋等狀況,往往上線一個新功能,須要開發團隊和數據團隊進行埋點溝通,徒增了時間和人力成本;其次時常由於產品排期緊張,功能急於上線,就不得不先砍掉了埋點的需求,在後續的版本更新中再補上,這使得新上線的功能得不到驗證,而新上線的功能對業務和產品性能的影響都很關鍵。

現現在,一個好的前端監控產品須要實現「無埋點」監控,充當一雙眼睛,時時刻刻監控着產品的運做狀況,全量採集頁面事件和用戶行爲,爲業務分析和錯誤診斷都提供充足的信息。

圖4. 用戶行爲採集流程圖

以上流程圖爲Arms作行爲採集的大體步驟,首先須要在正常的html頁面中插入一小段js代碼,即引入咱們具備行爲日誌採集功能的SDK,以下代碼所示,經過createElement(「script」)在Dom節點添加script的元素,並將SDK的js文件引入進來,用於收集用戶行爲,並在適當的時候上報到後端,具體方法以下代碼所示,其中bl.js爲SDK文件。

<script>
!(function(c,b,d,a){c[a]||(c[a]={});c[a].config={pid:"xxxxxxx",appType:"web",imgUrl:"https://arms-retcode.aliyuncs.com/r.png?"};
with(b)with(body)with(insertBefore(createElement("script"),firstChild))setAttribute("crossorigin","",src=d)
})(window,document,"https://retcode.alicdn.com/retcode/bl.js","__bl");
</script>

3)SDK中行爲採集的實現

  • API行爲採集
    重寫原生XMLHttpRequest或fetch方法,採集必要的API信息(如url,狀態碼,狀態message等)。
reWrite(XMLHttpRequest.send,
  originalSend =>
    function(...args){
      const xhr = this;
      function onreadystatechangeHandler(){
        if (xhr.readyState === 4) {
          //記錄API行爲
        }
      }
      if ('onreadystatechange' in xhr && typeof xhr.onreadystatechange === 'function') {
        reWrite(xhr.onreadystatechange, original=> function(...innerargs){
          //記錄API行爲
          original.apply(this, innerargs);
        });
      } else {
        xhr.onreadystatechange = onreadystatechangeHandler;
      }
      return originalSend.apply(this, args);
});
  • 控制檯行爲採集
    重寫console的常見控制檯輸出方法,好比: log,warning,error等。
var consoleType = ['debug', 'info', 'warn', 'log', 'error', 'assert'];
for (var i = 0; consoleType.length; i++) {
    var type = consoleType[i];
    reWrite(console.type, function (orig) {
        var thisType = type;
        return function (...args) {
            // 添加console行爲
            if (orig) {
                Function.prototype.apply.call(orig, window.console, args);
            }
        };
    });
}

注意,經過重寫 console 對象監控瀏覽器控制檯的打印信息,這樣會致使在控制檯下打印的日誌沒法正確看到原代碼文件中的位置,console的位置會定位到SDK的代碼中,能夠經過配置瀏覽器 Blackboxing來解決。

圖5. console定位在SDK代碼中

  • 用戶交互行爲採集(鼠標鍵盤事件等): 
    能夠在頂層的document上全面監聽各種用戶交互事件,如click,keypress,mousemove,scroll等等。可是這種方法也有一個明顯的缺陷,假設用戶監聽了某個dom上的click事件,而且設置了event.stopPropagation(),這種狀況的點擊事件是沒法被document監聽到的,而每每這類行爲對於錯誤診斷和業務分析都尤其重要。解決方法是在addEventListener中埋入鉤子。
var bhEventHandler = function(){
  //記錄用戶行爲
}
document.addEventListener('click', bhEventHandler, false);
var types = ['EventTarget', 'Node'];
for (var i = 0; i < types.length; i++) {
    var type = types[i];
    var proto = window[type] && window[type].prototype;
    reWrite(proto.addEventListener, function (orig) {
        //重寫addEventListener,記錄用戶行爲
    });
}
  • 路由跳轉行爲採集
    路由跳轉無疑會觸發瀏覽器歷史記錄的改變,每當處於激活狀態的歷史記錄條目發生變化時,window的popstate事件會觸發,可是調用history.pushState()或者history.replaceState()不會觸發popstate事件。所以路由跳轉的監控能夠分爲兩個方面,一方面在window. onpopstate中埋入鉤子,另外一方面在history.pushState和history.replaceState中埋入鉤子。
var origPopstate = window.onpopstate;
window.onpopstate = function () {
    //記錄路由行爲    
    if (origPopstate) {
        return origPopstate.apply(this, args);
    }
};
var dosomething = function (orig) {
    return function (...args) {
        //記錄路由行爲    
        return orig.apply(this, args);
    };
};
reWrite(window.history.pushState, dosomething);
reWrite(window.history.replaceState, dosomething);
  • js error
    全局監聽js error:
window.addEventListener('error', function(event){...})

監聽全局未處理的rejection:

window.addEventListener('unhandledrejection', function(event){...})

2. 行爲上報

SDK採集到用戶行爲後,以必定的格式進行信息拼接,而後假裝成圖片發送給後端。爲何要使用圖片來發送日誌信息而不是直接使用ajax呢?這是由於SDK的script文件和後端分析的代碼可能不在相同的域內,而將image對象的src屬性指向後端腳本並攜帶參數,能夠輕鬆實現跨域請求。不一樣平臺對於前端監控行爲的類別都比較相似,但上報的方式仍是不盡相同的,服務於不一樣的業務需求。如下是兩種比較典型的行爲上報形式:

1)持續全量上報

圖6是截取的New relic的行爲日誌上報信息,爲何稱之爲持續全量上報呢?「持續」是指它的行爲日誌是定時上報的,每隔幾秒便會上報一次,上報請求很是密集。「全量」則是指它採集的行爲類型覆蓋面很廣,能夠從上報的Request Payload中看到,它採集的不只限於咱們上面提到的一些主要的頁面行爲,它幾乎覆蓋了全部的頁面事件,包括鼠標移動等等。而這種上報方式也是利弊參半:

優勢:它能夠更真實地還原用戶在整個頁面週期內的行爲,保真度很高,對於還原現場來講是有必定優點的。

缺陷:首先是無差異的行爲採集,這顯然增大了行爲日誌的數據體量,在上報中對流量的消耗很大,日誌存儲成本也很高,而這巨大的損耗所帶來的信息價值呢?幾乎大部分都是鼠標移動或者滑輪滾動的大量重複信息,不管是對業務分析仍是錯誤診斷都沒有太大貢獻,甚至增長了分析問題的複雜性。

其次是行爲日誌的頻繁上報,這對業務方來講可能也是不太友好的,性能監控請求發的比自己的業務請求都多。

圖6. 行爲日誌全量上報

2)場景觸發上報

場景觸發上報便是指,當符合必定條件或者場景時才上報行爲日誌,對於JS錯誤診斷中的用戶行爲回溯場景而言,固然就是錯誤觸發上報,當頁面監聽到error時,便把當前採集到的行爲列表伴隨error詳情一併上報,做爲錯誤診斷的輔助信息。

行爲日誌伴隨錯誤一塊兒上報的方式須要維護一個行爲隊列,隊列應設有最大長度,當捕獲到js error的時候,將行爲隊列做爲js錯誤日誌的一個補充信息一塊兒上報,圖7所示爲sentry錯誤日誌上報的request payload,其用戶行爲隊列包含在錯誤日誌的「breadcrumbs」字段中。

這種用戶行爲伴隨錯誤上報的方式就相對輕量不少,首先錯誤和行爲一塊兒上報減小了請求數量;其次對用戶行爲選擇性採集,避免了大量冗餘信息對流量和存儲的消耗,就js錯誤診斷而言會使得錯誤現場變得更清晰,對分析錯誤有利。

固然這種行爲上報的方式也是有不可忽視的問題,即行爲和js error的強耦合,使得用戶行爲信息失去了業務分析的擴展性,僅適用於JS錯誤診斷。

圖7. 行爲日誌breadcrumbs上報

3、用戶行爲回溯在js錯誤診斷中的應用

圖8展現了Arms前端性能監控的JS錯誤診斷中用戶行爲回溯的狀況,在錯誤詳情中復原出錯現場,輔助排查。下圖展現了用戶在進行了「接口請求-控制檯輸出-點擊按鈕-控制檯輸出」的一系列行爲後發生了JS錯誤,便可從按鈕的點擊事件的處理函數中進行錯誤排查。

圖8. Arms前端性能監控的用戶行爲回溯


原文連接 本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索