使用【React】的错误边界,防止白屏同时在发生错误时将日志记录到Cloudwatch

首先

这是一个简单的插图,但解释了我想要做的事情。

当前情况

images_before.png

Webアプリ上で、予期しないエラーが発生した場合に、画面が真っ白(ホワイトアウト)になるのを回避したい

ReactのError boundaryの機能を使ってエラー発生時に予め用意したエラー画面へ飛ばすようにします。

予期しないエラーが発生した場合に、なぜエラーになったのかを解析できるようにログを残したい

ReactなどのSPA Webアプリケーションだと基本的にクライアントの中だけでアプリが動くので、エラーが発生した時の情報は意図的にサーバーに投げない限りは検知できません。
そこで、エラーが発生した時にAWSのcloudwatchにログを出力するようにします

Ideal:

images_after.png

错误边界

在React中,有一个名为Error boundary的功能,它可以很好地捕捉和处理错误。这个功能是在React 16之后引入的。

错误边界是一个React组件,用于捕获并记录其子组件树中发生的JavaScript错误,并在崩溃的组件树上显示备用界面。错误边界可以捕获在整个子树渲染、生命周期方法内和构造函数内发生的错误。

    Error Boundary

云观察

由于AWS提供了名为CloudWatch Logs的日志服务,因此我们将使用它。

    Amazon CloudWatch Logsとは

最终的成果形态

我会在登录功能中登录后的页面上故意制造错误并测试其行为。

之前

图像

before.gif

occure Errorボタンを押したあと、エラーによって画面が真っ白(ホワイトアウト)になっているのがわかります。

在之后

图像

after.gif

occure Errorボタンを押したあと、エラー用の画面に遷移させることができました。

日志 (rì zhì)

003_logDetail.png

当出现错误时,日志可以被输出到Cloudwatch。

所使用的库版本

我使用Vite进行构建并实现了基于TypeScript的开发。

ライブラリバージョン@aws-amplify/ui-react^4.3.3aws-amplify^4.3.10aws-amplify-react^5.1.9react-error-boundary^3.1.4vite^4.0.0

实施

基础建设

    viteでベースとなるReactを構築
yarn create vite

✔ Select a framework: › React
✔ Select a variant: › TypeScript
    package install
yarn add @aws-amplify/ui-react aws-amplify aws-amplify-react react-error-boundary

ログイン画面とログイン後画面の実装

App.tsxの元々の画面をpages/Home.tsxとして利用します

App.tsx

Amplify + cognitoでログイン画面周りを作る実装の詳細は検索すれば色々と記事が出てくるのでそちらを参照(このあたりとか)

import { Authenticator } from "@aws-amplify/ui-react";
import "@aws-amplify/ui-react/styles.css";
import { Amplify, Auth } from "aws-amplify";
import moment from "moment";
import { useEffect } from "react";
import Home from "./pages/home";

const App: React.FC = () => {
  useEffect(() => {
    Amplify.configure({
      Auth: {
        identityPoolId: // cognito identityId,
        region: // リージョン,
        userPoolId: // cognito ユーザープールID,
        userPoolWebClientId: // cognito クライアントID,
      },
    });
  }, []);

  return (
    <Authenticator hideSignUp={true}>
      <Home />
    </Authenticator>
  );
};

export default App;
    • pages/home.tsx

App.tsxの元々の実装の使い回し

import { useState } from "react";
import reactLogo from "../assets/react.svg";
import "../App.css";

const Home = () => {
  const [count, setCount] = useState(0);

  return (
    <div className="App">
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src="/vite.svg" className="logo" alt="Vite logo" />
        </a>
        <a href="https://reactjs.org" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </div>
  );
};

export default Home;

在这个阶段,登录界面和登录后的界面已经被实现。

错误边界

关于ErrorBoundary的实现,我们将使用react-error-boundary来简单地实现ErrorBoundary功能的包装库。

    • pages/error.tsx

まずエラー時に遷移させる先の画面を作ります。

import { FallbackProps } from "react-error-boundary";

const ErrorFallback = ({ error }: FallbackProps) => {
  return (
    <div>
      <h1>Error-boudanry</h1>
      <p>{error.message}</p>
      <button onClick={() => window.location.reload()}>reload</button>
    </div>
  );
};

export default ErrorFallback;
    • pages/home.tsx

ErrorBoundaryの設定と、わざとErrorを発生させるボタンを追加実装します。

import { useState } from "react";
import reactLogo from "../assets/react.svg";
import "../App.css";
+ import { ErrorBoundary } from "react-error-boundary";
+ import ErrorFallback from "./error";

const Home = () => {
  const [count, setCount] = useState(0);
+  const [isError, setError] = useState(false);

+  const onError = (error: Error, info: { componentStack: string }) => {
+    console.log("Error boundary", error.message);
+    console.log("Error boundary", info.componentStack);
+  };


  return (
    <div className="App">
+      <ErrorBoundary FallbackComponent={ErrorFallback} onError={onError}>
        <div>
          <a href="https://vitejs.dev" target="_blank">
            <img src="/vite.svg" className="logo" alt="Vite logo" />
          </a>
          <a href="https://reactjs.org" target="_blank">
            <img src={reactLogo} className="logo react" alt="React logo" />
          </a>
        </div>
        <h1>Vite + React</h1>
        <div className="card">
          <button onClick={() => setCount((count) => count + 1)}>
            count is {count}
          </button>
          <p>
            Edit <code>src/App.tsx</code> and save to test HMR
          </p>
+          <button onClick={() => setError((preError) => !preError)}>
+            occure Error
+          </button>
+          {isError ? (
+            <>
+              <p>エラーを起こす</p>
+              <ThrowError />
+            </>
+          ) : (
+            <></>
+          )}
        </div>
        <p className="read-the-docs">
          Click on the Vite and React logos to learn more
        </p>
+      </ErrorBoundary>
    </div>
  );
};

export default Home;

+ function ThrowError(): JSX.Element {
+   throw new Error("manual throw Error");
+ }

after.gif

将日志输出到CloudWatch

使用Amplify功能实现将日志输出到CloudWatch。
请确保将日志写入CloudWatch的权限授予关联到Cognito的IAM角色。

实施

    • pages/home.tsx

Error発生時(onError発火時)に、cloudwatchへログを書き込むように処理を追加します。

import { useState } from "react";
import reactLogo from "../assets/react.svg";
import "../App.css";
import { ErrorBoundary } from "react-error-boundary";
import ErrorFallback from "./error";
+ import {
+   Logger as AmplifyLogger,
+   AWSCloudWatchProvider,
+   Auth,
+   Amplify,
+ } from "aws-amplify";

const Home = () => {
  const [count, setCount] = useState(0);
  const [isError, setError] = useState(false);

  const onError = (error: Error, info: { componentStack: string }) => {
    console.log("Error boundary", error.message);
    console.log("Error boundary", info.componentStack);

+    const awsLogger = new AmplifyLogger("log-prefix", "INFO");
+    Amplify.register(awsLogger);
+    awsLogger.addPluggable(new AWSCloudWatchProvider());
+    awsLogger.error("Error boudary", error.message, info.componentStack);
  };

  return (
    <div className="App">
      <ErrorBoundary FallbackComponent={ErrorFallback} onError={onError}>
        <div>
          <a href="https://vitejs.dev" target="_blank">
            <img src="/vite.svg" className="logo" alt="Vite logo" />
          </a>
          <a href="https://reactjs.org" target="_blank">
            <img src={reactLogo} className="logo react" alt="React logo" />
          </a>
        </div>
        <h1>Vite + React</h1>
        <div className="card">
          <button onClick={() => setCount((count) => count + 1)}>
            count is {count}
          </button>
          <p>
            Edit <code>src/App.tsx</code> and save to test HMR
          </p>
          <button onClick={() => setError((preError) => !preError)}>
            occure Error
          </button>
          {isError ? (
            <>
              <p>エラーを起こす</p>
              <ThrowError />
            </>
          ) : (
            <></>
          )}
        </div>
        <p className="read-the-docs">
          Click on the Vite and React logos to learn more
        </p>
      </ErrorBoundary>
    </div>
  );
};

export default Home;

function ThrowError(): JSX.Element {
  throw new Error("manual throw Error");
}

可以使用AmplifyLogger的第2个参数来设置是否将日志级别及更高级别的日志输出到Cloudwatch。
在这个例子中,我们设置了INFO级别,所以INFO、WARN和ERROR级别的日志都会输出到Cloudwatch。

    • App.tsx

Cloudwatchへの出力のためのAmplifyの設定を追加します。

import { Authenticator } from "@aws-amplify/ui-react";
import "@aws-amplify/ui-react/styles.css";
import { Amplify, Auth } from "aws-amplify";
import moment from "moment";
import { useEffect } from "react";
import Home from "./pages/home";

const App: React.FC = () => {
  useEffect(() => {
    Amplify.configure({
      Auth: {
        identityPoolId: // cognito identityId,
        region: // リージョン,
        userPoolId: // cognito ユーザープールID,
        userPoolWebClientId: // cognito クライアントID,
      },
+      Logging: {
+        // ログの種類ごと
+        logGroupName: `/error-boundary/testlogging`,
+        // ログを出力するアプリケーションのインスタンスごと
+        logStreamName: '/hoge',
+        region: poolConf.region
+      },
    });
  }, []);

  return (
    <Authenticator hideSignUp={true}>
      <Home />
    </Authenticator>
  );
};

export default App;

logGroupNameで Cloudwatch上にログを出力する際のロググループを、logStreamNameでログストリーム名を設定しています。

Loggingの中にregionを設定しないと以下のようなエラーが出て動作しませんでした。
react_devtools_backend.js:4012 [ERROR] 06:03.501 AWSCloudWatch – error getting log group – Error: Region is missing
react_devtools_backend.js:4012 [ERROR] 06:03.502 AWSCloudWatch – failure during log group search: Error: Region is missing
react_devtools_backend.js:4012 [ERROR] 06:03.502 AWSCloudWatch – failure while getting next sequence token: Error: Region is missing
react_devtools_backend.js:4012 [ERROR] 06:03.503 AWSCloudWatch – error during _safeUploadLogEvents: Error: Region is missing
react_devtools_backend.js:4012 [ERROR] 06:03.503 AWSCloudWatch – error when calling _safeUploadLogEvents in the timer interval – Error: Region is missing

AWS 的配置

给与与Cognito相关联的IAM角色对Croudwatch的写入权限。

006_iam_policy.png

总结

    • Reactの中でエラーが発生した場合に自動的にキャッチしてエラー画面に遷移すること。

 

    エラー画面に遷移した場合にエラーの内容をCloudwatchへ連携すること。

可以实施了。

這個Cloudwatch的實現,這次只應用在錯誤部分,但是也可以應用在目的為追踪用戶操作的Cloudwatch輸出,例如每個用戶按鈕點擊或者畫面轉換。使用console.log,可以很容易地在SPA中進行用戶操作追踪,我認為這相當不錯。

请仿写以下内容的中文原文,只需要提供一种选项:

“Can you give me a reference for this book?”

    • AWS を用いたフロントエンドモニタリング入門

 

    • Amplify でウェブフロントエンドのログを CloudWatch に送る

 

    • AWS Amplify で CloudWatchLogs にカジュアルにログを送りたい

 

    Error: Region is missing の解決に参考になった issue
广告
将在 10 秒后关闭
bannerAds