欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

前端實(shí)現(xiàn)監(jiān)控SDK的實(shí)戰(zhàn)指南

 更新時(shí)間:2024年10月21日 10:41:11   作者:棋丶  
本文討論了前端監(jiān)控和數(shù)據(jù)統(tǒng)計(jì)的設(shè)計(jì)思路,包括錯(cuò)誤監(jiān)控、用戶(hù)行為日志、PV/UV統(tǒng)計(jì)等方面,介紹了數(shù)據(jù)采集、日志上報(bào)、日志查詢(xún)的流程,以及監(jiān)控錯(cuò)誤的類(lèi)型和用戶(hù)埋點(diǎn)統(tǒng)計(jì)的手段,同時(shí)提到了PV和UV的統(tǒng)計(jì)方法,需要的朋友可以參考下

監(jiān)控內(nèi)容

  • 錯(cuò)誤監(jiān)控
    如:瀏覽器兼容問(wèn)題、代碼bug、后端接口掛掉等問(wèn)題
  • 行為日志
    如常用的電商app,通過(guò)分析用戶(hù)瀏覽時(shí)間較長(zhǎng)頁(yè)面有哪些、常點(diǎn)擊按鈕有哪些等行為,通過(guò)分析用戶(hù)的行為定制不同策略引導(dǎo)用戶(hù)進(jìn)行購(gòu)買(mǎi)
  • PV/UV統(tǒng)計(jì)
    如:統(tǒng)計(jì)用戶(hù)訪(fǎng)問(wèn)頁(yè)面次數(shù),每天有多少用戶(hù)訪(fǎng)問(wèn)系統(tǒng)

 圍繞以上三點(diǎn)進(jìn)行設(shè)計(jì),主要流程如下:

數(shù)據(jù)采集:采集前端監(jiān)控的相關(guān)數(shù)據(jù),包括PV/UV、用戶(hù)行為、報(bào)錯(cuò)信息。

日志上報(bào):將采集到的數(shù)據(jù)發(fā)送給服務(wù)端。

日志查詢(xún):在后臺(tái)頁(yè)面中查詢(xún)采集到的數(shù)據(jù),進(jìn)行系統(tǒng)分析。

功能拆分

初始化

獲取用戶(hù)傳遞的參數(shù),調(diào)用初始化函數(shù),在初始化函數(shù)中可以注入一些監(jiān)聽(tīng)事件來(lái)實(shí)現(xiàn)數(shù)據(jù)統(tǒng)計(jì)的功能。

/**
 * 初始化配置
 * @param {*} options 配置信息
 */
function init(options) {
  // ------- 加載配置 ----------
  loadConfig(options);
}
/**
 * 加載配置
 * @param {*} options 
 */
export function loadConfig(options) {
  const { 
    appId,  // 系統(tǒng)id
    userId, // 用戶(hù)id
    reportUrl, // 后端url
    autoTracker, // 自動(dòng)埋點(diǎn)
    delay, // 延遲和合并上報(bào)的功能
    hashPage, // 是否hash錄有
    errorReport // 是否開(kāi)啟錯(cuò)誤監(jiān)控
  } = options;

  // --------- appId ----------------
  if (appId) {
    window['_monitor_app_id_'] = appId;
  }

  // --------- userId ----------------
  if (userId) {
    window['_monitor_user_id_'] = userId;
  }

  // --------- 服務(wù)端地址 ----------------
  if (reportUrl) {
    window['_monitor_report_url_'] = reportUrl;
  }

  // -------- 合并上報(bào)的間隔 ------------
  if (delay) {
    window['_monitor_delay_'] = delay;
  }

  // --------- 是否開(kāi)啟錯(cuò)誤監(jiān)控 ------------
  if (errorReport) {
    errorTrackerReport();
  }

  // --------- 是否開(kāi)啟無(wú)痕埋點(diǎn) ----------
  if (autoTracker) {
    autoTrackerReport();
  }

  // ----------- 路由監(jiān)聽(tīng) --------------
  if (hashPage) {
    hashPageTrackerReport(); // hash路由上報(bào)
  } else {
    historyPageTrackerReport(); // history路由上報(bào)
  }
}

錯(cuò)誤監(jiān)控

前端是直接和用戶(hù)打交道的,頁(yè)面報(bào)錯(cuò)是特別影響用戶(hù)體驗(yàn)的,即使在測(cè)試充分上線(xiàn)后也會(huì)因用戶(hù)操作行為和操作環(huán)境出現(xiàn)各種錯(cuò)誤,所以不光是后端需要加報(bào)警監(jiān)控,前端的錯(cuò)誤監(jiān)控也很重要。

錯(cuò)誤類(lèi)型

  • 語(yǔ)法錯(cuò)誤
    語(yǔ)法錯(cuò)誤一般在開(kāi)發(fā)階段就可以發(fā)現(xiàn),如拼寫(xiě)錯(cuò)誤、符號(hào)錯(cuò)誤等,語(yǔ)法錯(cuò)誤無(wú)法被try{}catch{}捕獲,因?yàn)樵陂_(kāi)發(fā)階段就能發(fā)現(xiàn),也不會(huì)發(fā)布到線(xiàn)上。
              try {
                const name = 'wsjyq;
                console.log(name);
              } catch (error) {
                console.log('--- 語(yǔ)法錯(cuò)誤 --')
              }
  • 同步錯(cuò)誤
    指在js同步執(zhí)行過(guò)程中發(fā)生的錯(cuò)誤,如變量未定義,可被try-catch捕獲
     try {
       const name = 'wsjy';
       console.log(nam);   
       } catch (error) {
       // console.log('--- 同步錯(cuò)誤 ---- ')
     }
  • 異步錯(cuò)誤
    指在setTimeout等函數(shù)中發(fā)生的錯(cuò)誤,無(wú)法被try-catch捕獲

    異步錯(cuò)誤也可以用Window.onerror捕獲處理,比try-catch方便很多

          {/* 異步錯(cuò)誤 */}
          <button
            style={{ marginRight: 20 }}
            onClick={() => {
              // 異步錯(cuò)誤無(wú)法被trycatch捕獲
              try {
                setTimeout(() => {
                  let name = 'wsjyq';
                  name.map();
                })
              } catch (error) {
                console.log('--- 異步錯(cuò)誤---- ')
              }
            }}
          >異步錯(cuò)誤</button>
     // ----- 異步錯(cuò)誤捕獲 --------
        /** 
         * @param {String} msg   錯(cuò)誤描述 
         * @param {String} url   報(bào)錯(cuò)文件
         * @param {Number} row   行號(hào)
         * @param {Number} col   列號(hào)
         * @param {Object} error 錯(cuò)誤Error對(duì)象
        */
        window.onerror = function (msg, url, row, col, error) {
          console.log('---- 捕獲到j(luò)s執(zhí)行錯(cuò)誤 ----');
          console.log(msg);
          console.log(url);
          console.log(row);
          console.log(col);
          console.log(error);
          return true;
        };
  • Promise錯(cuò)誤
    在Promise中使用catch可以捕獲到異步錯(cuò)誤,但如果沒(méi)寫(xiě)catch的話(huà)在Window.onerror中是捕獲不到錯(cuò)誤的,或者可以在全局加上unhandledrejection監(jiān)聽(tīng)沒(méi)被捕獲到的Promise錯(cuò)誤。

          {/* promise錯(cuò)誤 */}
          <button
            style={{ marginRight: 20 }}
            onClick={() => {
              Promise.reject('promise error').catch(err => {
                console.log('----- promise error -----');
              });
    
              Promise.reject('promise error');
            }}
          >promise錯(cuò)誤</button>
        // ------ promise error -----
        window.addEventListener('unhandledrejection', (error) => {
          console.log('---- 捕獲到promise error ---')
        }, true);
  • 資源加載錯(cuò)誤
    指一些資源文件獲取失敗,一般用Window.addEventListener來(lái)捕獲。

          {/* resource錯(cuò)誤 */}
          <button 
            style={{ marginRight: 20 }}
            onClick={() => {
              setShow(true);
            }}
          >resource錯(cuò)誤</button>
          {
            show && <img src="localhost:8000/images/test.png" /> // 資源不存在
          }
        </div>
     // ------ resource error ----
        window.addEventListener('error', (error) => {
          console.log('---- 捕獲到resource error ---')
        }, true);

SDK監(jiān)控錯(cuò)誤就是圍繞這幾種錯(cuò)誤實(shí)現(xiàn)的,try-catch用來(lái)在可預(yù)見(jiàn)情況下監(jiān)控特定錯(cuò)誤 ,Window.onerror主要來(lái)捕獲預(yù)料之外的錯(cuò)誤,比如異步錯(cuò)誤。但對(duì)于Promise錯(cuò)誤和網(wǎng)絡(luò)錯(cuò)誤是無(wú)法進(jìn)行捕獲的,所以需要用到Window.unhandledrejection監(jiān)聽(tīng)捕獲Promise錯(cuò)誤,通過(guò)error監(jiān)聽(tīng)捕獲資源加載錯(cuò)誤,從而達(dá)到各類(lèi)型錯(cuò)誤全覆蓋。

用戶(hù)埋點(diǎn)統(tǒng)計(jì)

埋點(diǎn)是監(jiān)控用戶(hù)在應(yīng)用上的一些動(dòng)作表現(xiàn),如在淘寶某商品頁(yè)面上停留了幾分鐘,就會(huì)有一條某用戶(hù)在某段時(shí)間內(nèi)搜索了某商品并停留了幾分鐘的記錄,后臺(tái)根據(jù)這些記錄去分析用戶(hù)行為,并在指定之后推送或產(chǎn)品迭代優(yōu)化等,對(duì)于產(chǎn)品后續(xù)的發(fā)展起重要作用。

埋點(diǎn)又分為手動(dòng)埋點(diǎn)自動(dòng)埋點(diǎn)

手動(dòng)埋點(diǎn)

手動(dòng)在代碼中添加相關(guān)埋點(diǎn)代碼,如用戶(hù)點(diǎn)擊某按鈕或者提交一個(gè)表單,會(huì)在按鈕點(diǎn)擊事件中和提交事件中添加相關(guān)埋點(diǎn)代碼。

      {/* 手動(dòng)埋點(diǎn) */}
      <button
        onClick={() => {
          tracker('submit', '提交表單');
          tracker('click', '用戶(hù)點(diǎn)擊');
          tracker('visit', '訪(fǎng)問(wèn)新頁(yè)面');
        }}
      >按鈕1</button>

      {/* 屬性埋點(diǎn) */}
      <button  data-target="按鈕2被點(diǎn)擊了">按鈕2</button>
  • 優(yōu)點(diǎn):可控性強(qiáng),可以自定義上報(bào)具體數(shù)據(jù)。
  • 缺點(diǎn):對(duì)業(yè)務(wù)代碼入侵性強(qiáng),若需要很多地方進(jìn)行埋點(diǎn)需要一個(gè)個(gè)進(jìn)行添加。

自動(dòng)埋點(diǎn)

自動(dòng)埋點(diǎn)解決了手動(dòng)埋點(diǎn)缺點(diǎn),實(shí)現(xiàn)了不用侵入業(yè)務(wù)代碼就能在應(yīng)用中添加埋點(diǎn)監(jiān)控的埋點(diǎn)方式。

      {/* 自動(dòng)埋點(diǎn) */}
      <button 
        style={{ marginRight: 20 }}
        onClick={(e) => {
          //業(yè)務(wù)代碼
        }}
      >按鈕3</button>
/**
 * 自動(dòng)上報(bào)
 */
export function autoTrackerReport() {
  // 自動(dòng)上報(bào)
  document.body.addEventListener('click', function (e) {
    const clickedDom = e.target;

    // 獲取標(biāo)簽上的data-target屬性的值
    let target = clickedDom?.getAttribute('data-target');

    // 獲取標(biāo)簽上的data-no屬性的值
    let no = clickedDom?.getAttribute('data-no');
    // 避免重復(fù)上報(bào)
    if (no) {
      return;
    }

    if (target) {
      lazyReport('action', {
        actionType: 'click',
        data: target
      });
    } else {
      // 獲取被點(diǎn)擊元素的dom路徑
      const path = getPathTo(clickedDom);
      lazyReport('action', {
        actionType: 'click',
        data: path
      });
    }
  }, false);
}

需要注意的是:無(wú)痕埋點(diǎn)是通過(guò)全局監(jiān)聽(tīng)click事件的冒泡行為實(shí)現(xiàn)的,如果在click事件中阻止了冒泡行為,是不會(huì)冒泡到click監(jiān)聽(tīng)里的,所以,對(duì)于加了冒泡行為的click事件需要進(jìn)行手動(dòng)埋點(diǎn)上報(bào),從而保證上報(bào)全覆蓋。

      {/* 自動(dòng)埋點(diǎn) */}
      <button 
        style={{ marginRight: 20 }}
        onClick={(e) => {
          e.stopPropagation(); // 阻止事件冒泡
          tracker('submit', '按鈕1被點(diǎn)擊了'); //手動(dòng)上報(bào)
        }}
      >按鈕3</button>
  • 優(yōu)點(diǎn):不用入侵代碼就可以實(shí)現(xiàn)全局埋點(diǎn)上報(bào)。
  • 缺點(diǎn):只能上報(bào)基本的行為交互信息,無(wú)法上報(bào)自定義數(shù)據(jù)。只要在頁(yè)面中點(diǎn)擊了,就會(huì)上報(bào)至服務(wù)器,導(dǎo)致上報(bào)次數(shù)會(huì)太多,服務(wù)器壓力大。

PV統(tǒng)計(jì) 

PV即頁(yè)面瀏覽量,表示頁(yè)面的訪(fǎng)問(wèn)次數(shù) 
非SPA頁(yè)面只需通過(guò)監(jiān)聽(tīng)onload事件即可統(tǒng)計(jì)頁(yè)面的PV,在SPA頁(yè)面中,路由的切換主要由前端來(lái)實(shí)現(xiàn),而單頁(yè)面切換又分為hash路由和history路由,兩種路由的實(shí)現(xiàn)原理不一樣,本文針對(duì)這兩種路由分別實(shí)現(xiàn)不同的數(shù)據(jù)采集方式

 history路由

history路由依賴(lài)全局對(duì)象history實(shí)現(xiàn)的

  • history.back(): 返回上一頁(yè) (瀏覽器回退)
  • history.forward():前進(jìn)一頁(yè) (瀏覽器前進(jìn))
  • history.go():跳轉(zhuǎn)歷史中某一頁(yè)
  • history.pushState():添加新記錄
  • history.replaceState():修改當(dāng)前記錄

history路由的實(shí)現(xiàn)主要由pushStatereplaceState實(shí)現(xiàn),但這兩個(gè)方法不能被popstate監(jiān)聽(tīng)到,所以需要對(duì)這兩個(gè)方法進(jìn)行重寫(xiě)并進(jìn)行自定義事件監(jiān)聽(tīng)來(lái)實(shí)現(xiàn)數(shù)據(jù)采集。

import { lazyReport } from './report';

/**
 * history路由監(jiān)聽(tīng)
 */
export function historyPageTrackerReport() {
  let beforeTime = Date.now(); // 進(jìn)入頁(yè)面的時(shí)間
  let beforePage = ''; // 上一個(gè)頁(yè)面

  // 獲取在某個(gè)頁(yè)面的停留時(shí)間
  function getStayTime() {
    let curTime = Date.now();
    let stayTime = curTime - beforeTime;
    beforeTime = curTime;
    return stayTime;
  }

  /**
   * 重寫(xiě)pushState和replaceState方法
   * @param {*} name 
   * @returns 
   */
  const createHistoryEvent = function (name) {
    // 拿到原來(lái)的處理方法
    const origin = window.history[name];
    return function(event) {
      // if (name === 'replaceState') {
      //   const { current } = event;
      //   const pathName = location.pathname;
      //   if (current === pathName) {
      //     let res = origin.apply(this, arguments);
      //     return res;
      //   }
      // }

  
      let res = origin.apply(this, arguments);
      let e = new Event(name);
      e.arguments = arguments;
      window.dispatchEvent(e);
      return res;
    };
  };

  // history.pushState
  window.addEventListener('pushState', function () {
    listener()
  });

  // history.replaceState
  window.addEventListener('replaceState', function () {
    listener()
  });

  window.history.pushState = createHistoryEvent('pushState');
  window.history.replaceState = createHistoryEvent('replaceState');

  function listener() {
    const stayTime = getStayTime(); // 停留時(shí)間
    const currentPage = window.location.href; // 頁(yè)面路徑
    lazyReport('visit', {
      stayTime,
      page: beforePage,
    })
    beforePage = currentPage;
  }

  // 頁(yè)面load監(jiān)聽(tīng)
  window.addEventListener('load', function () {
    // beforePage = location.href;
    listener()
  });

  // unload監(jiān)聽(tīng)
  window.addEventListener('unload', function () {
    listener()
  });

  // history.go()、history.back()、history.forward() 監(jiān)聽(tīng)
  window.addEventListener('popstate', function () {
    listener()
  });
}

hash路由

url中的hash值變化會(huì)引起hashChange的監(jiān)聽(tīng),所以只需在全局添加一個(gè)監(jiān)聽(tīng)函數(shù),在函數(shù)中實(shí)現(xiàn)數(shù)據(jù)采集上報(bào)即可。但在react和vue中hash路由的跳轉(zhuǎn)是通過(guò)pushState實(shí)現(xiàn)的,所以還需加上對(duì)pushState的監(jiān)聽(tīng)。

/**
 * hash路由監(jiān)聽(tīng)
 */
export function hashPageTrackerReport() {
  let beforeTime = Date.now(); // 進(jìn)入頁(yè)面的時(shí)間
  let beforePage = ''; // 上一個(gè)頁(yè)面

  function getStayTime() {
    let curTime = Date.now();
    let stayTime = curTime - beforeTime; //當(dāng)前時(shí)間 - 進(jìn)入時(shí)間
    beforeTime = curTime;
    return stayTime;
  }

  function listener() {
    const stayTime = getStayTime();
    const currentPage = window.location.href;
    lazyReport('visit', {
      stayTime,
      page: beforePage,
    })
    beforePage = currentPage;
  }

  // hash路由監(jiān)聽(tīng)
  window.addEventListener('hashchange', function () {
    listener()
  });

  // 頁(yè)面load監(jiān)聽(tīng)
  window.addEventListener('load', function () {
    listener()
  });

  const createHistoryEvent = function (name) {
    const origin = window.history[name];
    return function(event) {
      //自定義事件
      // if (name === 'replaceState') {
      //   const { current } = event;
      //   const pathName = location.pathname;
      //   if (current === pathName) {
      //     let res = origin.apply(this, arguments);
      //     return res;
      //   }
      // }
      
      let res = origin.apply(this, arguments);
      let e = new Event(name);
      e.arguments = arguments;
      window.dispatchEvent(e);
      return res;
    };
  };

  window.history.pushState = createHistoryEvent('pushState');

  // history.pushState
  window.addEventListener('pushState', function () {
    listener()
  });
}

UV統(tǒng)計(jì)

統(tǒng)計(jì)一天內(nèi)訪(fǎng)問(wèn)網(wǎng)站的用戶(hù)數(shù)
UV統(tǒng)計(jì)只需在SDK初始化時(shí)上報(bào)一條消息即可。

/**
 * 初始化配置
 * @param {*} options 配置信息
 */
function init(options) {
  // ------- 加載配置 ----------

  // -------- uv統(tǒng)計(jì) -----------
  lazyReport('user', '加載應(yīng)用');
}

數(shù)據(jù)上報(bào)

  • xhr接口請(qǐng)求
    采用接口請(qǐng)求的方式,就像其他業(yè)務(wù)請(qǐng)求一樣,知識(shí)傳遞的數(shù)據(jù)是埋點(diǎn)的數(shù)據(jù)。通常情況下,公司里處理埋點(diǎn)的服務(wù)器和處理業(yè)務(wù)邏輯的服務(wù)器不是同一臺(tái),所以需要手動(dòng)解決跨域問(wèn)題。另一方面,如果在上報(bào)過(guò)程中刷新或者重新打開(kāi)頁(yè)面,可能會(huì)造成埋點(diǎn)數(shù)據(jù)的缺失,所以傳統(tǒng)xhr接口請(qǐng)求方式并不能很好適應(yīng)埋點(diǎn)的需求。
  • img標(biāo)簽
    img標(biāo)簽的方式是將埋點(diǎn)數(shù)據(jù)偽裝成圖片url的請(qǐng)求方式,避免了跨域問(wèn)題,但瀏覽器對(duì)url長(zhǎng)度會(huì)有限制,所以不適合大數(shù)據(jù)量上報(bào),也會(huì)存在刷新或重新打開(kāi)頁(yè)面的數(shù)據(jù)丟失問(wèn)題。
  • sendBeacon
    這種方式不會(huì)出現(xiàn)跨域問(wèn)題,也不糊存在刷新或重新打開(kāi)頁(yè)面的數(shù)據(jù)丟失問(wèn)題,缺點(diǎn)是存在兼容性問(wèn)題。在日常開(kāi)發(fā)中,通常采用sendBeacon上報(bào)和img標(biāo)簽上報(bào)結(jié)合的方式。
/**
 * 上報(bào)
 * @param {*} type 
 * @param {*} params 
 */
export function report(data) {
  const url = window['_monitor_report_url_'];

  // ------- fetch方式上報(bào) -------
  // 跨域問(wèn)題
  // fetch(url, {
  //   method: 'POST',
  //   body: JSON.stringify(data),
  //   headers: {
  //     'Content-Type': 'application/json',
  //   },
  // }).then(res => {
  //   console.log(res);
  // }).catch(err => {
  //   console.error(err);
  // })

  // ------- navigator/img方式上報(bào) -------
  // 不會(huì)有跨域問(wèn)題
  if (navigator.sendBeacon) { // 支持sendBeacon的瀏覽器
    navigator.sendBeacon(url, JSON.stringify(data));
  } else { // 不支持sendBeacon的瀏覽器
    let oImage = new Image();
    oImage.src = `${url}?logs=${data}`;
  }
  clearCache();
}

總結(jié) 

到此這篇關(guān)于前端實(shí)現(xiàn)監(jiān)控SDK的文章就介紹到這了,更多相關(guān)前端監(jiān)控SDK內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論