iT邦幫忙

2024 iThome 鐵人賽

DAY 6
1
DevOps

全端監控技術筆記---從Sentry到Opentelemetry系列 第 6

Day06--Sentry是如何獲取error? 直接手寫看看(下)

  • 分享至 

  • xImage
  •  

前言

上篇文章我們大致了解了如何捕獲全局error和全局的promise error。但在前端框架中,有些它們框架本身的錯誤處理,例如React的ErrorBoundary 和Vue的onErrorCaptured 。下列就以React的ErrorBoundary 為例,看Sentry是如何結合的。

React的ErrorBoundary

ErrorBoundary 是 React在處理渲染階段錯誤的一種方式。當React組件樹中的某個組件渲染、生命週期函數錯誤、或者其他組件級別發生錯誤時,ErrorBoundary可以捕獲錯誤、然後將其組件節點以及它以下的所有節點都從渲染流程中移除,替換成宣告的fallback 組件。如:

import { ErrorBoundary } from 'react-error-boundary';

function App() {
  return (
    <ErrorBoundary fallback={<div>Something Wrong</div>}>
      <MyComponent />
    </ErrorBoundary>
  );
}

這樣的方式可以確保應用程序即便出現錯誤,也不會直接崩潰,能夠展示預定義的錯誤 UI。

手寫 ErrorBoundary

雖然一般情況下可以直接用react-error-boundary這個庫來直接使用ErrorBoundary ,但為了更大程度的控制和自由度,我們也可以自己實現一個簡單的ErrorBoundary 組件。

特別一提,在近期的React 版本中,幾乎沒有組件事用class的方式來寫,官方也是建議都以函數組件來開發,就是除了ErrorBoundary。其中最重要的就是getDerivedStateFromError的靜態方法以及componentDidCatch的生命週期方法:

  • getDerivedStateFromError---當子組件發生錯誤時更新 state,讓組件渲染 fallback UI。
  • componentDidCatch ---捕獲錯誤並執行自定義錯誤處理(例如記錄錯誤)。

Sentry 與 ErrorBoundary

在development mode時,React 是會將錯誤訊息從ErrorBoundary拋到全局,所以這時window.onerror是可以獲取到相關的error資訊;但在 production mode (也就是React build過後),ErrorBoundary會捕獲子組件的錯誤、然後拋一個簡單的錯誤訊息到全局而已,沒有相關的上下文。

image

因此,為了能夠捕獲更詳細的錯誤信息,Sentry 需要自己實現一個 ErrorBoundary 來攔截 React 的錯誤訊息。

手寫 ErrorBoundary instrument

根據 React 官方的示例,我們可以簡單修改,實現一個自定義的 ErrorBoundary 並將其與 跟我們寫的簡單 SDK 結合。

import React from 'react';

export default class MyErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false };
    }

    static getDerivedStateFromError(error) {
        // Update state so the next render will show the fallback UI.
        return { hasError: true };
    }

    componentDidCatch(error, info) {
        console.log('error:', error);

        // Example "componentStack":
        //   in ComponentThatThrows (created by App)
        //   in ErrorBoundary (created by App)
        //   in div (created by App)
        //   in App
        console.log('info.componentStack:', info.componentStack);
    }

    render() {
        if (this.state.hasError) {
            // You can render any custom fallback UI
            return this.props.fallback;
        }

        return this.props.children;
    }
}
  • getDerivedStateFromError---當 MyErrorBoundary的子組件發生錯誤的時候,React會調用該方法,強制更新 MyErrorBoundaryhasError state,觸發重新渲染並顯示fallback組件。
  • componentDidCatch---該方法允許我們捕獲錯誤並進行自定義處理,例如通過 Sentry SDK 上報錯誤。

將自己寫的ErrorBoundary結合到SDK

可以直接使用剛剛寫好的MyErrorBoundary,然後再包一層來方便調用

import MyErrorBoundary from './MyErrorBoundary';

export default class SelfSentry {
    static ErrorBoundary({ children, fallback }) {
        return (
            <MyErrorBoundary fallback={fallback}>{children}</MyErrorBoundary>
        );
    }
}

export const selfSentry = new SelfSentry();

然後就可以在App.jsx中調用這個自定義的 Error Boundary sdk 方法,並嘗試捕捉錯誤。

import { useState } from 'react';
import './App.css';
import SelfSentry, { selfSentry } from './self-sentry/index';
selfSentry.init();

const DemoError = () => {
    throw new Error('Demo Component error');
};

function App() {
    const [shouldMount, setShouldMount] = useState(false);
    return (
        <SelfSentry.ErrorBoundary fallback={<div>Something Wrong</div>}>
            <div>
                {shouldMount && <DemoError />}
                <button onClick={() => setShouldMount((pre) => !pre)}>
                    mount DemoError Component
                </button>
            </div>
        </SelfSentry.ErrorBoundary>
    );
}

export default App;

這邊就可以看到SDK捕獲到了 ErrorBoundary的error資訊:

image

小結

本文通過手寫一個自定義的 ErrorBoundary並結合 Self SDK,展示了如何在 React 應用中有效地捕獲錯誤。這種集成方式在開發模式下,能夠直接捕捉完整的錯誤上下文,並顯示具體的錯誤訊息。

然而,在 production mode 中(即應用被打包後),因為 JavaScript 被壓縮、混淆,我們還需要使用 source map 來將壓縮過的錯誤堆疊還原回原始的程式碼上下文。

本文的程式碼可在Github repository中查看。

ref

ChangeLog

  • 20240920--初稿

上一篇
Day05--Sentry是如何獲取error? 直接手寫看看(上)
下一篇
Day07--如何在錯誤發生時、同時獲取user actions記錄
系列文
全端監控技術筆記---從Sentry到Opentelemetry13
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言