どうも。フロントエンドのテックリードをやってるっぽい、みゅーとん(@_mew_ton)です。
nuxt で sentry を使う方法を調べると、 @nuxt/sentry
(https://sentry.nuxtjs.org/) に行き着くことが多いかと思われます。
しかし、このライブラリは nuxt2 のみに対応しており、nuxt3 への対応は一向に進んでいない様子が見られます。
そこで、独自に対応しました。以下にその方法をまとめていきます。
TL;DR
- クライアントサイドは nuxt plugin、サーバサイド(SSR / api) は nitro plugin で実装する
- アクセス制限を導入している環境を想定すると sentry tunnel api を nitro に作ると良い
この記事で扱わないこと
- sentry とはなにか / 入門
- sentry に mapfile を publish する方法
要件
sentry 導入に際し、以下の要件がありました。
- レンダリング方法は基本的に SSR
- nitro サーバ を BFF としても利用しており、 server/api に API エンドポイントを実装している
- toB 向けアプリを作っており、ネットワーク制限がかかっている環境が存在しうる
クライアントサイド
以下のコードを src/plugins/sentry.client.ts
として実装すればOKです。
import type { NuxtApp } from '#app' export default defineNuxtPlugin(async ({ vueApp }) => { // dynamic import することで、バンドルサイズを軽減する const sentry = await import('@sentry/vue') sentry.init({ dsn: '', // DSN app: vueApp, /** ここに各種設定値を記載 */ }) })
上記コードでは、@sentry/vue
を動的 import しています。build した際にバンドルされる entry.(hash).js
が肥大化してしまうのを、これで防いでいます。
また、ファイル名を sentry.client.ts
とすることで、クライアント側のみの plugin として作用するようになっています。
サーバサイド (SSR / nitro server)
nuxt の SSR は nitro 上で動作するため、サーバサイドのロジックに sentry を仕込みたい場合は、nitro の plugin を使用する必要があります。
以下のコードを src/server/plugin/sentry.ts
として実装すればOKです。
import * as Sentry from '@sentry/node' export default defineNitroPlugin(async (nitro) => { Sentry.init({ dsn: '', // DSN /** ここに各種設定値を記載 */. }) })
@sentry/vue
ではなく @sentry/node
をインポートしている点に注意してください。ブラウザではなく、 nodejs 上で動作するため、このようになっています。
また、バンドルサイズを考慮しなくて良いため、動的 import は行っていません。
tunnel API
クライアントサイドでエラーを sentry 検知したとき、それをそのまま sentry のサーバに post しようとします。これが、仮に「ネットワークアクセス制限を有効にしている」環境であれば、sentry に post できず、エラーを集積することができなくなります。
このケースでは、server/api に sentry tunnel api を生やし、BFF経由で sentry に通知させる対応方法があります。
例として、 /api/__sentry
に post する前提で対応する場合を紹介します。
クライアントサイドの書き換え
src/plugins/sentry.client.ts
にて、sentry の初期化に以下を追記します
sentry.init({
dsn: '', // DSN (省略しない!!)
app: vueApp,
+ tunnel: '/api/__sentry',
})
この設定をすると、ブラウザ側でエラーを検知した際に /api/__sentry
に対して post するようになります。
tunnel api を追加
src/server/api/__sentry
に index.post.ts
を追加します
import { type H3Event, defineEventHandler, readRawBody } from 'h3' async function sendToSentry(event: H3Event): Promise<void> { const body = await readRawBody(event) try { await $fetch("" /** Sentry Envelope URL */, { method: 'POST', body }) } catch (error) { // レスポンスエラーは sentry にハンドリングさせるため、 console.error に出力する // エラーレスポンスとして返さず、ここでエラーを握りつぶす console.error(error) } } export default defineEventHandler<null>((event) => { // 非同期的に処理を行うため、レスポンスを待たない sendToSentry(event) return null })
sentry DSN が https://hogehoge@oxxxxxx.ingest.sentry.io/12345678
だとすると、envelope url は https://oxxxxxx.ingest.sentry.io/api/12345678/envelope/
になります。
ここでは、 post に含まれるデータをそのまま envelope url に送るだけの処理をしています。
また、クライアントサイドにレスポンスを待たせたくないため、sendToSentry をあえて await しないロジックにしています。(効果があるかはわかりませんが・・)
まとめ
nuxt3 で sentry を利用する方法をまとめました。
エラー検知の一助となれば幸いです。
また、うまく知見がたまってきたら、nuxt module として汎用化も考えています。
株式会社hacomonoでは一緒に働く仲間を募集しています!
採用情報や採用ウィッシュリストも是非ご覧ください!