こんにちは。
ソーイ株式会社、入社1年目の村上です。
業務として、あるコンポーネントファイルのJSエラーをSentryに通知するよう実装を行った話です。
実装を行った理由としては、Agoraを使った通話システム内でネットワーク通信切断により発生するコンポーネントエラーをSentryで確認&管理できるようにしたいという要件から実装を行いました。
今回は同じ状況のサンプルページを作成して実装を真似してみます。
この記事で分かること
・Laravel(バックエンド)とVue(フロントエンド)の両方にSentryを組み込み、それぞれのエラーを通知する方法
・コンポーネントファイルで発生するJSエラーをSentryに通知する方法
・Sentryに通知される形を整える方法
全体構成
ここでいう通話室は1対1の通話環境を提供するものです。
この流れを意図的にJSエラーを発生するページを作成して検証します。
Sentry SDKのダウンロード
まずはSentry SDKのダウンロードを行います。
npmでダウンロードしましょう。
npm install @sentry/vue --save
なお、※ npm 5以降では
npm install実行時、自動的にpackage.jsonのdependenciesに追加されるため、--saveオプションは不要です。
ダウンロード完了したら、package.jsonを確認しましょう。
下記のようにdependenciesの欄に'sentry/vue'があったら成功です。
"dependencies": {
"@sentry/vue": "^7.120.4",
"node": "^25.5.0",
"vue": "^3.5.27"
}
SentryとVueをつなげる
まずSentryと今回の作成プロジェクトとつなげます。
やることは以下の2つです。
- Sentry側でプロジェクト作成->DSNの取得
- Vue側の環境変数に登録
Sentry側の設定を進めましょう。
Sentry の管理画面で Laravel プロジェクトを作成すると
DSNが発行されるため、それを.envに設定します。
# Sentry Laravel SDK をインストール
composer require sentry/sentry-laravel
# DSNは.envに設定
SENTRY_LARAVEL_DSN=https://<your-dsn>
上記コマンド入力後、Sentryにエラー通知が届くか下記コマンドで確認できます。
php artisan sentry:test
これを入力した後、Laravelへの登録が完了している場合、ダッシュボードにテスト通知が送られてきます。

ここまで確認できれば、あなたのプロジェクトはSentryとつながっていることがわかります。
設定は終了です。
フロントに組み込む
今回はボタンを押したらエラーが出るページを作成し、通知を受け取ってみます。
今回はVue3のViteを使った環境で行います。
<template>
<div class="error-page">
<h1>エラーの間</h1>
<button @click="triggerError" class="error-button">
エラーを発生させる
</button>
</div>
</template>
<script>
import * as Sentry from '@sentry/vue';
export default {
name: 'ErrorPage',
methods: {
triggerError() {
// 意図的にエラーを発生させる
try {
// 存在しないプロパティにアクセス
const obj = null;
obj.nonExistentProperty.someMethod();
} catch (error) {
// Sentryにエラーを送信
Sentry.captureException(error);
// エラーを再スローしてコンソールにも表示
throw error;
}
}
}
}
</script>
import { createApp } from 'vue';
import * as Sentry from '@sentry/vue';
import ErrorPage from '../components/ErrorPage.vue';
// Vueアプリケーションの作成
const app = createApp(ErrorPage);
// Sentryの初期化(Vueアプリ作成後)
const sentryDsn = import.meta.env.VITE_SENTRY_DSN;
if (sentryDsn) {
try {
Sentry.init({
app: app,
dsn: sentryDsn,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
],
tracesSampleRate: 1.0, // 本番環境では0.1などに設定
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
});
} catch (error) {
console.warn('Sentry初期化エラー:', error);
}
} else {
console.warn('VITE_SENTRY_DSNが設定されていません。Sentryは無効です。');
}
// Vueアプリケーションをマウント
app.mount('#app');
環境変数設定
Viteでは、クライアント側のコードから参照できる環境変数はVITE_プレフィックス付きのものに限定されています。
そのため、Vue側でSentryを初期化するにはVITE_SENTRY_DSNを使用し、Laravel側では通常のSENTRY_DSNを使用します。
そのため環境変数としてVITE_SENTRY_DSNを追加しましょう。
といってもSENTRY_DSNと中身は同じものでいいです。
VITE_SENTRY_DSN=SENTRY_LARAVEL_DSNと同じものを定義
動作検証
この状態でフロントのエラーを起こすボタンを押してみると...?


無事に通知が届きましたね。
より深い実装を
通知が来るまではできましたが、実装したシステム向けではありませんでした。
なぜなら利用ケースを考えたとき、Sentryの仕様が足を引っ張ると感じたためです。
ここから以下2つの修正を施しました。
・発生場所ごとにIssue分けを行う
・通知や一覧画面でエラーが起きた場所を把握できる
Issue分けとURLを参照して表示
開発システムの仕様上、SentryのIssueの扱いが運用面で課題になると感じました。
対象の通話システムではAgoraを使用しており、通信断などの影響で
同一コンポーネントから同じJavaScriptエラーが複数の通話室で発生するケースがあります。
この場合、Sentryではコンポーネントが同じであれば発生場所が異なっていても同一Issueにグルーピングされるため、Issue Alertの設定によっては初回発生時しか通知されず、別の通話室で再発しても検知できないことがあります。
そのため、確認や管理をしやすくする目的で、通話室IDごとにIssueを分ける実装を行いました。
サンプルページとして通話室作成ボタンを設置し、room/{通話室ID} のURLをIssueに含めることで、通話室ごとにエラーを識別できるようにしています。
<template>
<div class="error-page">
<h1>エラーの間</h1>
<button @click="triggerError" class="error-button">
エラーを発生させる
</button>
<button @click="createRoom" class="room-button">
通話室を作成
</button>
</div>
</template>
<script>
import * as Sentry from '@sentry/vue';
export default {
name: 'ErrorPage',
methods: {
triggerError() {
// 意図的にエラーを発生させる
try {
// 存在しないプロパティにアクセス
const obj = null;
obj.nonExistentProperty.someMethod();
} catch (error) {
// Sentryにエラーを送信
Sentry.captureException(error);
// エラーを再スローしてコンソールにも表示
throw error;
}
},
createRoom() {
// ランダムな通話室IDを生成
const roomId = Math.random().toString(36).substring(2, 10);
// 通話室ページに遷移
window.location.href = `/room/${roomId}`;
}
}
}
</script>
<style scoped>
.error-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 2rem;
}
h1 {
font-size: 2.5rem;
margin-bottom: 2rem;
color: #333;
}
.error-button {
padding: 1rem 2rem;
font-size: 1.2rem;
background-color: #ef4444;
color: white;
border: none;
border-radius: 0.5rem;
cursor: pointer;
transition: background-color 0.3s;
}
.error-button:hover {
background-color: #dc2626;
}
.room-button {
margin-top: 1rem;
padding: 1rem 2rem;
font-size: 1.2rem;
background-color: #3b82f6;
color: white;
border: none;
border-radius: 0.5rem;
cursor: pointer;
transition: background-color 0.3s;
}
.room-button:hover {
background-color: #2563eb;
}
</style>
<template>
<div class="room-page">
<h1>通話室: {{ roomId }}</h1>
<p class="room-info">通話室ID: {{ roomId }}</p>
<div class="button-group">
<button @click="triggerError" class="error-button">
エラーを発生させる
</button>
<button @click="closeRoom" class="close-button">
通話室を閉じる
</button>
</div>
</div>
</template>
<script>
import * as Sentry from '@sentry/vue';
export default {
name: 'RoomPage',
props: {
roomId: {
type: String,
required: true
}
},
methods: {
triggerError() {
// 意図的にエラーを発生させる
try {
// 存在しないプロパティにアクセス
const obj = null;
obj.nonExistentProperty.someMethod();
} catch (error) {
// Sentryにエラーを送信(通話室IDをfingerprintに含める)
Sentry.withScope((scope) => {
// fingerprintに通話室IDを含めて、通話室ごとに別エラーとして扱う
scope.setFingerprint(['room', this.roomId, error.message]);
scope.setTag('room_id', this.roomId);
Sentry.captureException(error);
});
// エラーを再スローしてコンソールにも表示
throw error;
}
},
closeRoom() {
// トップページに戻る
window.location.href = '/';
}
}
}
</script>
<style scoped>
.room-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 2rem;
}
h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
color: #333;
}
.room-info {
font-size: 1rem;
color: #666;
margin-bottom: 2rem;
}
.button-group {
display: flex;
flex-direction: column;
gap: 1rem;
}
.error-button {
padding: 1rem 2rem;
font-size: 1.2rem;
background-color: #ef4444;
color: white;
border: none;
border-radius: 0.5rem;
cursor: pointer;
transition: background-color 0.3s;
}
.error-button:hover {
background-color: #dc2626;
}
.close-button {
padding: 1rem 2rem;
font-size: 1.2rem;
background-color: #6b7280;
color: white;
border: none;
border-radius: 0.5rem;
cursor: pointer;
transition: background-color 0.3s;
}
.close-button:hover {
background-color: #4b5563;
}
</style>
import { createApp } from 'vue';
import * as Sentry from '@sentry/vue';
import RoomPage from '../components/RoomPage.vue';
// URLから通話室IDを取得
const pathParts = window.location.pathname.split('/');
const roomId = pathParts[pathParts.length - 1] || 'unknown';
// Vueアプリケーションの作成
const app = createApp(RoomPage, { roomId });
// Sentryの初期化(Vueアプリ作成後)
const sentryDsn = import.meta.env.VITE_SENTRY_DSN;
if (sentryDsn) {
try {
Sentry.init({
app: app,
dsn: sentryDsn,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
],
// RoomPageコンポーネントのみトラッキング
trackComponents: ['RoomPage'],
// エラー送信前にtransactionを設定
beforeSend(event) {
// transactionを room/{roomId} に設定
event.transaction = `room/${roomId}`;
return event;
},
tracesSampleRate: 1.0, // 本番環境では0.1などに設定
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
});
} catch (error) {
console.warn('Sentry初期化エラー:', error);
}
} else {
console.warn('VITE_SENTRY_DSNが設定されていません。Sentryは無効です。');
}
// Vueアプリケーションをマウント
app.mount('#app');
コードのポイント
カギは初期化処理にあります。
この初期化処理では以下を行っています。
- 特定のVueコンポーネントのみ監視
- URL から通話室IDを取得
- Vue アプリ起動後に Sentry を初期化
- エラー送信前に transaction を
room/{通話室ID}に設定 - 通話室単位でエラーを追跡できるようにする
初期化処理で使っているオプションは Sentry 公式ドキュメントにも記載されています。
例えばbeforeSendを使ってイベントを書き換える方法は公式ガイドにあります。
また、トランザクション名をカスタマイズする例は公式ガイドにて解説されています。
特定のコンポーネントのみ監視する処理であるtrackComponentsも公式ガイドにて解説されています。
動作検証
ここのURLはroom/shhzfr4qとなっています。
さてエラーを発生させてみましょう。
room_idがshhzfr4qの通話室でエラーが発生したとわかりやすいですね。
次に通話室を複数作ってエラーを発生させSentryを確認しましょう。
ちゃんと通話室IDごとで別Issueになっています。
絞り込みにも適応する処理を入れておくと、通話室IDを使えるため便利です。
まとめ
今回はVue.jsを使ったLaravel Webアプリで、コンポーネント内で発生するJavaScriptエラーをSentryに分かりやすく通知する方法をまとめました。
保守の観点から、単にエラーを通知するだけではなく、通話室単位でIssueを分けることで、どの場所でエラーが発生したのかを把握しやすくする実装を行いました。通話室IDをエラー情報に含めることで、管理や確認がしやすい構成を実現しています。
また、エラー通知基盤としてのSentryの柔軟性と有効性を確認しました。Laravel側の実装で制御する方法もありますが、Sentry側のIssue Alertや条件設定を活用することで、コードを変更せずに高度な通知設計が可能です。
今後Webアプリを開発する際には、AWSと同様にSentryも重要な運用ツールの一つとして位置付け、エラー監視や運用設計に積極的に活用していきます。
お知らせ
技術ブログを週1〜2本更新中、ソーイをフォローして最新記事をチェック!
https://qiita.com/organizations/sewii




