首頁  >  文章  >  web前端  >  初探埋點系統

初探埋點系統

coldplay.xixi
coldplay.xixi轉載
2020-09-15 16:24:552575瀏覽

初探埋點系統

相關學習推薦:javascript影片教學

#前言

最近雜七雜八的事情比較多,難得花時間彌補之前的系列,欠大家的埋設系列現在開始走起來

為什麼需要埋點系統

電影中

前端開發攻城獅開開心心的coding,非常自豪的進行了業務、UI 分離開發,各種設計模式、算法優化輪番上陣,代碼寫的Perfect(勞資代碼天下第一),沒有BUG,程序完美,兼容性No. 1,程式碼能打能抗品質高。下班輕鬆打卡,回家看娃。

現實中

實際上,開發環境與生產環境並不能等同,並且測試的過程再完善,依然會有漏測的情況存在。考慮到使用者使用客戶端環境、網路環境等等一系列的不確定因素存在。

所以在開發過程中一定要記得三大原則(我胡詌的

  1. 沒有完美的程式碼,只有沒發現的BUG
  2. 絕對不要相信測試環境,沒有一種測試環境都涵蓋所有線上情況
  3. 如果線上沒有一點回饋,不要懷疑,問題應該藏得很深、很深

什麼是埋點系統

埋點就像城市中的攝像頭,從產品的角度考慮,它可以監控到用戶在我們產品裡的行為軌跡,為產品的迭代、專案的穩定性提供依據,WHO、WHEN、WHERE、HOW、WHAT 是埋點擷取資料的基礎維度

對前端開發而言,可以監控頁面資源載入效能,異常等等,提供了頁面體驗和健康指數,為後續效能最佳化提供依據,及時回報異常和發生場景。從而能夠及時修正問題,提升專案品質等。

埋點可以大概分為三類:

  1. 無痕埋點 - 無差別收集頁面所有資訊包括頁面進出、事件點擊等等,需要進行資料沖洗才能取得有用資訊
  2. 視覺化埋點 - 根據產生的頁面結構取得特定點位,單獨埋點分析
  3. 業務程式碼手動埋點 - 根據具體複雜的業務,除掉上述兩種不能涵蓋的地方進行業務代碼埋點

程式碼埋點 視覺化埋點 無痕埋點
典型場景 無痕埋點無法覆寫到,例如需要業務資料 簡單規範的頁面場景 #簡單規範的頁面場景,
優勢 業務資料明確 開發成本低,營運人員可直接進行相關埋點配置 無需配置,資料可回溯
不足 資料不可回溯,開發成本高 無法關聯業務數據,資料不可回溯 資料量較大,無法關聯業務資料

大部分情況,我們可以透過無痕埋點收集到所有的資訊數據,再配合可視化埋點,能夠具體定位到某一個點位,這樣大部分的埋點資訊都據此分析出來。

在特殊情況下,可以多加上業務代碼手動埋點,處理一下特別的場景(大部分情況是走強業務與正常的點擊,刷新事件無關需要上報的信息)

埋點SDK 開發

埋點資料收集分析

  • 事件基本資料
    • 事件發生時間
    • 發生時頁面資訊快照
  • 頁面
    • 頁面PV,UV
    • 用戶頁面停留時間
    • 頁面跳轉事件
    • 頁面進入後台
    • 使用者離開頁面
  • 使用者資訊
    • 使用者uid
    • 使用者裝置指紋
    • 裝置資訊
    • ip
    • 定位
  • 使用者操作行為
    • 使用者點選
      • #點選目標
    ##頁面AJAX 請求
  • #請求成功
    • #請求失敗
    • 請求逾時
##頁面報錯

資源載入錯誤

JS 執行報錯
  • 資源載入新效能
    • 圖片腳本
    • 頁面載入效能
    • 上面的資料透過
    • 3 個維度來定義埋點事件
    • ·LEVEL : 描述埋點資料的日誌等級
  • INFO
  • :某些使用者操作,請求成功,資源載入等等正常的資料記錄
      ERROR
    • : JS報錯,介面報錯等等錯誤類型的資料記錄
        DEBUG
      • : 預留開發人員透過手動呼叫的方式回傳排除bug的資料記錄
      • WARN
      • : 預留開發人員透過手動呼叫的方式回傳非正常使用者行為的的資料記錄
      ##CATEGORY
    • :描述埋點資料的分類
    • TRACK
    • : 埋點SDK物件的生命週期管理整個埋點資料。
    • WILL_MOUNT
    • :sdk物件即將初始化加載,產生一個預設ID,追蹤全部相關事件
    • DID_MOUNTED
    • :sdk物件初始化完成,主要獲取設備指紋等等的非同步操作完成
    AJAX
  • : AJAX相關資料
  • ERROR
  • :頁面中的例外相關資料

PERFORMANCE: 關於效能相關資料

初探埋點系統#OPERATION
: 使用者操作相關資料

#EVENT_NAME
:具體的事件名稱

根據上述的維度,我們可以簡單設計如下的架構

##根據上圖的架構,再進行下面的具體程式碼開發

##在瀏覽器中現在主要有2種請求方式,一個是

XMLHttpRequest

, 一個是Fetch代理XMLHttpRequest<pre class="brush:php;toolbar:false;">function NewXHR() { var realXHR: any = new OldXHR(); // 代理模式里面有提到过 realXHR.id = guid() const oldSend = realXHR.send; realXHR.send = function (body) { oldSend.call(this, body) //记录埋点 } realXHR.addEventListener(&amp;#39;load&amp;#39;, function () { //记录埋点 }, false); realXHR.addEventListener(&amp;#39;abort&amp;#39;, function () { //记录埋点 }, false); realXHR.addEventListener(&amp;#39;error&amp;#39;, function () { //记录埋点 }, false); realXHR.addEventListener(&amp;#39;timeout&amp;#39;, function () { //记录埋点 }, false); return realXHR; }复制代码</pre>代理Fetch<pre class="brush:php;toolbar:false;"> const oldFetch = window.fetch; function newFetch(url, init) { const fetchObj = { url: url, method: method, body: body, } ajaxEventTrigger.call(fetchObj, AJAX_START); return oldFetch.apply(this, arguments).then(function (response) { if (response.ok) { //记录埋点 } else { //上报错误 } return response }).catch(function (error) { fetchObj.error = error //记录埋点 throw error }) }复制代码</pre>監聽頁面的

PV

UV

#在進入頁面時,我們透過演算法產生一個唯一

session id

,作為這次埋點行為的全域id,回報用戶id,裝置指紋,裝置資訊。在使用者未登入的情況下,透過裝置指紋來計算 UV,透過 session id計算 PV異常捕獲

異常就是乾擾程式的正常流程的不尋常事故

RUNTIME ERRORJS中可以通過

window.onerror
  • window.addEventListener('error', callback) 捕捉運行時異常,一般使用window.onerror
      ,它相容性更好。
    • window.onerror = function(message, url, lineno, columnNo, error) {    const lowCashMessage = message.toLowerCase()    if(lowCashMessage.indexOf(&#39;script error&#39;) > -1) {      return
          }    const detail = {      url: url    
            filename: filename,      columnNo: columnNo,      lineno: lineno,      stack: error.stack,      message: message
          }    //记录埋点}复制代码
      Script Error
    • 在這裡我們過濾了
    • Script Error, 它產生的原因主要是頁面中載入的第三方跨域腳本報錯,例如託管在第三方CDN 中的js
    • 腳本。這類問題比較難以檢驗。解決的方法有:
  • 開啟CORS(Cross Origin Resource Sharing,跨域資源共享),如下步驟

05f1bd78c9bf8ef485ba6d27b73916f72cacc6d41bbb37262a98f745aa00fbf0

修改Access-Control-Allow-Origin: * | 指定網域名稱

#使用###try catch###
  <script scr="crgt.js"></script> //加载crgt脚本,window.crgt = {getUser: () => string}
  try{      window.crgt.getUser();
  }catch(error) {      throw error // 输出正确的错误堆栈
  }复制代码
##########Promise reject#########js### 在非同步異常時無法通過###onerror### 方法捕獲,當Promise 物件在reject 時,同時並沒有進行處理時 會拋出一個 ###unhandledrejection### 的錯誤,並不會被上述的方法所捕獲,所以需要添加單獨的處理事件。 ###
window.addEventListener("unhandledrejection", event => {  throw event.reason
});复制代码

资源加载异常

在浏览器中,可以通过 window.addEventListener('error', callback) 的方式监听资源加载异常,比如 js 或者 css 脚本文件丢失。

window.addEventListener(&#39;error&#39;, (event) => {  if (event.target instanceof HTMLElement) {    const target = parseDom(event.target, [&#39;src&#39;]);    const detail = {      target: target,      path: parseXPath(target),
    }    //  记录埋点
  }
}, true)复制代码

监听用户行为

通过 addEventListener click 监听 click 事件

window.addEventListener(&#39;click&#39;, (event) => {    //记录埋点}, true)复制代码

在这里通过组件的 displaName 来定位元素的位置,displaName 表示组件的文件目录,比如 src/components/Form.js 文件导出的组件 FormItem 通过 babel plugin 自动添加属性 @components/Form.FormItem,或者使用者主动给组件添加 static 属性 displayName

页面路由变化

  • hashRouter 监听页面hash变化,对hash进行解析
window.addEventListener(&#39;hashchange&#39;, event => {  const { oldURL, newURL } = event;  const oldURLObj = url.parseUrl(oldURL);  const newURLObj = url.parseUrl(newURL);  const from = oldURLObj.hash && url.parseHash(oldURLObj.hash);  const to = newURLObj.hash && url.parseHash(newURLObj.hash);  if(!from && !to ) return;  // 记录埋点})复制代码

监听页面离开

通过 addEventListener beforeunload 监听离开页面事件

window.addEventListener(&#39;beforeunload&#39;, (event) => {    //记录埋点})复制代码

SDK 架构

class Observable {    constructor(observer) {
        observer(this.emit)
    }
    emit = (data) => {        this.listeners.forEach(listener => {
            listener(data)
        })
    }
    listeners = [];
    
    subscribe = (listener) => {        this.listeners.push(listeners);        return () => {            const index = this.listeners.indexOf(listener);            if(index === -1) {                return false
            }            
            this.listeners.splice(index, 1);            return true;
        }
     }
}复制代码
const clickObservable = new Observable((emit) => {    window.addEventListener(&#39;click&#39;, emit)
})复制代码

然而在处理 ajax,需要将多种数据组合在一起,需要进行 merg 操作,则显得没有那么优雅,也很难适应后续复杂的数据流的操作。

const ajaxErrorObservable = new Observable((emit) => {    window.addEventListener(AJAX_ERROR, emit)
})const ajaxSuccessObservable = new Observable((emit) => {    window.addEventListener(AJAX_SUCCESS, emit)
})const ajaxTimeoutObservable = new Observable((emit) => {    window.addEventListener(AJAX_TIMEOUT, emit)
})复制代码

可以选择 RxJS 来优化代码

export const ajaxError$ = fromEvent(window, &#39;AJAX_ERROR&#39;, true)export const ajaxSuccess$ = fromEvent(window, &#39;AJAX_SUCCESS&#39;, true)export const ajaxTimeout$ = fromEvent(window, &#39;AJAX_TIMEOUT&#39;, true)复制代码
ajaxError$.pipe(
    merge(ajaxSuccess$, ajaxTimeout$), 
    map(data=> (data) => ({category: &#39;ajax&#39;, data; data}))
    subscribe(data => console.log(data))复制代码

通过 merge, map 两个操作符完成对数据的合并和处理。

数据流

初探埋點系統

项目结构

  • core
    • event$ 数据流合并
    • snapshot 获取当前设备快照,例如urluserIDrouter
    • track 埋点类,组合数据流和日志。
  • logger
    • logger 日志类
      • info
      • warn
      • debug
      • error
  • observable
    • ajax
    • beforeUpload
    • opeartion
    • routerChange
    • logger
    • track

参考

  • www.alibabacloud.com/help/zh/doc…

结尾

自建埋点系统是一个需要前后端一起合作的事情,如果人力不足的情况下,建议使用第三方分析插件,例如 Sentry 就能足够满足大部分日常使用

但还是建议多了解,在第三方插件出现不能满足业务需求的时候,可以顶上。

想了解更多编程学习,敬请关注php培训栏目!

以上是初探埋點系統的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.im。如有侵權,請聯絡admin@php.cn刪除