【前端監控】日誌池

前端監控系列,SDK,服務、存儲 ,會全部總結一遍,寫文不易,點個贊吧


前端監控上報數據的時候,是怎麼發請求的呢,是每產生一條監控數據就上報一次嗎

當然不是了,如果監控點很多,那估計請求都快發爆炸了,請求發得多,不僅會加重服務器壓力,數據丟失的概率也大,畢竟10條請求的成功率肯定比 一條請求 的成功率小嘛

所以纔會出現日誌池,這篇內容不屬於前端監控的一部分,屬於是其中的一個優化點

不多說了,開始正文

本文其實挺簡單的,分爲三個部分

1、基本思路

2、具體邏輯

3、代碼Demo倉庫



基本思路

最簡單的說法就是

每次上報數據的時候,不會立即發送,把數據存到一個數組裏面

然後設置 setTimeout,定時從數組裏面取固定數量的日誌(比如20條數據),然後再進行上報

簡單畫個圖,如下

在這個基礎上再進行優化

1、錯誤重試。上報請求發生錯誤的時候,會進行重試,以免日誌就這麼丟失,這裏在離線日誌中有過相關處理

2、頁面關閉發送剩餘日誌。因爲我們使用定時發送的方式,可能會存在用戶關閉界面的時候,還有緩存的日誌沒有發送。


所以需要在最後一刻發送一波

下面就來詳細說下具體的處理邏輯


具體邏輯

看了上面基本就知道這裏就有三個步驟

1、定時發送

2、錯誤重試

3、監聽頁面關閉發送日誌


1定時發送

1、把所有日誌數據都會先緩存到一個數組中,調用上報方法的時候,直接 push 進去

const LOG_CACHE = []
function report(data){
    LOG_CACHE.push(data)
}


2、初始化的時候會設置一個循環定時器,從 日誌數組中,取一定數量的數據 進行上報

let timer = null;
const duration = 500;
const limit = 20;
let LOG_CACHE = [];

function init({
  loop();
}

function loop({
  timer = setTimeout(() => {
    timer = null;
    LOG_CACHE.length && send();
    loop();
  }, duration);
}

function send({
  const logs = LOG_CACHE.splice(0, limit);
  if (!logs.length) return;
  fetch("www.test.com/report", {
    bodyJSON.stringify(logs),
  });
}


這個 定時器時間 和 單次上報的數量 可以自己定義

時間不能太長,導致積壓的數據過多,丟失風險大

數量不能太大,不然請求體太大,響應時間長,丟失的成本大

我們這裏設置的是,500ms 單次發送20條數據


2錯誤重試

當上報請求發生錯誤的時候,有兩種選擇

1、直接重試

2、緩存等待重試


直接重試

如果是偶爾的錯誤,可以直接重試,但是如果是頻繁報錯,可能是接口服務或者用戶網絡的問題

所以仍然直接重試沒有什麼意義

直接重試條件是,如果3s內發生錯誤沒有超過3次,那麼就直接重試。

每次報錯疊加一次failCount,並且重設定時器,定時器會把錯誤次數置爲0。

重試的做法是,把上報的數據,重新 push 進 數組


緩存等待重試

定時器內超過3次的,會存入本地。

緩存進本地的日誌,什麼時候會重試?

1、頁面初始化時,讀取本地數據,push 進數組

2、每次存本地時,會開啓一個定時器,從本地讀取數據,push 進數組

所以緩存進本地,是爲了把數據延遲更久上報,同時保證數據不丟失

看下大概的實現代碼

其中操作 indexdb 的代碼是我簡化寫的,實際不是這麼操作

更詳細的關於存日誌到 indexdb 的部分在 離線日誌

let failCount = 0// 上報錯誤次數

function send({
  const logs = LOG_CACHE.splice(0, limit);
  if (!logs.length) return;
  fetch("www.test.com/report", {
    bodyJSON.stringify(logs),
  }).catch((e) => {
    handlerReportError();
    if (failCount < 3) {
      LOG_CACHE.push(...logs); // 僞代碼
    } else {
      saveLogs(logs)
    }
  });
}

let failTimer = 0// 錯誤定時器
function handlerReportError({
  failCount++;
  //  發生錯誤就重置定時器
  clearTimeout(failTimer);
  failTimer = setTimeout(() => {
    failTimer = null;
    failCount = 0;
  }, 3000);
}

let readTimer = null// 讀取本地數據庫定時器
function saveLogs(data{
  indexDBStore.add("fail_log", data);
  if (!readTimer) return;
  readTimer = setTimeout(() => {
    const logs = indexDBStore.get("fail_log");
    LOG_CACHE.push(...logs); // 僞代碼
  }, 3000);
}



3監聽頁面關閉發送日誌

因爲定時發送請求的原因,可能存在用戶關閉頁面,仍然有部分日誌沒有發送,所以需要監聽頁面關閉,把剩餘日誌發送一下

具體處理就是

1、監聽頁面關閉事件

2、使用 navigator.sendBeacon 發送請求

關於這部分存在的兼容性,我寫在另一篇文章了,請看 【兼容性】監聽頁面關閉發送請求

具體代碼如下

四個事件都是爲了監聽頁面關閉,但是各端支持程度不一樣,所以全部監聽,誰生效就用誰

使用一個標誌位,只要有事件觸發過了,其他事件就不用了

window.addEventListener("beforeunload", sendRemainLogs);
window.addEventListener("pagehide", sendRemainLogs);
window.addEventListener("unload", sendRemainLogs);

// IOS14 之前不會冒泡,只能監聽document
document.addEventListener("visibilitychange ", () => {
  if (document.visibilityState !== "visible") {
    sendRemainLogs();
  } else {
    // 如果界面又顯示了,說明沒有關閉,重置標誌位
    sendRemainLogsSuccess = false;
  }
});

let sendRemainLogsSuccess = false;

function sendRemainLogs(data{
  if (sendRemainLogsSuccess) return;   

  if (!navigator.sendBeacon) {
    indexdbStore.add("fail_logs", data);
    return;
  }

  const logs = LOG_CACHE;
  const result = navigator.sendBeacon(
    "www.test.com/report",
    JSON.stringify(logs)
  );
  if (!result) {
    indexdbStore.add("fail_logs", data);
  }
}


現在所有的處理都說完了

現在來看下這個日誌池的流程圖


代碼demo錯誤

把大概的實現寫了一個demo,可以參考下,相對於上面出現的代碼片段,做了一些封裝和錯誤判斷,是一個可以走通整個邏輯的

https://gitee.com/hoholove/study-code-snippet/blob/master/LOGGER/pool.js


最後

鑑於本人能力有限,難免會有疏漏錯誤的地方,請大家多多包涵, 如果有任何描述不當的地方,歡迎後臺聯繫本人,領取紅包

本文分享自微信公衆號 - 神仙朱(skying-zhu)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章