ReactアプリケーションにSentryを導入してみる（Webpack使用）

[React Native版の記事はこちら](https://qiita.com/mildsummer/items/56dd85802a479690451d)です。

SentryをReactアプリケーションに導入してみます。前半はReactに限らず、ブラウザ上で動かすJSに共通する設定になります。

設定に必要な基本的な情報はすでに取得している状態を想定して進めていきます。
最低限の使用の場合は、`@sentry/browser`をインストールし、以下のコードをJSの最上位ファイルに追加するだけです。

```console
$ npm install @sentry/browser
```

```App.js
import * as Sentry from '@sentry/browser';

Sentry.init({
  dsn: "ここにプロジェクトのDSN"
});

throw new Error('error'); // 適当にエラーをスローしてみる
```

![Error__error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514229/9088f376-710a-e860-b77e-cac417134c50.png)

ローカルで動かしてみると、問題なくSentryのissuesの中にエラーイベントが入りました。

このままでも使えるのですが、ソースマップファイルやリリース情報をSentryに送信してログをより見やすくするために、Webpackの設定を追加してみます。

[公式のドキュメント](https://docs.sentry.io/platforms/javascript/sourcemaps/#webpack)に従いますが、必要のない設定項目は割愛しています。
まずは`.sentryclirc`というファイルを追加して、Webpackでビルドする時にSentryのプロジェクトにアクセスできるようにします。

```text:.sentryclirc
[auth]
token=Authトークン
[defaults]
org=組織名
project=プロジェクト名
```

公式のWebpackプラグインをインストールして、

```console
$ npm install --save-dev @sentry/webpack-plugin
```

```js:webpack.config.js
const SentryWebpackPlugin = require('@sentry/webpack-plugin');

...
plugins: [
  new SentryWebpackPlugin({
    include: "build/static/js"
  }),
  ...
]
...
```

Webpackの`plugins`に追加します。ここで`include`に、ソースマップファイルを送信してほしいJSのディレクトリを指定します。
デフォルトではディレクトリ内の`node_modules`以外の全てのJS/CSSファイルが送信されます。

そのほかのオプションは[こちら](https://github.com/getsentry/sentry-webpack-plugin#options)。
オプション一覧の最初の行を確認してください。このオプションに`release`文字列を渡せば任意のリリース情報を設定できますが、デフォルトではgitのコミットIDがそのままリリース情報として使用されるようです。

この段階でもしgitの設定をしていなければ、`git init`から`add`、`commit`まで一通りしてから、Webpackでビルドしてみます。

```console
Creating an optimized production build...
> Analyzing 6 sources
> Rewriting sources
> Adding source map references
> Bundled 6 files for upload
> Uploaded release files to Sentry
> File upload complete

Source Map Upload Report
  Minified Scripts
    ~/2.0f707d42.chunk.js (sourcemap at 2.0f707d42.chunk.js.map)
    ~/main.663c8045.chunk.js (sourcemap at main.663c8045.chunk.js.map)
    ~/runtime-main.d5ffe654.js (sourcemap at runtime-main.d5ffe654.js.map)
  Source Maps
    ~/2.0f707d42.chunk.js.map
    ~/main.663c8045.chunk.js.map
    ~/runtime-main.d5ffe654.js.map
Compiled with warnings.
```

こんな感じでコンソールにソースマップの情報が表示されます。
あとは何かしらサーバーにデプロイしてみてください。これでSentryの準備は完了です。

デプロイしたページを開いて、エラーを発生させてみると、

![Error__error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514229/feed06be-b3ff-128e-9264-8c0f535adf1f.png)

まず、`RELEASE`欄にちゃんとコミットIDが入っているのがわかります。

![Error__error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514229/2cbddbb5-2ca7-b38d-dcbc-bb2bf5ed92e3.png)

ソースマップが送信できているので、エラーの発生箇所も確認できます。

## ReactのError Boundaryでレポートダイアログを使用してみる

Sentryには、エラーが発生した時にユーザーからフィードバックを得るためのフォームを表示する機能が用意されています。

User Feedback
https://docs.sentry.io/enriching-error-data/user-feedback/?platform=javascript

これをReactのError Boundaryで使用する例がドキュメントに載っていたので、試してみました。
https://docs.sentry.io/platforms/javascript/react/#error-boundaries

```jsx:SentryBoundary.js
import React, { Component } from 'react';
import * as Sentry from '@sentry/browser';

class SentryBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { eventId: null };
  }

  // エラーが発生した時にstateを更新する
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  // エラーが発生した時にSentryのscope（エラーの情報をまとめるもの）を作成
  componentDidCatch(error, errorInfo) {
    Sentry.withScope((scope) => {
      scope.setExtras(errorInfo); // エラーの情報をセット
      const eventId = Sentry.captureException(error); // Sentryにエラーを送信し、IDを取得
      this.setState({ eventId }); // エラーIDをstateにセット
    });
  }

  render() {
    if (this.state.hasError) { // エラーが発生したら画面にボタンを表示する
      return (
        <button
          onClick={() => Sentry.showReportDialog({ eventId: this.state.eventId })} // Sentryのダイアログを表示
        >
          エラーを報告する
        </button>
      );
    }

    // 何もない場合はそのまま子要素を表示
    return this.props.children;
  }
}

export default SentryBoundary
```

[Error Boundary](https://ja.reactjs.org/docs/error-boundaries.html)自体はReactの機能（実装パターン）です。

```jsx:App.js
<SentryBoundary>
  <ChildComponent />
</SentryBoundary>
```

使用する単位は任意ですが、このように子要素をError Boundaryで囲っておいて、子要素のレンダリングで何かしらエラーが発生した時に代りに別の要素を表示したりすることができます。

この例では、エラーが発生した時に「エラーを報告する」ボタンを表示し、ボタンが押されたらSentryのダイアログを表示するようにしています。

実際にError Boundaryの子要素でエラーが発生するようにしてみます。

例えば下記のように、Reactのレンダリングとは関係ないところでエラーを発生させてもError Boundaryはcatchしません。

```jsx:ChildComponent.js
import React from 'react';

function ChildComponent() {
  window.setTimeout(() => {
    throw new Error('error test');
  }, 1000);
  return (
    <div>
      エラーを発生させます...
    </div>
  );
}

export default ChildComponent;
```

このように、Reactのレンダリングが不可能になるエラーが発生した場合にError Boundaryがエラーをcatchします。
（例えば、returnするJSXの中で存在しないfunctionを叩く）

```jsx:ChildComponent.js
import React from 'react';

function ChildComponent() {
  return (
    <div>
      エラーを発生させます...
      {window.foo()}
    </div>
  );
}

export default ChildComponent;
```

![React_App_と_sentry-test____works_tamaki_sentry-test__-_____src_ChildComponent_js__sentry-test_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514229/adc9fc47-db25-015b-8cda-01bee24e67e8.png)
起動してみると、「エラーを発生させます...」という文言の代わりにボタンが表示されました。

これを押すと、このようなSentryのダイアログが表示されます。
![React_App.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514229/e728b2d5-2e4c-c218-a835-52924a2a9b11.png)

名前とメールアドレス、詳細を入力して送信してみると...

Sentryのエラー詳細の方にUser Feedbackがちゃんと追加されました。
![TypeError__window_foo_is_not_a_function.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514229/f1a4311b-eaba-e1b9-e824-dc702792f8ec.png)

ダイアログの文言なども変更できるようです。
https://docs.sentry.io/enriching-error-data/user-feedback/?platform=javascript#customizing-the-widget

言語は自動的にブラウザの言語から設定されるようですが、一番大きなタイトルが英語のままだったので、オプションを渡してみます。ユーザーの名前・メールアドレスは最初から指定すれば最初から入力しておけるようです。

```jsx:SentryBoundary.js
...
        <button
          onClick={() => Sentry.showReportDialog({
            eventId: this.state.eventId,
            title: "すみません、問題が発生しました",
            user: {
              email: "test@test.com",
              name: "React 和子"
            }
          })}
        >
          エラーを報告する
        </button>
...
```

![React_App.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514229/fb90c4b1-9a20-1411-1b83-1f87a9a7c88b.png)

問題なく設定できました。

![React_App.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514229/6b09660e-a392-f4f1-8141-94943867c9ea.png)

ダイアログにはclassがセットされているので、見た目のカスタマイズもCSSで簡単にできそうです。

以上、ReactNativeとWebでSentryを触ってみました。特に嵌るところもなく、スムーズに導入できました。