ブログ一覧に戻る
Sentryモノレポエラー監視DevOps

Sentry を 9 アプリのモノレポに入れたら、Wizard だけでは終わらなかった話

はじめに

私たちが運用する SaaS は、Next.js による Web フロント 5 アプリ・Expo の Mobile・Electron の Desktop・NestJS の API・Cloud Run Job の Worker、合計 9 個のアプリ を 1 つのモノレポで管理しています。

エラー監視として Sentry を採用することにし、公式ドキュメントが推奨する Wizard (npx @sentry/wizard@latest -i <platform>) で全部済むと思っていました。

実際は、半分は手動で仕上げる必要があり、想定外の罠も複数遭遇 しました。本記事では、その経験を共有します。

Wizard 対応と非対応の振り分け

Sentry Wizard の -i フラグで指定できる platform を確認すると、対応・非対応がはっきり分かれます。

アプリフレームワークWizardSDK
Web フロント 5 個Next.js-i nextjs@sentry/nextjs
DesktopElectron-i electron@sentry/electron
MobileExpo / React Native-i reactNative@sentry/react-native
APINestJS@sentry/nestjs (手動)
WorkerNode.js (Cloud Run Job)@sentry/node (手動)

NestJS と汎用 Node.js は Wizard 非対応のため、Dashboard で Project を手動作成 → SDK install → init コード追加 → DSN 投入を自前で行うことになります。

罠 1: Wizard 半自動の Electron / React Native

Electron と React Native の Wizard は、Next.js のように「config 全部生成 + 質問に答えるだけ」とはいきません。

Electron の場合

npx @sentry/wizard@latest -i electron

を実行すると以下のメッセージで止まります:

✗ @sentry/electron isn't in your dependencies
  please install it with yarn/npm

事前 install が前提 です。先に npm install @sentry/electron してから再実行する必要があります。

さらに、Wizard は sentry.properties をコメントアウトされた雛形しか生成しません:

defaults.url=https://sentry.io/
#defaults.org=
#defaults.project=
#auth.token=
cli.executable=../../node_modules/@sentry/cli/bin/sentry-cli

org / project / auth.token を手動で書く必要があります。さらに main process と renderer process それぞれに Sentry.init コードを書く必要があり、自動生成はされません。

React Native の場合

npx expo install @sentry/react-native
npx @sentry/wizard@latest -i reactNative

Wizard が app.json への plugin 追加スニペットを表示しますが、私たちは app.config.ts (TypeScript dynamic config) を使っていたため、スニペットを TypeScript シンタックスに変換し、既存の plugins 配列にマージする手作業が必要でした。

plugins: [
  // ... 既存
  [
    "@sentry/react-native/expo",
    { url: "https://sentry.io/", project: "...", organization: "..." },
  ],
],

罠 2: Wizard が CommonJS で書く → ES module 化が必要

Next.js Wizard が完了後、dev サーバを起動するとビルドエラー:

ReferenceError: require is not defined in ES module scope
This file is being treated as an ES module because it has a '.js' file extension and
'.../package.json' contains "type": "module".

Wizard が next.config.js の末尾に追加するコードが CommonJS 形式 だったためです:

// Wizard が追加 (CommonJS)
const { withSentryConfig } = require("@sentry/nextjs");
module.exports = withSentryConfig(module.exports, {
  /* ... */
});

私たちの Next.js アプリは "type": "module" で ES module を採用していたため、5 アプリすべてを以下のように書き換えました:

// 冒頭
import { withSentryConfig } from "@sentry/nextjs";

// 末尾
export default withSentryConfig(nextConfig, {
  org: "...",
  project: "...",
  silent: !process.env.CI,
  tunnelRoute: "/monitoring",
});

罠 3: Docker volume の anonymous volume

私たちの Docker Compose は anonymous volume を使って image の node_modules を保持しています:

volumes:
  - ./services/turborepo:/app
  - /app/node_modules # anonymous volume

これにより、ホスト側で npm install @sentry/nestjs --workspace=apps/api を実行しても、コンテナ内の /app/node_modules には反映されません。

Cannot find module '@sentry/nestjs' or its corresponding type declarations.

docker compose exec api npm install も node-gyp 系の native module rebuild で失敗します。最終的に image rebuild が必要でした(以下は共通イメージを使うワーカー系サービスを worker で代表させた例です):

docker compose build worker  # 共通 turborepo image を rebuild
docker compose stop api worker
docker compose rm -f api worker  # anonymous volume を削除
docker compose up -d api worker  # 新 image で起動

罠 4: アプリごとに異なる env vars 規約

Sentry DSN を投入する env vars 名が、フレームワークごとに 4 種類に分かれます:

アプリenv var prefix
Next.jsNEXT_PUBLIC_* (build 時置換)NEXT_PUBLIC_SENTRY_DSN
Electron + Viteimport.meta.env.MAIN_VITE_*MAIN_VITE_SENTRY_DSN
ExpoEXPO_PUBLIC_* (Metro 置換)EXPO_PUBLIC_SENTRY_DSN
NestJS / Node.jsprocess.env.* (runtime 評価)SENTRY_DSN

特に Electron + Vite は process.env.SENTRY_DSN ではなく import.meta.env.MAIN_VITE_SENTRY_DSN を読む必要があり、最初に process.env で書いたら値が undefined になり Sentry が起動しないという、見落としやすい罠でした。

apps/desktop/src/main/env.d.ts への型定義追加も忘れずに:

interface ImportMetaEnv {
  readonly MAIN_VITE_SENTRY_DSN?: string;
}

罠 5: Session Replay の PII 露出

Wizard デフォルトの Session Replay 設定は 入力フォームの値はマスクされるが、表示済テキストはマスクされない ため、給与額・取引金額・氏名等の機密情報がそのまま動画記録されるリスクがあります。

業務 SaaS では致命的なため、すべてマスクするように厳格化:

Sentry.replayIntegration({
  maskAllText: true,
  maskAllInputs: true,
  blockAllMedia: true,
}),

これで UI 構造のみ録画され、PII 漏洩リスクはゼロになります。デバッグ価値は下がりますが、業務 SaaS では妥当なトレードオフです。

設計判断: env-tag 方式 vs Project 分離方式

DEV / STG / PRD の環境を Sentry でどう分けるか、2 つの選択肢がありました:

観点env-tag 方式 (採用)env 別 Project 方式 (不採用)
Project 数927 (9 × 3 env)
Issue 比較同 project 内で「DEV で fix → PRD で再発」が即わかるenv 跨ぎ比較不可
Release trackingenv 横断で release 進捗追跡可env 別に分断
Source map uploadproject 単位で 1 回env 別に重複
Spike Protectionenv 別ではない (デメリット)env 別に分離可

私たちの規模 (β 期間) ではメリットが大きく、env-tag 方式 (1 project = 1 DSN、env 切替は SENTRY_ENVIRONMENT tag のみ) を採用しました。dev での event が過剰に発生するリスクは、Inbound filter で environment=development を破棄することで対応しています。

設計判断: Logs 機能を採用しない (ログ責務分離)

Sentry の Logs 機能 (2025 GA) は Wizard で Yes (recommended) がデフォルトですが、No を選択 しました。

私たちはログを 4 系統に責務分離しています:

ログ種別保管先
インフラ層 (Cloud Run stdout / SQL slow query / GCS access)Cloud Logging
アプリ層エラー (exception)Sentry
エラー周辺コンテキストログSentry breadcrumbs (default で console.log 自動 capture)
顧客側監査ログ (audit_log)BigQuery (CDC 経由)

Sentry breadcrumbs で error 直前 100 件の console.log / fetch / click が自動 capture されるため、Logs 機能を別途有効化しなくてもデバッグに必要なコンテキストは取得済。二重保管 + 運用複雑性を回避できます。

まとめ

項目結論
9 アプリ統合の所要時間約 4 時間 (Wizard 自動部分は 5 分 × 7 アプリ、手動仕上げと罠対応で 3 時間半)
Wizard だけで完結しない (NestJS / Node.js は完全手動、Electron / RN は半自動)
想定外の罠5 つ (CommonJS / docker volume / env vars 規約 / PII / 半自動 Wizard 等)
動作確認全 9 project で test event 着弾確認 (eventId 発行 + flush success)

公式ドキュメントの Wizard 推奨は決して間違っていませんが、モノレポ × 多フレームワーク × Docker × 厳格 PII という条件が重なると、半分は手動仕上げになります。本記事が同じ構成のチームの参考になれば幸いです。