Webpackを使っているElectronアプリにSentry導入してみた
ElectronアプリにSentryを導入し、エラーの詳細を把握できるようにしてみました。
Webpackの設定手順が煩雑なので実際のプロジェクトでは electron-webpack (Doc) や electron-react-boilerplate (Doc)などのテンプレートプロジェクトを元にプロジェクトを作っていますが、今回は新規にアプリケーションを作ってSentryでエラーを補足できるまでの手順の詳細を書いてみます。
手順の詳細を把握することで仕組みが理解でき、既に動いているElectronアプリに関しても導入が容易になるかと思います。 Sentryでエラーを補足するためにsource mapを送る方法がいくつかあるのですが、一番手間が少ないのでWebpack Pluginを使った方法をご紹介します。
サンプルプロジェクト
今回作ったサンプルプロジェクトをgithubにコードを上げておいたので、よかったら参考にしてみてください。 github.com
環境
webpack4
からは設定ファイル webpack.config.js
が必要なくなったという理由から、 webpack-cli/init@0.3
では自動的に生成しなくなったので、バージョン 0.2.2
を使用しました。
ドキュメント
Sentry
導入するにあたって事前にSentryの以下のドキュメントを読み、オプション等を把握しておきました。
Electron crash-reporter
また、Sentry SDK( @sentry/electron
)の内部で使っているElectronのcrash-reporterのドキュメントも合わせて読んでおいて
- crash-reporterが元々どういう機能のもの
- Sentry側がどこ設定を簡略化してくれるのか
を把握でき、何かトラブルが発生した時に対処できるようになると思いますので、一読しておくと良いと思います。
事前準備
Sentryのアカウントなければ、取得しておきます。
Sentryのプロジェクト準備
プラットフォームにElectronを選択し、Sentryのプロジェクトを作っておきます。
以下のように開発プロジェクト側の設定手順が画面が表示されるので、基本的には同じ事を設定すればOKです。最新(2020年7月現在)のバージョンでは注意点があるので、追って解説していきます。
プロジェクトの作成
設定ファイル(package.json, webpack.config.jsを作成)
プロジェクトをディレクトリを作ります。
$ mkdir electron-sentry $ cd electron-sentry
package.json
の初期ファイルを作ります。
$ npm init -y
Webpackの初期ファイルを作ります。
$ npm i @webpack-cli/init@0.2.2 --save-dev $ npx webpack-cli init # Electronはメインプロセスのエントリファイルが1つのためNoにする ? Will your application have multiple bundles? No # mainプロセスのjsファイルをmainにしたいのでmainにする ? Which will be your application entry point? main # Webpackビルド後のファイルの出力先をdistにする。outputとかフォルダ変えても良い。 ? In which folder do you want to store your generated bundles? 'dist' # Typescriptでも良いがES6にする。今回CSSは使わないけど、SASSにする。 ? Will you use one of the below JS solutions? ES6 ? Will you use one of the below CSS solutions? SASS ? If you want to bundle your CSS files, what will you name the bundle? (press en ter to skip) main conflict package.json # package.jsonを上書き ? Overwrite package.json? overwrite force package.json create .babelrc create main.js create README.md
Electron、sentry関連、HTMLバンドルのためのパッケージ追加
各種パッケージ追加
$ npm i electron @sentry/electron @sentry/webpack-plugin html-webpack-plugin --save-dev
メインプロセスのjs(main.js)、レンダラプロセスの画面(index.html)・js(renderer.js)を作る
シンプルになコードを作ってみました。(全てプロジェクト直下に追加し、既に存在するmain.jsは上書きしました。)
注意点は、以下の3点です。
- Sentry SDKのオプションdsnには、上記のSentryのプロジェクト準備で表示されたURLを入れます。
- Sentry SDK のinit関数をメインプロセスとレンダラプロセスで分けます
- メインプロセスの場合
@sentry/electron/dist/main
から読む - レンダラプロセスの場合は
@sentry/electron/dist/renderer
読み込む - ※そうしないと
TypeError: mod.require is not a function
というエラーが発生。参考issue
- メインプロセスの場合
- init関数はメインプロセスの場合、app.on("ready")より前、できるだけ早いタイミングで呼ぶと良いようです
【メインプロセスのjsファイル(main.js)】
# main.js const { app, BrowserWindow } = require("electron") import * as path from 'path'; import * as url from 'url'; import { init } from '@sentry/electron/dist/main' import * as Sentry from '@sentry/electron' let win; // Sentry SDKのinit。この後メインプロセス内でエラーを補足するとSentryに通知。 init({dsn: 'https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@oxxxxxx.ingest.sentry.io/xxxxxxx'}) // 画面表示(レンダラプロセス起動) function createWindow() { win = new BrowserWindow({ webPreferences: { nodeIntegration: true }, width: 800, height: 600, webSecurity: false }); win.loadURL( url.format({ pathname: path.join(__dirname, 'index.html'), protocol: 'file:', slashes: true }) ); win.on("closed", () => { win = null; }); } // readyのライフサイクルでレンダラプロセスを表示 app.on("ready", createWindow); // ウィンドウを全部閉じたらappを終了 app.on("window-all-closed", () => { if (process.platform !== "darwin") { app.quit(); } }); // ウィンドウアクティブ時にwinオブジェクトがなければ画面表示 app.on("activate", () => { if (win === null) { createWindow(); } })
【レンダラプロセスの画面ファイル(index.html)】
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>electron-sentry | Electronでsentry通知するサンプル</title> </head> <body> <h1>レンダラプロセスの画面</h1> <!-- レンダラプロセスのJSを読み込む --> <script src="dist/renderer.js"></script> </body> </html>
【レンダラプロセスのjsファイル(renderer.js)】
import { remote } from 'electron' const { app } = remote import { init } from '@sentry/electron/dist/renderer' import * as Sentry from '@sentry/electron' // Sentry SDKのinit。この後レンダラプロセス内でエラーを補足するとSentryに通知。レンダラプロセスが複数ある場合、その全てでinitする必要があります。 init({dsn: 'https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@oxxxxxx.ingest.sentry.io/xxxxxxx'}) document.addEventListener("DOMContentLoaded", () => { // 定義していない関数を呼び出して、エラーを発生させる myUndefinedFunction() })
Webpack設定ファイル webpack.config.js
の編集
ポイントとしては
- Webpackの出力先(output)
- アプリケーション実行に必要なファイルがdist内のみに収まるようにHtmlWebpackPluginでレンダラ画面のhtmlもコピー
- SentryCliPluginの引数
となるようにしてみました。
const SentryCliPlugin = require('@sentry/webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const path = require('path'); const webpack = require('webpack') const package = require('./package.json'); const sentryRelease = `${package.name}${package.version}`; const main = { mode: 'development', devtool: 'source-map', target: 'electron-main', node: { __dirname: false, __filename: false, }, entry: './main.js', output: { path: __dirname + '/dist', filename: 'main.js' }, plugins: [ new SentryCliPlugin({ release: sentryRelease, include: './dist' }) ] } const renderer = { mode: 'development', devtool: 'source-map', target: 'electron-renderer', node: { __dirname: false, __filename: false, }, entry: { renderer: './renderer.js', }, output: { path: __dirname + '/dist', filename: 'renderer.js' }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, './index.html'), }), new SentryCliPlugin({ release: sentryRelease, include: './dist' }) ] } module.exports = [ main, renderer ]
npm設定ファイル package.json
の編集
上記で生成したpackage.json
のmainとscriptsを変更します。
electron
がWebpackで生成した dist/main.js
を実行するようにしておきます。
// 〜〜〜 "main": "dist/main.js", "scripts": { "start": "electron .", } // 〜〜〜
.envファイルを追加
SentryCliPluginはWebpackのバンドル時に、自動的に.env等の設定があればそれを参照し、distに出力されたファイルをsentryに送信してくれるので、.envファイルをプロジェクト配下に作っておきます。
# .env SENTRY_DSN= https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@oxxxxxx.ingest.sentry.io/xxxxxxx SENTRY_ORG=[org name] SENTRY_PROJECT=electron-sentry SENTRY_AUTH_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sentry cliの認証に関しての詳細は、以下のドキュメントに詳しく書いていました。 Configuration and Authentication | Sentry Documentation
また、AUTH_TOKENは、Sentryのアカウント毎に以下のURLから生成することができます。 https://sentry.io/settings/account/api/auth-tokens/
Sentryでエラーの捕捉を確認
Electronアプリ実行してエラーを確認する前に
- npmパッケージインストール
- distディレクトリに実行するコードをwebpackで生成
- Sentryにsource mapを送る
をしておきます。 以下を実行してください
$ npm install $ npx webpack > Found 4 release files > Analyzing 4 sources > Rewriting sources > Adding source map references > Bundled 4 files for upload > Uploading release files... > Source Map Upload Report > Scripts > ~/main.js > ~/renderer.js > Source Maps > ~/main.js.map > ~/renderer.js.map > Uploaded release files to Sentry > File upload complete
Electronアプリを起動すると
$ npm start
画面表示後に、chrome developer toolsを開き(ショートカットはCtrl+Shift+i)、consoleにエラーが出ていることが確認できます。
Sentryのコンソールを開いてみても、エラーが発生していることが確認できます。ソースコードが読めているのでsource mapsもちゃんと送れていそうです。
また、Webpackでバンドルした際にSentryにsource mapが送られていることも確認しておきます。ReleasesのArtifactsから確認できます。
うまくソースコードが表示できない場合
エラーは通知されるのに、Sentry上でうまくソースコードが表示されない時は大体
- 通知されたコードのパス
- SentryのReleasesのArtifactsのファイルのパス
がずれていることが原因です。そういう時は、urlPrefixをつけて相対パスを指定してあげたりすれば、表示されるようになります。
new SentryCliPlugin({ include: './js', release: sentryRelease, urlPrefix: '~/js' }),