iT邦幫忙

2024 iThome 鐵人賽

DAY 24
2

https://ithelp.ithome.com.tw/upload/images/20241008/201520732yTd04lBiz.png

前言

我們了解 Faro 從資料收集到資料呈現的過程,需要有一個 Agent 做為中繼站以及 Grafana Loki 及 Tempo 作為數據收集的服務,背後的傳遞和流程雖然複雜,但對於 Faro 在前端應用程式中的設置,其實只需要幾行程式碼即可完成,而除了基本設置,Faro 還提供許多手動及進階的使用方法,讓開發團隊可以依據更適合的場景推送適當的資訊。接下來我們將更進一步了解 Faro 的初始化實作原理,並透過實作設置的方式,了解如何將 Faro 整合到我們的專案中,以及在 Grafana Dashboard 中如何檢視我們的數據。

Initialization process 初始化流程

initializeFaro({
  url: '<collect-endpoint-url>/collect/<API key>',
  app: {
    name: 'YourAppName',
    version: '1.0.0',
    environment: 'production'
  },
  instrumentations: [
    ...getWebInstrumentations(),
    new TracingInstrumentation(),
  ],
});

上面的範例是一個最簡單的 Faro 初始化程式碼,然而初始化一個 Faro 背後所調動的是非常複雜的元件處理。而這些初始化是有順序的,且初始化的結果也會互相牽連。順序可以分為三大類:core 元件、重複註冊檢查、所有 API 的初始化,在確認初始化完成後才會依序將 Metas、Transports、Instrumentations 等功能做預設的註冊。以下讓我們更近一了解 Faro 所初始化及建立實例進行資料收集的步驟。

Core 元件

由於 Unpatched Console 的設置結果會影響 Internal Logger 的 log 設置方法,所以會先初始化 unpatchedConsole 再接續著 internalLogger。

  1. Unpatched Console

    unpatchedConsole 主要功能是提供給 Faro 作內部 Log 使用,初始化檢查時,會先查看 initializeFaro 中是否有設置 unpatchedConsole 屬性,預設且未設置的情況下,會使用 runtime 中的全域 console 物件淺拷貝。當然也可以設定自定義的 console 或是使用第三方套件的 console 例如:Winston,若有設定會覆寫預設的 console 作為內部使用且不影響全域的 console 物件。

  2. Internal Logger

    Internal Logger 是 Faro 的內部記錄器,會在初始化和運行過程中記錄 Debug 資訊、警告和錯誤。會接續在 Unpatched Console 之後立刻初始化。由於全域的 console 可能會被其他工具覆蓋,所以內部記錄器會依賴 unpatched console,而不是全域的 console ,這樣做是為了確保內部 log 不會被當作普通的應用 log 捕獲和發送。包含 debug、info、warn 和 error 四種 level。

重複註冊檢查

接下來會為 Faro 是否重複註冊作檢查,預設情況下,Faro 是一個全域單例物件,只能初始化一次。然而,可以透過使用 isolate 設定選項將其標記為隔離模式,從而初始化多個實例。可以使用在含有多個子應用的大型應用中,讓每個子應用都獨立進行監控。

當 Faro 被註冊時,會在 runtime 的全域物件上創建一個名為 _faroInternal 的不可枚舉、不可修改屬性,其值為 Faro 實例。並且檢查這個屬性是否存在來判斷 Faro 是否已經被註冊。如果檢測到已有實例初始化且當前未設置 isolate: true,則會停止初始化過程,同時顯示 'Faro is already registered. Either add instrumentations, transports etc. to the global faro instance or use the "isolate" property' 的訊息。

初始化元件的順序

各元件的初始化參數也是息息相關,所以也會依照順序進行初始化。要特別注意的是,初始化過程結束後才會依序為 Metas、Transports、Instrumentation 註冊啟用,此階段僅做資料參數的整理。

  1. Metas:下一個初始化的元件,因為後續所有元件都依賴它。是 Faro 中負責儲存和管理 metadata 的核心元件,為所有收集的數據附加上下文資訊,用於後續的數據分析與問題診斷。Metas 能儲存和檢索各種 metadata,例如 session 資訊、使用者資訊和 view 資訊等。另外,API 依賴 Metas 來維持訊號數據的一致性,而 Transports 和 Instrumentations 也需要 Metas 提供的 metadata,這些數據會提供給開發者,有助於編寫自訂功能。

  2. Transports:是 Faro 架構中的最終數據處理器,負責將由 Instrumentations 收集並經過內部 API 處理的數據進行最終處理或發送。Transports SDK 負責管理這些 Transports,並提供添加、移除、暫停等操作。

    Transports 會在 API 初始化之前完成,這樣 API 可以利用它們來傳遞信號。然而,Transports 並不依賴 API,保持了單向的依賴關係。

  3. API:API 是 Faro 的核心元件,提供與使用者互動的主要介面,負責收集訊號並支援擴充功能(如 metas)API 會在 Instrumentation 之前初始化,以支援訊號的傳遞,提供完整的監控和分析功能。API 支援以下訊號類型:

    • Events:記錄使用者互動,如點擊、頁面導覽等。(pushEvent
    • Exceptions:處理應用程式錯誤。(pushErrorchangeStacktraceParsergetStacktraceParser
    • Logs:捕捉內部事件,用於故障排除。(pushLog
    • Measurements:視作指標的訊號。(pushMeasurement
    • Traces:追蹤應用行為,依賴 OpenTelemetry。(pushTraces
    • Metas:提供輔助方法來管理 session、view 及使用者資訊(如 getSessiongetViewsetUser)。
  4. Instrumentation:是最後初始化的元件。它依賴其他元件,例如 API 方法用來發送訊號,metas 用來決策某些值等。

  5. Faro SDK:一旦所有元件初始化完成,最終可以構建全域的 Faro 物件。此物件將透過 npm 套件中的 faro 對象供最終使用者導入使用。如果未傳遞 preventGlobalExposure 設定屬性,該物件也會曝光到全域物件上。然而,如果全域物件已包含相同名稱的屬性,會在 console 中顯示警告訊息,但過程仍會繼續。

實作

initializeFaro 的 config 中有許多設定值,雖然上面有呈現最基本的設置方法,但根據使用情境還可以設定其他屬性,例如 sessionTracking、transports、preventGlobalExposure 等,甚至是 React 封裝套件的使用。而初始化前也需要申請 collector 的指定網址,像是收集器的 endpoint,以下直接開始:

連接應用程式

如果需要在 Grafana Cloud 上建立前端的可觀測平台,首先需擁有 Grafana Cloud 會團隊 Grafana 平台的帳號權限,接著找到左側選單的 Frontend 選項,進入 Grafana 所提供的可觀測應用程式,並新增一個新的專案。

https://ithelp.ithome.com.tw/upload/images/20241008/20152073Cly8n1e5Cj.png

新增專案後,平台會引導使用者輸入應用程式名稱、以及應用程式的 Domain,需符合 CORS 的可用來源使得平台可以訪問應用程式,如果是開發環境則可使用 localhost

https://ithelp.ithome.com.tw/upload/images/20241008/20152073Pj7qzeHOB1.png

最後平台會從如何安裝、需要安裝什麼 package,以及依據 Web 或是 React 專案讓使用者選擇對應的初始化程式碼,按照平台指示完成後,即可將應用程式與 Grafana 的平台連接,並立即開始收集資料。

https://ithelp.ithome.com.tw/upload/images/20241008/20152073leiXHOWcKs.png

基本使用

在文章一開始以及平台所提供的初始化程式碼是一個最基本的範例,而在這個範例中做了以下設定:

  • 使用 initializeFaro 方法初始化 Faro SDK。
  • 指定了資料收集的 url,以及應用程式的基本資訊(名稱、版本、環境)。
  • 設置了 instrumentations,包括所有預設的 Web instrumentations 以及 Tracing instrumentation。
import { initializeFaro } from '@grafana/faro-web-sdk';

const faro = initializeFaro({
  url: '<collect-endpoint-url>/<app-key>',
  app: {
    name: 'YourAppName',
    version: '1.0.0',
    environment: 'production',
  },
  instrumentations: [
    ...getWebInstrumentations(),
    new TracingInstrumentation(),
  ],
});

進階使用

基本的使用已經足以收集完整的資訊並呈現視覺話的圖表進行可觀性的分析,然而前端的使用會有非常多的 log 或資料收集,有時候希望做更細膩的調整或是將不需要監控的服務請求做篩選,也可能是希望客製化的屬性需求或 Instrumentation 都可以在 initializeFaro 的 config 中進行設置,以下將這些屬性分類為:

  • Logs 與 Events 處理:透過 transportsbatchingignoreErrors 等設定控制 log 和 event 的記錄與傳送方式。
  • 效能監控:使用 trackResourcestrackWebVitalsAttribution 來啟用資源載入和 Web 核心指標的追蹤。
  • 使用者與 session 管理:透過 usersessionTracking 設定追蹤使用者資訊與 session 狀態。
  • 安全與隔離:透過 isolatepreventGlobalExposure 確保環境安全性與變數隔離。
  • 自訂功能:使用 instrumentationslogArgsSerializermetas 等項目來新增自訂的監控與 log 格式化功能。

上方列出的內容僅以一小段敘述很難有印象或是不知從何下手使用,所以接下來會依據這些進階設定屬性以範例對照來介紹使用的情境與方法。

Logs 與 Events 處理

分別有 transportsignoreErrorsignoreUrlsbatching 等方法,ignoreErrorsignoreUrls 較為簡單,可以使用明確的 string 或正則表達式表示,來忽略 array 中所設定的 errors 及網址收集:

ignoreErrors: ['Error: ResizeObserver', /FetchError[:\s\w\/]*pwc/],
ignoreUrls: [/.*foo-analytics/, /.*.analytics.com/, 'http://example.com/awesome-image'],

transports 屬性能讓使用者設定更細節的資料發送參數,有 FetchTransport 和 ConsoleTransport 兩種類型,在基本用法中 Faro 所收集的數據資料會傳送到 url 所指定的 endpoint 中,然而若是在 transports 中設定 FetchTransport 實例,則以其設定的 url 作為傳送的目的地,要注意的是 url 及 FetchTransport 無法同時設置,否則會出現牴觸的錯誤: 'if "transports" is defined, "url" and "apiKey" should not be defined’。當然如果兩者皆無設置也會出現需擇一設定的錯誤。

FetchTransport 的行為就像是封裝好的請求工具,因此有許多選項可以設定,包括請求緩衝(bufferSize)、並發控制(concurrency)、速率限制處理(defaultRateLimitBackoffMs),以及底層 fetch 請求的細節。

ConsoleTransport 則是將數據同時輸出到瀏覽器控制台,可以在開發環境中 debug 使用。

  transports: [
    new FetchTransport({
      url: 'http://localhost:12345/collect',
      apiKey: 'secret',
    }),
    new ConsoleTransport(),
  ],

batching 的設定主要代表了如何將多個事件或數據項組合在一起,然後一次性發送,而不是每次都單獨發送。分別有四個屬性,enabled、sendTimeout、itemLimit、paused:

  • enabled: 預設為 true,會等待批次發送資訊,如果設為 false,每個事件都被立即單獨處理和發送,若在短時間內產生大流量的事件,可能會增加伺服器和客戶端的負載。
  • itemLimit: 預設值為 50,代表批次中可以包含的最大數量。當緩衝區中的數量達到這個限制時,會觸發一次發送操作。
  • sendTimeout: 預設值為 250 毫秒,為定期發送批次的時間間隔。即使沒有達到 itemLimit,也會每隔這麼多毫秒發送一次批次。
  • paused: 這個設置決定了批次處理是否暫停。如果設置為 true,則不會添加新項目或發送批次。
batching: {
enabled: true,
  sendTimeout: 1,
  itemLimit: 1,
},

效能監控

trackResources 屬性用來控制資源加載的效能數據收集範圍:

  • trackResources = true:收集所有資源的效能數據,包括圖片、腳本、樣式表等。
  • trackResources 未設置(null 或 undefined):為預設值,只收集 XMLHttpRequest 和 Fetch 請求的效能數據。
  • trackResources = false:不收集任何資源的效能數據。

trackWebVitalsAttribution 則是擴充了 web vitals 所收集的資訊,在預設時只會將各個 web vitals 的數值傳給 measurements。如果將此屬性設為 true,則可以收集每個 web vital 更詳細的屬性(請比較下圖左右的資料量),這在做效能監測時能更清楚了解可以優化的方向。

而這些詳細的屬性是由 web-vitals.js 套件所提供,有使用 web vitals 的外掛程式也可以在瀏覽器控制台中查看。儲存的規則是,如果值是 string 會被存在 context 的物件中,如果值是 number 則存在 value 的物件裡,而值為 0 時則不會傳送該屬性的資料。目前提供的屬性有:

  • CLS: loadState, largestShiftValue, largestShiftTime, largestShiftTarget

    https://ithelp.ithome.com.tw/upload/images/20241008/20152073OqvsUdjULu.png

  • FCP: firstByteToFCP, timeToFirstByte, loadState

    https://ithelp.ithome.com.tw/upload/images/20241008/20152073UciIEFI9pc.png

  • FID: eventTime, eventTarget, eventType, loadState

  • INP: interactionTime, presentationDelay, inputDelay, processingDuration, nextPaintTime, loadState, interactionTarget, interactionType

    https://ithelp.ithome.com.tw/upload/images/20241008/20152073s2O3MqPNRz.png

  • LCP: elementRenderDelay, resourceLoadDelay, resourceLoadDuration, timeToFirstByte, element

    https://ithelp.ithome.com.tw/upload/images/20241008/20152073hjF4Rb9DhF.png

  • TTFB: dnsDuration, connectionDuration, requestDuration, waitingDuration, cacheDuration

    https://ithelp.ithome.com.tw/upload/images/20241008/20152073pEJlN7eS1H.png
    https://ithelp.ithome.com.tw/upload/images/20241008/20152073Scrtpavgaw.png

使用者與 session 管理

sessionTracking 當 enabled 設置為 true 時,Faro 會開始追蹤使用者 session。persistent 屬性決定了 session 資料是否應該在頁面刷新或關閉後保持。samplingRate 則可以控制數據收集的比例(值為 0~1),例如設置為 0.5 意味著只有 50% 的 session 會被追蹤。sampler 是自訂取樣率的函數,可以取得 meta 的資料作取樣率的判斷。onSessionChange 是當 session 發生變化時會被調用,可以自定義邏輯。

config 中也可以額外設定 user 屬性,可以收集客戶端的 email、id、username 或其他自定義屬性(只要符合 Record<string, string>)。以及預設的 view 屬性,提供 SPA 頁面在路由切換的初始值。

sessionTracking: {
  enabled: true,
  persistent: true,
  samplingRate: 0.5,
  sampler: (metas) => {
		return metas.browser.mobile ? 0.8 : 1
	},
  onSessionChange: (oldSession, newSession) => {
    console.log('Session changed', oldSession, newSession);
  },
},
view: {
	name: 'default'
},

安全與隔離

isolate 已在上方「重複註冊檢查」段落中介紹過,此處不再重複。而 preventGlobalExposure 用來阻止 Faro 實例暴露為全域變數,提升應用程式的安全性,並減少全域命名空間的污染。這對於大型且複雜的應用程式特別重要,因為全域變數的管理容易變得複雜。

啟用此選項後,開發者需明確地將 Faro 實例傳遞給需要使用它的組件,而非依賴全域訪問。若禁用此選項(預設情況下),則可通過 window.faro 輕鬆訪問 Faro 實例,這對於 debug 和快速開發很便利,但可能帶來安全風險。

筆者語錄
在本篇文章中介紹非常多 Faro 的細節,從初始化的過程到實作方法,但一篇是無法介紹完 Faro 提供的功能,因此下一篇會介紹更多實用的功能以及進階的自定義功能,可以特別注重於 Trace 的部分。還有其他介面的呈現及巧思也都會陸續為讀者們介紹,希望透過這篇文章能帶領大家一起跟隨 Faro 的思緒走一遍這資料的傳輸過程。

參考資料

https://github.com/grafana/faro-web-sdk/blob/96bcd042d8b0a8c6d22e5460680096cb2ebf2200/packages/core/README.md

https://github.com/grafana/faro-web-sdk/blob/main/docs/sources/tutorials/quick-start-browser.md


上一篇
靠 Grafana 吃飯的第二十三天 - 前端可觀測性的未來 - Grafana Faro
下一篇
靠 Grafana 吃飯的第二十五天 - Grafana Faro 前端可觀測性的進階技巧
系列文
論前端工程師如何靠 Grafana 吃飯:從 Grafana App 到前端可觀測性30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言