頁面在使用者那裡運作,如果10%的使用者頁面出現問題而自己本地沒有辦法重制? 如何先一步了解到前端出現的問題,而不是等使用者回報? 能不能像檢視服務端日志一樣來定位前端頁面運作的問題?
當一個前端頁面釋出出去了之後,頁面所運作的裝置、浏覽器、網絡環境、使用者操作習慣等等因素都可能是造成頁面不正常的原因。
是以對前端頁面需要做一定的監控,而最可行的前端監控方式就是将頁面的日志選擇上報到監控日志伺服器中。
對業務邏輯的執行收集了日志資料之後可以參數的形式構造一個url,再通過一個<code>Image</code>請求發送到到伺服器就完成了日志的上報。
這樣一行代碼就搞定了日志的上報,然鵝,在生産環境中,日志上報所延伸的問題要複雜很多。
日志上報最終是為了服務業務,監控業務的運作狀态,一般而言前端運作的場景中開發者最期望監控的不外乎頁面&API請求是否正常響應和頁面js邏輯是否正常執行。
為了覆寫這兩個監控目标,需要通過很多類型的日志來覆寫,還有一些特殊場景下,開發者還希望能與具體業務靈活結合,實作自定義上報。是以常見的日志類型如下
頁面&API請求是否正常響應
API調用日志 - API調用成功與否及其耗時
頁面性能日志 - 頁面連接配接耗時、首次渲染時間、資源加載耗時等
通路統計日志 - PV/UV,短時間内斷崖式的量變化很容易反應問題
頁面js邏輯是否正常執行
頁面穩定性日志 - 頁面加載和頁面互動産生的js error資訊
業務相關日志
自定義上報 - 某些業務邏輯的結果、速度、統計值等自定義内容
随着前端業務的壯大,日志監控上報的量會快速增加,監控的邏輯也會越來越複雜,而在生産環境中,前端監控的最基本原則是日志擷取和上報本身不能抛出異常或者影響頁面性能。
浏覽器的相容性,前端業務邏輯依賴,日志上報方式,日志上報效率,使用者操作習慣,網絡環境等因素都可能讓日志上報産生問題甚至影響業務。這些因素會給日志上報帶來可靠和性能兩方面的問題
浏覽器相容性
上報可靠性
日志采集sdk可能因為網絡原因無法加載,是以安全的方式是sdk注入的位置合理的靠後,那麼頁面打開到sdk初始化這段時間就會産生漏報;
後端為了業務分離,通常會獨立設定一個日志采集伺服器,這種情況下日志上報可能會遇到跨域問題;
使用者的頻繁操作和關閉頁面會可能造成很多已經收集的資料漏報。
在一個複雜站點中,這些日志資料可能會非常多,上報可能會因為浏覽器并發數量的限制阻塞業務的網絡請求,或者影響頁面性能。
為了避免影響業務,那麼理所當然,為了不占用業務計算資源,日志上報需要單獨設定後端服務。
同時也不能使用與業務相同的域名,這跟頁面盡量使用CDN引入資源的原理相似,浏覽器會對同一個域名有一定的并發數限制。
而頁面性能、資源加載、初始化API、PV/UV、初始化js邏輯錯誤等日志都是頁面初始化的時候觸發上報,這種短時間大量的上報可能會造成網絡請求延時。例如chrome對同一個域名的最大并發連接配接數為6個,如果日志同時上報了6次以上,就會對同域名的業務造成影響;更壞的情況如頁面有一些錯誤、網絡連接配接品質品質不高會讓日志上報阻礙頁面渲染。
是以日志上報可以像使用CDN服務一樣,使用單獨域名和日志處理服務。
既然使用了不同的域名,那麼跨域問題随之而來,這需要前後端共同支援。伺服器需要允許外部通路<code>Access-Control-Allow-Origin:*</code>;前端在進行日志上報的時候要添加避免跨域辨別,如fetch方式:
不同域名一個性能缺點是增加首次DNS解析時間,不過可以通過在頁面添加DNS預解析來避免。
在資源隔離的基礎上,日志上報的異常處理也需要隔離,日志本身抛出的異常絕對不能和業務異常混在一起上報。
進行充分測試的前提下,最簡單粗暴的方式是在整個監控sdk外面添加<code>try...catch...</code>,好處是永遠不會出現sdk本身錯誤上報,不過同時也讓開發者失去了發現sdk問題的途徑。是以兩者兼得的方式是必要的。
這裡提供一個關鍵子產品埋點的方法,它對整個前端監控sdk多個關鍵點上埋點并且收集的結果中隻标記是否成功。話不多說,直接上示例代碼:
壓縮之前重新審視一下<code>(new Image).src</code>的日志發送方式:
HTTP Request: 前端日志資料以多組<code>key=value</code>的字元串形式接在一個Image資源請求的url後面,前端發送Image請求。 HTTP Responce: 伺服器傳回響應結果或者空圖檔。
日志資料直接放到url中的好處是網絡傳輸效率高。然而url長度是有限制的,例如IE浏覽器是2083個字元,同時伺服器也會對url長度進行限制。
類似如下的js error資訊就沒有辦法完整上報,
不僅僅是js error的錯誤棧深還因為urlencode對特殊字元和漢字的轉碼,這兩個因素會使url長度輕松突破限制。
另外業務邏輯實際上不關注而且也應關注日志上報的響應結果,是以這個請求的結果應該盡可能省去。
針對封包壓縮有以下方式:
http請求中,每次請求都會傳輸一系列的請求頭來描述請求的資源及其特性,然而實際上每次請求都有很多相同的值,如<code>Host:,user-agent:,Accept</code>等。這些資料能夠占用到300-800byte的傳輸量,如果攜帶大的cookie,請求頭甚至可以占用1kb的空間,而實際真正需要上報的日志資料僅僅隻有10~50byte的大小。在HTTP 1.x中,每次日志上報請求頭都攜帶了大量的重複資料導緻性能浪費。
HTTP/1.1效果HTTP/2.0效果![]()
如何優雅地上報前端監控日志前端日志上報可以很簡單日志上報帶來的問題更優雅的上報姿勢小結 頭部壓縮後每條日志請求的size都大大減小,響應的速度也有提升。![]()
如何優雅地上報前端監控日志前端日志上報可以很簡單日志上報帶來的問題更優雅的上報姿勢小結
最需要壓縮即js error的錯誤棧,錯誤棧當中占位最多是錯誤定位的檔案位址,而很多錯誤棧有很多相同的檔案,壓縮空間就來源于stack中js檔案的url重複。
一個典型的jserror stack經常會出現這種形式如下:
可考慮把檔案url抽取出來單獨作為一個字典,那麼上報内容可縮減為
即可大大縮減日志長度。
日志上報本身隻關注日志有沒有上報,而對上報請求的傳回内容并不關注,甚至完全可以不需要傳回内容。是以使用HTTP HEAD的方式上報,并且傳回的響應體為空,避免響應體傳輸資源損耗。
這時候隻需要設定一個nginx伺服器來記錄日志内容并傳回200狀态碼即可。
既然一個頁面上報的次數那麼多,一個更容易想到的idea應該是把日志合并上報來減小請求數量。
使用者浏覽器和日志伺服器之間産生多次HTTP請求,而在HTTP/1.1 Keep-Alive下,日志上報會以串行的方式傳輸,會讓後面的日志上報延時。通過HTTP/2的多路複用來合并上報,節省網絡連接配接的開銷。
POST請求因為request body可以有更大施展空間,在HTTP POST中隻要一次包含多條日志的内容,那麼相對于一條日志一次HTTP HEAD請求的方式會更加經濟。
在HTTP POST的基礎上,可以順便解決使用者關掉或者切換頁面造成的漏報問題。
以前常見的解決方式是監聽頁面的<code>unload</code>或者<code>beforeunload</code>事件,并以通過同步的<code>XMLHttpRequest</code>請求或者構造一個特定<code>src</code>的<code><img></code>标簽來延遲上報。
這種上報的缺點是會給下一個頁面的性能造成影響。更優雅的方式是使用<code>navigator.sendBeacon()</code>,它能夠異步地發送日志資料。
合并前合并後(navigator.sendBeacon)![]()
如何優雅地上報前端監控日志前端日志上報可以很簡單日志上報帶來的問題更優雅的上報姿勢小結 ![]()
如何優雅地上報前端監控日志前端日志上報可以很簡單日志上報帶來的問題更優雅的上報姿勢小結 理想情況下,合并n個日志上報耗費的總時間能達到原來的1/n![]()
如何優雅地上報前端監控日志前端日志上報可以很簡單日志上報帶來的問題更優雅的上報姿勢小結
前端業務場景和浏覽器的相容性千差萬别,是以日志上報要相容多種方式;頁面生命周期、業務邏輯影響了日志是否可擷取和是否漏報,是以對應的日志類型和上報時機要嚴格把握;前端業務邏輯快速疊代且場景多樣,是以日志上報要做到與業務解耦合同時可以自定義上報...
這些大大小小的坑促使我們把前端日志監控沉澱為一個獨立且系統性的工程來做,在打磨這個工程的過程中我們同樣還在探索是否有更加高效、穩定的日志上報方式。
附:阿裡雲業務實時監控服務(ARMS)前端監控系介紹
<a href="https://www.aliyun.com/product/arms">業務實時監控服務 ARMS</a>
<a href="https://help.aliyun.com/document_detail/63796.html">應用監控</a>
<a href="https://help.aliyun.com/document_detail/58652.html">前端監控</a>
<a href="https://help.aliyun.com/document_detail/42790.html">自定義監控</a>