Vendure 監視・運用設計書¶
概要¶
Fly.io + 専用 Redis アプリで運用される Vendure システムの監視・運用戦略を説明します。
[!IMPORTANT] 継続監視の正式導線は Sentry Uptime / Uptime Kuma / GitHub Actions fallback とする。
observability/配下の Grafana / Prometheus / Loki / log shipper は、現時点では Fly.io の正式運用 app ではなく、ローカル確認または将来導入用テンプレートとして扱う。
現在の verification / monitoring lanes¶
[!TIP]
requestId/traceId/spanId/workflowTraceIdの役割分離と、Playwright manifest / workflow archive / Sentry diagnostics の相関方法は Observability 相関ガイド を正本とする。
subtle-error detection layers(現在の正規導線)¶
| Layer | Primary automation | Manual entrypoint | 役割 |
|---|---|---|---|
| browser synthetic | scheduled-dashboard-smoke.yml / dashboard post-deploy smoke |
just dashboard-smoke-postdeploy production / just dashboard-smoke-postdeploy staging |
React Dashboard の login / runtime / error boundary の false green を、/products /product-variants /customers /orders の list に加え /products/:id /customers/:id /orders/:id detail 画面まで含めて継続検知する。 |
| dashboard admin API smoke | _deploy-dashboard-workers.yml / _deploy-vendure-fly.yml / scheduled-dashboard-smoke.yml |
just dashboard-admin-api-smoke production / just dashboard-admin-api-smoke staging |
React Dashboard が依存する Admin API の auth / list / asset upload を browser synthetic とは別レーンで fail-fast する。products 系に加えて customers / orders も確認する。 |
| readiness contract | check-ready-schema-drift.sh / verify-vendure-deploy / Sentry Uptime / Uptime Kuma |
just monitor-endpoints-check |
Storefront / Vendure readiness・schema drift を deploy 前後と継続監視の両方で止める。 |
| WordPress drift audit | wordpress-drift-audit.yml |
just wp-drift-audit / just wp-drift-audit-vps staging / just wp-drift-audit-vps production |
WordPress CMS の plugin / ACF JSON / option / fixed page 構成 drift を read-only で検知し、DB テーブル差分と混同せずに切り分ける。 |
| Sentry browser/runtime alerts | observability/sentry-workflow-alerts.json + scripts/ops/sentry-workflow-alerts.mjs |
just sentry-alerts-list / just sentry-alerts-upsert-dry-run / just sentry-alerts-upsert |
browser/runtime の first-seen・regression・dependency spike を notification layer として拾う。 |
| monitor / alert drift audit | scripts/ops/sentry-live-config-audit.mjs |
just sentry-live-config-audit -- --allow-drift --json |
checked-in の Sentry workflow / uptime monitor 設定と live state の drift を即座に見つける。 |
| deploy gate | _deploy-dashboard-workers.yml / _deploy-vendure-fly.yml / deploy-prod-preview.yml / prod-preview-smoke-storefront.yml |
上記 manual entrypoint の再実行 + workflow rerun | release 前後に「緑だが壊れている」状態を残さない。prod-preview は未リリース Storefront / Dashboard を production data plane へ接続する限定公開 gate として扱う。追加の Vendure Fly app / machine / DB は作らず、Cloudflare Access 配下で短時間だけ確認する。 |
| business-canary / KPI proxy lane | staging-smoke-storefront.yml / prod-preview-smoke-storefront.yml / production-smoke-storefront.yml |
just storefront-business-canary staging / just storefront-business-canary prod-preview / just storefront-business-canary production / just storefront-shadow-probe production |
login / browse / cart の主要導線を KPI proxy として継続確認する。staging / prod-preview / production ともに shadow-readonly probe を先に流し、遅延や data anomaly も同時に拾う。staging のみ代引 / 振込 / 再注文の注文成立まで担保する。prod-preview は production-smoke channel/principal を使い、default channel に注文を作らない。business-canary 失敗時は deploy/Access 問題と production-smoke fixture 運用問題を切り分ける。 |
- Continuous-safe (Sentry):
observability/external-monitor-endpoints.json+scripts/ops/sentry-uptime-monitors.mjs - Trigger: 5 分ごと
- Isolation: Sentry Uptime
- 役割: production の Storefront readiness / Vendure readiness / Vendure schema drift を継続監視
- Continuous-safe (Uptime Kuma):
observability/external-monitor-endpoints.json+scripts/ops/uptime-kuma-monitors.mjs - Trigger: 5 分ごと
- Isolation: Uptime Kuma
- 役割: production の Storefront readiness / Vendure readiness / Vendure schema drift / WordPress login を独立監視
- Fallback safe probes:
scheduled-safe-probes.yml - Trigger: 10 分ごと / 手動
- Isolation: safe probe
- 役割: staging / production の version / React Dashboard login / WordPress を含む fallback 確認。Vendure ready は
schemaDrift == "ok"まで含めて判定する - WordPress drift audit:
wordpress-drift-audit.yml - Trigger:
apps/wordpress-cms/**またはjust/wordpress.justの PR / 毎週定期実行 / 手動 - Isolation: local docker compose + read-only CMS audit
- 役割: WordPress bootstrap 後の CMS 構成が、checked-in の plugin / ACF JSON / option 前提と一致するかを CI で固定監査する
- 手動再現:
just wp-drift-audit。live の切り分けはjust wp-drift-audit-vps staging/production - WordPress live drift monitor:
wordpress-drift-monitor.yml - Trigger: 毎週定期実行 / 手動
- Isolation: authenticated WordPress REST endpoint (
/wp-json/ritsubi/v1/drift-audit) を read-only 実行 - 役割: staging / production の live WordPress が deploy 済み plugin と options 構成から drift していないかを、local compose とは独立に確認する
- 前提: AWS Secrets Manager 上の
WORDPRESS_ENDPOINTとCMS_API_TOKENが正しく同期されていること - Periodic React Dashboard smoke:
scheduled-dashboard-smoke.yml - Trigger: 6 時間ごと / 手動
- Isolation: browser synthetic
- 役割: React Dashboard の runtime error / error boundary / admin API wiring の false green を継続検知
- 手動再現:
just dashboard-admin-api-smoke production/just dashboard-smoke-postdeploy production(staging も同様) - Admin API smoke script:
apps/vendure-server/scripts/dashboard-api-canary.ts - Trigger: dashboard deploy 後 / Vendure deploy 後 /
scheduled-dashboard-smoke.yml/ 手動 - Isolation: authenticated read-only GraphQL canary
- 役割: React Dashboard が依存する Admin API contract(
products,featuredAsset,productVariants,customers,orders)を browser 描画前に検知する - 判定: login 成功後は session cookie または
vendure-auth-tokenheader のどちらでも継続認証できれば成功とみなす - Business-canary / KPI proxy:
production-smoke-storefront.yml/staging-smoke-storefront.yml - Trigger: deploy 後 / 手動
- Isolation: shadow-readonly + critical flow synthetic
- 役割: staging では shadow-readonly + critical auth + staging checkout、production では shadow-readonly + prod-safe synthetic を使い、login / browse / add-to-cart / checkout 表示までを business impact proxy として確認する
- 注文成立 (staging 限定): deploy gate は外部 SBPS contract に依存しない
checkout.staging-gate.real.spec.tsの銀行振込 checkout で注文完了を確認する。SBPS live checkout は #556 の vendor-side 再確認として手動 opt-in のみ許容し、checkout.sbps.real.spec.tsの staging/local guard により production URL へ向いた実決済は fail-closed で拒否する。 - 手動再現:
just storefront-shadow-probe production/just storefront-business-canary production(staging も同様)。注文成立フローの staging ローカル実行はjust storefront-e2e-real staging "tests/e2e/scenarios/standard-purchase-flow.real.spec.ts tests/e2e/scenarios/bank-transfer-purchase-flow.real.spec.ts tests/e2e/scenarios/reorder-from-history-flow.real.spec.ts tests/e2e/scenarios/gift-code-and-points-purchase-flow.real.spec.ts"。SBPS real checkout はRUN_SBPS_E2E=true just storefront-e2e-real staging "tests/e2e/checkout.sbps.real.spec.ts --project=chromium"を使う。決済方法別の注文成立を smoke 層で一括確認したい場合はjust storefront-payment-purchase-smoke staging(非リダイレクト 3 種+staging secret のRUN_SBPS_E2Eで SBPS クレカ)を使う。 - Pre-release hidden lane:
deploy-prod-preview.yml+prod-preview-smoke-storefront.yml - Trigger:
Staging Smoke Storefront成功後 / 手動 - Isolation: Cloudflare Access + production-smoke channel + shadow-readonly
- 役割: 未リリース Storefront / React Dashboard build を production Vendure / CMS /
assets / Secrets へ接続し、public production traffic へ出す前に確認する。
promote-main.ymlの前段 gate - Post-deploy / periodic:
production-smoke-storefront.yml - Trigger:
Deploy Production成功後 / 6 時間ごと / 手動 - Isolation: synthetic + shadow-readonly
- 役割: production の business-canary / KPI proxy lane を継続確認
- Observability canary:
production-sentry-smoke-storefront.yml - Trigger: 手動
- Isolation: safe probe + Sentry synthetic
- 役割: production observability 導線の個別切り分け
Sentry Web Vitals monitoring¶
Storefront と React Dashboard は、Sentry の browser tracing に加えて Core Web Vitals 相当値を Sentry metrics に送信する。transaction は個別 pageload / navigation の深掘り、Web Vitals metrics はページ表示速度の時系列・閾値監視に使う。
| Surface | Metric name pattern | Attributes | Unit |
|---|---|---|---|
| Storefront | storefront.web_vitals.{ttfb,fcp,lcp,cls,inp} |
surface, page |
millisecond / none |
| React Dashboard | react_dashboard.web_vitals.{ttfb,fcp,lcp,cls,inp} |
surface, page |
millisecond / none |
ttfb: navigation timing のresponseStartfcp:first-contentful-paintlcp: ページ非表示化またはpagehide時点の latest largest contentful paintcls: recent input を伴わない layout shift の累積値inp: interaction entry の最大 duration
初期の目安は次を使う。production の実測が溜まったら p75 / p95 を見て alert threshold を更新する。
lcp: 2500ms 以下を目安にし、同じpageの pageload transaction、画像配信、 backend fetch span、Cloudflare cache 状態を見る。inp: 200ms 以下を目安にし、Sentry replay と長い task / interaction 前後の browser issue を確認する。cls: 0.1 以下を目安にし、対象pageの late-loaded image / banner / header / font の layout shift を確認する。ttfb: 800ms 以下を目安にし、Storefront Worker / Vendure / WordPress の upstream span と Sentry Uptime の直近失敗を確認する。fcp: 1800ms 以下を目安にし、初期 bundle、font、critical CSS、Cloudflare asset delivery を確認する。
Sentry UI では、まず Metrics / Dashboards で surface と page
を絞り込む。regression が見えた場合は同じ時間帯の Performance transaction、
Replay、browser/runtime issue を同じ page と release で確認する。
PerformanceObserver 非対応 browser では metric は送信されない。これは速度監視の補助観測面であり、 Sentry SDK の error / tracing 初期化や通常の browser runtime alert を置き換えない。
[!IMPORTANT] production verification は production-smoke channel + shadow-readonly を正式運用とする。default channel では read-only / cart cleanup までに留め、 注文作成を伴う synthetic は専用
production-smokechannel、no-op payment、 smoke marker を前提にする。periodic な readiness 外形監視は Sentry Uptime + Uptime Kuma を正規運用とし、
.github/workflows/scheduled-safe-probes.ymlは manual fallback とする。
production verification の運用ルール¶
- synthetic principal
- production browser smoke は default channel 上の prod-safe smoke と read-only probe に限定する。
- 資格情報の正本は AWS Secrets Manager
prod/storefront配下のE2E_LOGIN_EMAIL/E2E_LOGIN_PASSWORDとする。 - fallback credential は使用しない。
- synthetic principal は、対象導線で使う商品 / 価格 / visibility
policy に適合していること。特に商品詳細 smoke は「ログインできる」だけでは不十分で、
validateVisibilityを通る顧客である必要がある。 - カート状態は実行前後に cleanup し、shippingMode を含めて初期化する。
- shadow-readonly lane
- read-only shadow probe は
/api/health/live/api/health/ready/api/version/auth/loginに加え、/commerce/shop-apiへの read-only query だけを実行する。 - GraphQL は
search/products/collections/product(slug)の read-only query を確認し、latency budget と minimum data threshold の両方で判定する。 - mutation、注文確定、決済確定、メール送信を伴う確認は production の shadow lane に含めない。
- default channel に対する mutation も production smoke には含めない。
- failure triage
deploy-prod-preview.yml/prod-preview-smoke-storefront.yml/production-smoke-storefront.ymlが失敗したら、まず workflow log archive と Playwright artifact を確認する。- artifact を開くときは、workflow archive / job attachment / Sentry
diagnostics / Playwright manifest を同じ
workflowTraceId/traceIdで辿る。attachment artifact のmanifest.jsonにあるdetectedManifests[]を起点に、どの nested manifest を先に見るべきか判断する。 - shadow probe が先に失敗している場合は、Storefront
/api/versionとVendure/versionの build metadata、ならびに Sentry Uptime monitorProduction Storefront readiness/Production Vendure readiness/Production Vendure schema driftの直近失敗有無を確認する。必要ならscheduled-safe-probes.ymlを manual fallback として再実行する。 - synthetic smoke のみ失敗している場合は、synthetic 顧客の cart 状態、login 可否、対象商品の可視性変更有無を確認する。
- WordPress リッチ説明だけが欠ける場合は、まず direct WP GraphQL で
wordPressProductDetailとproductDetailMeta.styleModeを確認する。styleModeが direct WP に存在しない場合は production WordPress が古い schema のままなので、just update-wp-productionを優先する。 - direct WP では新しい記事が見えるのに Vendure
shop-apiで古いnull/ default が返る場合は、Vendure の WordPress client cache(最大 5 分)を疑う。revalidate/cmsは Storefront cache だけを消す点に注意する。 - observability だけが失敗している場合は
production-sentry-smoke-storefront.ymlと Sentry 側の Feedback / release 状態を確認する。
dashboardApiCanary 調査の実運用メモ¶
/health/ready の dashboardApiCanary は、2026-04 以降は startup 一回だけの値ではなく
定期 refresh されます。したがって triage では次を見ます。
checkedAt- 古い: 古い runtime が残っているか、deploy が切り替わっていない
- 新しい: 現在の runtime で本当に fail している
errorlogin succeeded but no session cookie was returned- localhost probe では cookie 非返却でも
vendure-auth-tokenheader が返る場合がある - manual
just dashboard-admin-api-smoke productionが pass しているなら、 localhost canary の偽陰性を疑う
- localhost probe では cookie 非返却でも
- manual canary
just dashboard-admin-api-smoke production
curl -sS https://commerce.ritsubi-platform.com/health/ready | jq '{status, dashboardApiCanary, schemaDrift}'
- deploy 後チェック
dashboardApiCanary=okschemaDrift=ok- app machine checks passing
Fly release bookkeeping が監視を汚すとき¶
本番では live machine が healthy でも、Fly の release history だけが失敗扱いで残ることがあります。監視の正本は release history 単独ではなく 次の組み合わせです。
flyctl machine list -a ritsubi-ecommerceflyctl checks list -a ritsubi-ecommerce --jsoncurl -sS https://commerce.ritsubi-platform.com/health/ready | jq
ghost machine が残っている場合は、release history を見続ける前にまず machine inventory を整理してください。
監視アーキテクチャ¶
監視対象とメトリクス¶
graph TB
subgraph "Application Layer"
V[Vendure API]
NS["Storefront (Vite)"]
W[Worker Process]
WP[WordPress CMS]
end
subgraph "Infrastructure Layer"
PG[(PostgreSQL)]
R[(Redis)]
FS[File Storage]
end
subgraph "Current Monitoring"
SU[Sentry Uptime]
UK[Uptime Kuma]
GH[GitHub Actions Safe Probes]
SN[Sentry Issues / Logs / Replay]
STD[Platform stdout logs]
end
V --> PG
V --> R
V --> FS
NS --> V
W --> V
V --> SN
NS --> SN
W --> SN
V --> STD
NS --> STD
W --> STD
SU --> V
SU --> NS
UK --> V
UK --> NS
UK --> WP
GH --> V
GH --> NS
GH --> WP
1. アプリケーション監視¶
1.1 パフォーマンスメトリクス¶
レスポンス時間監視¶
// performance-metrics.ts
import { performance } from "perf_hooks";
import { Logger } from "@vendure/core";
export class PerformanceMonitor {
private logger = new Logger(PerformanceMonitor.name);
trackGraphQLQuery(operationName: string, duration: number) {
this.logger.verbose(`GraphQL ${operationName}: ${duration}ms`);
// Prometheus メトリクス
graphqlDurationHistogram.labels({ operation: operationName }).observe(duration / 1000);
// SLA閾値チェック
if (duration > 2000) {
this.logger.warn(`Slow query detected: ${operationName} took ${duration}ms`);
}
}
trackAPIEndpoint(endpoint: string, method: string, statusCode: number, duration: number) {
apiDurationHistogram
.labels({ endpoint, method, status: statusCode.toString() })
.observe(duration / 1000);
apiRequestsTotal.labels({ endpoint, method, status: statusCode.toString() }).inc();
}
}
// Prometheus メトリクス定義
import { register, Histogram, Counter } from "prom-client";
export const graphqlDurationHistogram = new Histogram({
name: "vendure_graphql_duration_seconds",
help: "GraphQL query duration",
labelNames: ["operation"],
buckets: [0.1, 0.5, 1, 2, 5, 10],
});
export const apiDurationHistogram = new Histogram({
name: "vendure_api_duration_seconds",
help: "API endpoint duration",
labelNames: ["endpoint", "method", "status"],
buckets: [0.1, 0.5, 1, 2, 5, 10],
});
export const apiRequestsTotal = new Counter({
name: "vendure_api_requests_total",
help: "Total API requests",
labelNames: ["endpoint", "method", "status"],
});
register.registerMetric(graphqlDurationHistogram);
register.registerMetric(apiDurationHistogram);
register.registerMetric(apiRequestsTotal);
ビジネスメトリクス¶
// business-metrics.ts
export class BusinessMetrics {
private logger = new Logger(BusinessMetrics.name);
// 注文関連メトリクス
trackOrder(order: Order) {
orderTotal.labels({ status: order.state }).observe(order.total);
ordersCreatedTotal
.labels({
customerType: this.getCustomerType(order.customer),
channel: order.channels[0]?.code || "default",
})
.inc();
}
// 商品関連メトリクス
trackProductView(productId: string, customerId?: string) {
productViewsTotal.labels({ productId }).inc();
if (customerId) {
customerActivityTotal.labels({ customerId, action: "product_view" }).inc();
}
}
// B2B特有メトリクス
trackRebateCalculation(customerId: string, amount: number, period: string) {
rebateAmountGauge.labels({ customerId, period }).set(amount);
rebateCalculationsTotal.labels({ period }).inc();
}
// キャンペーン効果測定
trackCampaignUsage(campaignId: string, discount: number) {
campaignUsageTotal.labels({ campaignId }).inc();
campaignDiscountTotal.labels({ campaignId }).observe(discount);
}
private getCustomerType(customer: Customer): string {
// B2B顧客タイプの判定ロジック
const customFields = customer.customFields as any;
return customFields?.customerStatus || "general";
}
}
// ビジネスメトリクス定義
export const orderTotal = new Histogram({
name: "vendure_order_total_amount",
help: "Order total amount",
labelNames: ["status"],
buckets: [1000, 5000, 10000, 50000, 100000, 500000],
});
export const ordersCreatedTotal = new Counter({
name: "vendure_orders_created_total",
help: "Total orders created",
labelNames: ["customerType", "channel"],
});
export const productViewsTotal = new Counter({
name: "vendure_product_views_total",
help: "Total product views",
labelNames: ["productId"],
});
export const rebateAmountGauge = new Gauge({
name: "vendure_rebate_amount",
help: "Customer rebate amount",
labelNames: ["customerId", "period"],
});
1.2 エラー監視¶
Sentry 統合¶
現在の対象 surface と project 対応¶
| Surface | 主な実行面 | Project slug の解決順 | release 名 | 現在の状態 |
|---|---|---|---|---|
| Storefront | Vite browser bundle + Worker API on Cloudflare Workers | SENTRY_PROJECT_STOREFRONT → SENTRY_PROJECT → 既定値 b2b-commerce-storefront |
storefront@<git-sha> |
release 作成、commit association、deploy marker、sourcemap upload、runtime enrichment、browser SDK 初期化、Session Replay、主要 API / client context の failure capture、warn/error の Sentry Logs、browser failed request capture が有効。Sentry Feedback widget は関係者向け導線として ?sentry-feedback=1 指定時のみ表示する。Worker runtime traces / logs は Cloudflare Observability OTLP export を正本とし、Sentry へ配送する。 |
| Vendure | NestJS / GraphQL API on Fly.io | SENTRY_PROJECT_VENDURE → SENTRY_PROJECT |
vendure@<git-sha> |
release 作成、commit association、deploy marker、sourcemap upload、runtime enrichment、Node Profiling、SMILE 定期 task と worker heartbeat monitor が有効。staging / production ともに CI build artifact から sourcemap を upload し、Fly runtime の --enable-source-maps は fallback として維持する |
| React Dashboard | browser bundle on Cloudflare Workers | SENTRY_PROJECT_DASHBOARD → SENTRY_PROJECT |
dashboard@<git-sha> |
release 作成、commit association、deploy marker、sourcemap upload、browser SDK 初期化、Session Replay、warn/error の Sentry Logs と browser failed request capture が有効。Sentry Feedback widget は搭載しない(2026-05 削除)。admin API request は x-request-id / request_id と actor_id / actor_type / dashboard_admin context で Vendure backend event と相関できる |
| WordPress Plugin | PHP runtime / browser in custom plugin on VPS (Webarena Indigo) | WORDPRESS_PLUGIN_SENTRY_RELEASE → SENTRY_RELEASE(release), browser DSN: WORDPRESS_BROWSER_SENTRY_DSN → SENTRY_DSN |
ritsubi-ec-plugin@<git-sha> |
自作 plugin を独立 surface として扱い、plugin header version を tag/context の正本にする。PHP runtime の fatal error / uncaught exception と browser の JS error は plugin metadata を付けて capture し、release 未注入時は ritsubi-ec-plugin@<plugin-version> を既定値にする。just wp-deploy-vps は deploy ごとに plugin release/environment を自動注入し、必要な Sentry credentials があれば deploy marker を作成する。package workflow も同じ release contract で zip artifact を生成する。 |
[!IMPORTANT] この runbook は「現在の実装」を正本とする。Vendure は staging / production ともに release / commit association / deploy marker に加えて CI から sourcemap upload を行う。Fly runtime の
--enable-source-mapsは fallback として残すが、triage の正本は upload 済み artifact とする。現時点の Sentry フル活用は release / sourcemap / diagnostics / feedback / Storefront / React Dashboard Session Replay / Logs / browser failed request capture / workflow alerts / uptime monitors / recurring task crons / Vendure profiling / worker heartbeat monitors / Storefront Worker traces+logs の Cloudflare Observability OTLP export を正式運用とし、AI monitoring は対象外 とする。
必須 env / secrets¶
Runtime 共通(Storefront server/edge・Vendure・WordPress PHP)¶
SENTRY_DSN: DSN。未設定ならその runtime の Sentry 送信は無効。SENTRY_ENVIRONMENT: 環境名。未設定時はNODE_ENVを利用。SENTRY_RELEASE: runtime が付与する release 名。CI deploy で surface ごとに注入する。SENTRY_TRACES_SAMPLE_RATE: tracing のサンプリング率。SENTRY_SEND_DEFAULT_PII:trueの場合のみ標準 PII を送信。
WordPress 追加メモ¶
- WordPress は SDK 依存ではなく、
apps/wordpress-cms/plugins/ritsubi-ec-pluginから DSN endpoint へ直接 event を送る。 - browser runtime は
browser.sentry-cdn.comの bundle を読み込み、plugin 側フックから WordPress login / admin で JS error を送る。 - 現在の公開面の大半は Storefront へ redirect されるため、WordPress browser surface は実運用上 login / admin が中心になる。
- browser 専用 env (
WORDPRESS_BROWSER_SENTRY_*) が無い場合は backend と同じSENTRY_*を流用する。 SENTRY_TRACES_SAMPLE_RATEは WordPress では現時点未使用。
Storefront browser 追加¶
VITE_PUBLIC_SENTRY_DSNVITE_PUBLIC_SENTRY_ENVIRONMENTVITE_PUBLIC_SENTRY_RELEASEVITE_PUBLIC_SENTRY_TRACES_SAMPLE_RATEVITE_PUBLIC_SENTRY_SEND_DEFAULT_PIIVITE_PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATEVITE_PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATESENTRY_DEBUG_TOKEN(任意): staging / development の疎通確認用。/api/debug-sentryと/api/client-logsの debug trigger で使用し、production では受け付けない。/api/client-logsは non-production でも same-origin request を必須とする。SENTRY_DEBUG_SIGNING_SECRET(staging canary 用):staging-sentry-smoke-storefront.ymlが run marker 束縛の request token を導出するための専用 secret。SENTRY_DEBUG_TOKENやCMS_REVALIDATE_SECRETのような別責務の値を流用しない。
React Dashboard build-time 追加¶
VITE_ADMIN_API_URLVITE_SENTRY_DSNVITE_SENTRY_ENVIRONMENTVITE_SENTRY_RELEASEVITE_SENTRY_TRACES_SAMPLE_RATEVITE_SENTRY_SEND_DEFAULT_PII- Replay sample rate はコード定数(production: session 10%、non-production: session 100%、on-error 100%)を使い、Secrets Manager では管理しない。
WordPress browser 追加¶
WORDPRESS_BROWSER_SENTRY_DSNWORDPRESS_BROWSER_SENTRY_ENVIRONMENTWORDPRESS_BROWSER_SENTRY_RELEASEWORDPRESS_BROWSER_SENTRY_SEND_DEFAULT_PII
[!NOTE] Sentry Feedback widget は Storefront のみで利用する(
?sentry-feedback=1指定時のみ表示)。既存の browser DSN を再利用するため、Userback token のような追加 secret は不要。React Dashboard では feedback widget を搭載していない。
CI / release automation¶
SENTRY_AUTH_TOKEN: AWS Secrets Managerb2b-ecommerce/ci/sharedから取得する。release/sourcemap に加え、workflow alert upsert と uptime monitor upsert でも利用するため、対象 project の monitor 作成 / 更新権限を持つ token を使う。- local の
just sentry-uptime-list/just sentry-uptime-upsert/just sentry-alerts-list/just sentry-alerts-upsert/just sentry-issuesは、既定で login 済みのsentryuser auth を使う。 just sentry-uptime-list-cliは raw なsentry api organizations/ritsubi/uptime/を直接叩く確認用 escape hatch とする。SENTRY_AUTH_TOKENを明示 inject した場合のみ、repo script は CI と同じ token transport を使う。したがって local で token は不要、CI / release automation では引き続き必要、という役割分担にする。SENTRY_ORG: 未設定時は root.sentryclircのritsubiを利用。SENTRY_PROJECT_STOREFRONTSENTRY_PROJECT_VENDURESENTRY_PROJECT_DASHBOARDSENTRY_REPOSITORY(任意): commit association 用 repository slug の上書き。SENTRY_URL(任意): self-hosted Sentry 利用時のみ。observability/sentry-workflow-alerts.json: checked-in の workflow alert 定義。既定の通知先は email issue owners(fallback:Active Members)。observability/sentry-live-config-audit.json: Sentry live config audit の suppressions。現在は issue#533に紐づく uptime drift をここで管理する。observability/external-monitor-endpoints.json: checked-in の外形監視 endpoint catalog。Sentry / Uptime Kuma の共通正本。scripts/ops/check-external-monitor-endpoints.mjs: shared endpoint catalog を使った手動確認 CLI。just monitor-endpoints-checkから一発確認できる。scripts/ops/sentry-uptime-monitors.mjs/scripts/ops/uptime-kuma-monitors.mjs: shared endpoint catalog から各 tool 向け monitor 定義を実行時に構築し、そのまま list / dry-run / upsert を行う。
release / sourcemap / commit association の流れ¶
- 各 deploy workflow が
scripts/ops/sentry-release.mjs prepareを実行し、surface ごとの release を作成する。 - 同時に
--shaと--previous-shaで commit association を登録する。 - build 後、sourcemap 対応済み surface では debug-id 注入 → upload →
.map除去まで CI で完結させる。Vendure は production のみapps/vendure-server/distを CI 上で build して upload し、staging は build 時に生成した TypeScript source map を Fly runtime の--enable-source-mapsで解決する。 - deploy 成功後に
scripts/ops/sentry-release.mjs finalizeを実行し、release URL を更新して released 状態にする。
マニュアルデプロイ後の Sentry エラー確認手順¶
CI を経由しないマニュアルデプロイ(緊急デプロイ・手動 rollback 等)では、Sentry release marker が自動作成されないため、以下の手順を踏む。
Step 1 — release marker を作成する¶
just sentry-mark-manual-deploy <surface> <sha> <env>
# 例: just sentry-mark-manual-deploy storefront abc1234 production
# 例: just sentry-mark-manual-deploy vendure abc1234 staging
# surface: storefront | vendure | dashboard
これは内部で sentry-release.mjs prepare → finalize → deploy を順に実行し、
Sentry 上に <surface>@<sha> という release とデプロイ履歴を登録する。
Step 2 — デプロイ起因エラーを確認する¶
just sentry-issues-release <surface> <sha>
# 例: just sentry-issues-release storefront abc1234
# JSON 出力: just sentry-issues-release storefront abc1234 json
release:<surface>@<sha> クエリで unresolved issues を絞り込む。
CI deploy の場合(marker 自動作成済み)¶
CI deploy は prepare-sentry-release / finalize-sentry-release /
create-sentry-deploy action が marker を自動作成するため、Step 1 は不要。
デプロイ後は Step 2 のみでよい。
時刻ベースの代替確認方法(release marker が存在しない場合)¶
SENTRY_ISSUES_QUERY="is:unresolved firstSeen:>2026-05-09T10:00:00Z" \
SENTRY_ISSUES_PROJECT=storefront \
just sentry-issues
sentry issue list を直接使う場合、時間範囲の flag は Sentry API の
statsPeriod ではなく CLI の --period を使う(例:
sentry issue list ritsubi/b2b-commerce-storefront --query 'is:unresolved' --period 14d)。
workflow alert automation(現在の正式運用)¶
- script:
scripts/ops/sentry-workflow-alerts.mjs - checked-in config:
observability/sentry-workflow-alerts.json - just recipes:
just sentry-alerts-list,just sentry-alerts-upsert-dry-run,just sentry-alerts-upsert - default org / base URL: root
.sentryclirc(ritsubi,https://sentry.io/) - notification target: email issue owners + Active Members fallback
ローカル / CI での使い方¶
- token なしで config を確認する:
just sentry-alerts-upsert-dry-run
- 現在の workflow 一覧を確認する:
just sentry-alerts-list
- checked-in config を org へ反映する:
just sentry-alerts-upsert
local では login 済みの sentry user auth、CI では alerts:write
(または org:write / org:admin)を持つ SENTRY_AUTH_TOKEN
を使う。
--json などの追加 option は just sentry-alerts-list --json
のようにそのまま渡す。
checked-in workflow の意味¶
Production high-priority issue notifications- production で
first_seen/regression/reappearedした issue のうち、priority が High 以上かつ level が error 以上のものを email で通知する。 - 通知先は
issue_owners、owner が無い場合はActive Membersへ fallthrough する。 - 目的: surface 横断の重大障害を SaaS UI 依存なしに再現可能な設定で維持する。
Production storefront vendure dependency failure spike- production の Storefront failure のうち
dependency.name=vendure、area=vendure-fetch、flow=graphql、level >= errorが揃い、1 時間で 20 件以上に達したものを通知する。 - 目的: checkout / login / customer-context などの上流依存障害を、Sentry 上の repo 既存 vocabulary で早期検知する。
Production storefront /products browse issue notifications- production の
/productsbrowse でfirst_seen/regression/reappearedした issue のうち、storefront.products.route=/productsかつlevel >= errorのものを email で通知する。 - 通知先は
issue_owners、owner が無い場合はActive Membersへ fallthrough する。 - 目的:
/productsbrowse 導線で新規障害や再発が出たときに、Slack integration へ依存せずに即時検知する。 Production storefront browser error boundary issue notifications- production の Storefront browser issue のうち、
area=storefront-error-boundary、storefront.runtime=client、level >= errorが揃うものを email で通知する。 - 通知先は
issue_owners、owner が無い場合はActive Membersへ fallthrough する。 - 目的: health / readiness では見えない client-side runtime 崩れを first_seen / regression / reappeared の時点で拾う。
Production react dashboard browser issue notifications- production の React Dashboard browser issue のうち、
service=react-dashboard、surface=react-dashboard、level >= errorが揃うものを email で通知する。 - 通知先は
issue_owners、owner が無い場合はActive Membersへ fallthrough する。 - 目的: React Dashboard の browser runtime error を Vendure backend issue と切り分けて早期検知する。
運用上の注意¶
upsertは workflow 名で既存 rule を照合し、同名があれば update、無ければ create する。just sentry-alerts-list/just sentry-alerts-upsert-dry-run/just sentry-alerts-upsertは local では login 済みsentryuser auth、CI ではSENTRY_AUTH_TOKENを使う単一入口とする。- workflow description には初動で叩く
justrecipe を埋め込み、Sentry UI だけ開いても operator が次の切り分けコマンドへ飛べるようにする。 - 同名 workflow が複数ある場合は fail する。UI 側で重複を整理してから再実行する。
- 現在の
ritsubiorg では local user auth から Slack integration が見えていないため、repo-managed workflow の既定通知は email fallback とする。Slack を再導入する場合のみ config を差し替える。 - AI monitoring は repo 内に対象 SDK がないため設定しない。OTel exporter は collector / routing 設計の合意が出るまで設定しない。
dashboard admin API canary(read-only contract lane)¶
- script:
apps/vendure-server/scripts/dashboard-api-canary.ts - shared logic:
apps/vendure-server/src/observability/dashboard-api-canary.ts - Nx target:
pnpm exec nx run ritsubi-vendure-server:canary:dashboard-api - manual just recipe:
just dashboard-admin-api-smoke - wired into:
scheduled-dashboard-smoke.yml,.github/workflows/_deploy-dashboard-workers.yml,.github/workflows/_deploy-vendure-fly.yml
ローカル / CI での使い方¶
- production の canary を手元で流す:
just dashboard-admin-api-smoke production
- staging の canary を手元で流す:
just dashboard-admin-api-smoke staging
canary の意味¶
products-basicproductslist のid/nameを read-only で確認する。products-featured-assetproductslist のfeaturedAsset { id preview }を確認する。asset_translationなど schema / relation drift に最も敏感な面として扱う。product-variants-basicproductVariantslist のid/sku/nameを確認する。customers-basiccustomerslist のid/emailAddressを確認する。orders-basicorderslist のid/code/stateを確認する。
failure 時の初動¶
- canary script 自体が failure 時に次の導線を stderr へ出す:
just dashboard-admin-api-smoke <env>just dashboard-smoke-postdeploy <env>just vendure-diagnose-dashboard-products <env>docs/03-implementation/infrastructure/schema-drift-runbook.md
canary 403 "missing-origin" / "disallowed-origin"(よくある障害)¶
症状: Sentry に Scheduled canary failed: 403 / Startup canary failed: 403 が記録される。
エラーメッセージ例: "error":"missing-origin" または "error":"disallowed-origin".
根本原因と仕組み:
canary は http://127.0.0.1:{port}/admin-api にローカルアクセスする(Cloudflare を経由しない)。
origin-validator.middleware.ts は POST 系リクエストに Origin ヘッダーを要求するが、
dashboardBaseUrl = env.ADMIN_URL が未設定の場合:
buildDashboardOrigin(undefined)→undefined- canary が Origin ヘッダーなしでリクエスト送信
- middleware: allowedOrigins 非空 + Origin なし → 403 "missing-origin"
production allowlist = [CORS_ORIGIN, STOREFRONT_URL, ADMIN_URL]
↑ ADMIN_URL が未設定だと allowlist は CORS_ORIGIN + STOREFRONT_URL のみになるが
canary が送るべき Origin も undefined になるため 403 になる
診断コマンド:
# production の ADMIN_URL が設定されているか確認
flyctl secrets list --app ritsubi-vendure-server | grep ADMIN_URL
# staging も同様
flyctl secrets list --app ritsubi-vendure-server-staging | grep ADMIN_URL
修正: just sync-fly-secrets で deploy-targets.sh の SSOT から ADMIN_URL を再注入する (推奨)。
手動で個別設定する場合は wrapper を使う:
# 推奨: SSOT から自動注入
just sync-fly-secrets production
just sync-fly-secrets staging
# 手動 (非推奨): wrapper 経由で --stage import
bash scripts/ops/fly-secrets-set.sh -a ritsubi-ecommerce ADMIN_URL="https://dashboard.ritsubi-platform.com" --deploy
bash scripts/ops/fly-secrets-set.sh -a ritsubi-ecommerce-staging ADMIN_URL="https://dashboard-staging.ritsubi-platform.com" --deploy
設定後は canary が正常動作するかを just dashboard-admin-api-smoke <env> で確認すること。
注:
ADMIN_URLは React Dashboard の Cloudflare Workers URL を指す。commerce.ritsubi-platform.comは Fly.io への DNS proxy(Worker ではない)なので、Dashboard URL は Cloudflare Workers のカスタムドメインを確認すること。[!TIP] React Dashboard の browser smoke は runtime error / error boundary を拾うのに強く、dashboard admin API canary は contract / schema drift に強い。両方を組み合わせて false green を減らす。
storefront business-canary / KPI proxy lane¶
- manual just recipes:
just storefront-shadow-probe,just storefront-business-canary - production は
shadow probe + @smoke:prod-safe、staging はshadow probe + @smoke:criticalを正規 lane とする。 just storefront-business-canary productionは read-only probe の後に authenticated Playwright smoke を流す。E2E_LOGIN_*など storefront smoke credential が AWS Secrets Manager 正本に無い場合は authenticated lane を fail させ、 shadow lane だけで green 扱いしない。default channel の business-canary は cart cleanup までに留める。just storefront-login-smoke-production-domainsは production のログイン専用 smoke。scripts/ops/deploy-targets.shに定義された canonical domain と public alias の両方で、実ブラウザのパスワードログインフォームが/api/auth-loginへ POST し、303 -> /account、session/vendure-auth-tokencookie、/api/auth-sessionauthenticated まで成立することを 確認する。Production Storefront deploy 後にログインや/commerce/shop-apiのドメイン差分を疑う場合は、storefront-business-canary production単体ではなく この recipe を先に実行する。just storefront-shadow-probe <env>は health/live + readiness + version + login に加え、Shop API の readonlysearch/products/collections/product(slug)query を確認する。GraphQL/HTTP latency budget と minimum data threshold を超えた場合も fail する。just storefront-business-canary stagingは現行のstaging-smoke-storefront.ymlと同じく shadow lane の後に critical flow smoke を replay する。just storefront-invoice-smoke stagingは、認証済み Storefront からritsOrderInvoiceを実 Shop API で呼び、status=READY、署名 URL、PDF signature (%PDF-) まで確認する。既定ではE2E_LOGIN_*の顧客の直近注文を使い、 明示したい場合はSTOREFRONT_SMOKE_INVOICE_ORDER_CODEに同顧客が所有する確定済み注文コードを 指定する。Cloudflare Browser Rendering / R2 / 署名 URL の環境差は mock E2E では 保証できないため、請求書発行を確認したい deploy 後はこの recipe を staging で実行する。just storefront-payment-purchase-smoke stagingは、各決済方法でストア注文が成立することを smoke 層で一括保証する(@smoke:payment-purchaseを business-canary で replay)。代引き / 売掛 / 銀行振込の非リダイレクト 3 種は常時実行し、SBPS クレジットカードは staging secret のRUN_SBPS_E2E=trueのときだけ加わって 4 種になる(local では非リダイレクト 3 種のみ)。実注文を 生成するため@smoke:prod-safeを持たず production smoke では実行されない。SBPS は実課金を伴うため staging/local 限定ガードで production URL への誤実行を fail-closed で止め、完了後に同日返金する。 spec はapps/storefront/tests/e2e/smoke/payment-methods-purchase.real.spec.ts。- shadow/business canary script は failure 時に次の導線を stderr へ出す:
just storefront-shadow-probe <env>just storefront-business-canary <env>docs/03-implementation/infrastructure/monitoring-operations.md
storefront /products performance tracing¶
- 対象導線: Storefront の
/productsserver render。 - Sentry trace 上で確認できる custom span:
Storefront ProductsPageStorefront Products Visible SearchStorefront Products Fallback Search- 上記 span の配下には既存の Vendure span がぶら下がる:
Vendure GetVisibleProductsForBrowseVendure GetProductsSimple(fallback 時)- trace / transaction に付与される主な tag / context:
storefront.products.current_pagestorefront.products.collectionstorefront.products.term_presentstorefront.products.result_source(search/fallback/empty)- context
storefront_products_browse - batch ごとの breadcrumb:
- category:
products-browse - message:
search-batch/fallback-batch - data:
batchIndex,rawSkip,rawTotal,sourceItems,visibleProducts,visibleTotal,pageProducts - raw search term は保存しない。検索有無は
term_present、複数語かどうかはterm_word_countで確認する。
/products が遅いときの見方¶
の直下で Visible Search と Fallback Search のどちらが支配的か確認する。2. storefront.products.result_source=fallback が多い場合は、search index /
backend の visible browse aggregation / fallback products query の順で疑う。3. products-browse breadcrumb の rawTotal と pageProducts
を見て、通常 browse
query で十分に page が埋まっているか、fallback へ退避していないかを確認する。
storefront /search?q=... search performance tracing¶
- 対象導線: Storefront の商品検索結果初回表示。
- 計測値は
/productsと同じwindow.__ritsubiProductsPageMetricsを使い、termPresent=trueを前提に評価する。 - 主要 artifact / smoke:
tests/e2e/helpers/products-page-performance.tstests/e2e/smoke/products-search-performance.real.spec.ts- trace / transaction では次を確認する:
- page span:
Storefront ProductsPage - search span:
Storefront Products Visible Search - tag:
storefront.products.term_present=true - breadcrumb:
products-browse/search-batch - raw search term は Sentry に保存しない。slow trace 上では
term_presentとterm_word_countだけで検索有無・複数語を判断する。
storefront /products/$slug product detail performance tracing¶
- 対象導線: Storefront の商品詳細初回表示。
- Sentry trace 上で確認できる page span:
Storefront ProductDetailPage- 上記 span 配下には既存の Vendure span がぶら下がる:
Vendure GetProductDetailPageVendure ValidateVisibilityVendure GetCoPurchaseRecommendationsPage(購入共起レコメンドがある場合)- trace / transaction に付与される主な tag / context:
storefront.product_detail.routestorefront.product_detail.slug- context
storefront_product_detail - browser first visible は
window.__ritsubiProductDetailPageMetricsに記録し、 breadcrumbproduct-detail:first-visibleで duration / relatedProductsCount / hasWpContent を確認する。 - 主要 artifact / smoke:
tests/e2e/helpers/product-detail-page-performance.tstests/e2e/smoke/product-detail-performance.real.spec.ts
shared endpoint catalog manual check¶
- source catalog:
observability/external-monitor-endpoints.json - script:
scripts/ops/check-external-monitor-endpoints.mjs - package script:
pnpm run monitor:endpoints:check - just recipe:
just monitor-endpoints-check - purpose: shared catalog の status / json assertion を live endpoint に対して手動確認する。 同じ URL / method の probe は 1 回だけ fetch し、複数 assertion をまとめて評価する。
ローカルでの使い方¶
- shared catalog の全 endpoint を一発確認する:
just monitor-endpoints-check
- Sentry / Uptime Kuma と同じ意味論で確認する:
just monitor-endpoints-check --target sentry
just monitor-endpoints-check --target uptime-kuma
- 特定 endpoint だけ確認する:
pnpm run monitor:endpoints:check -- --endpoint production-vendure-schema-drift
運用上の注意¶
- exit code は 1 つでも assertion が fail したら non-zero になる。
--target allは shared catalog 上の json check を union で評価するため、tool-specific な Sentry-only assertion(例:$.mode != "")も含めて確認したいときに使う。--target sentry/--target uptime-kumaは各 tool に割り当てた json check だけを評価する。
uptime monitor automation(現在の正式運用)¶
- source catalog:
observability/external-monitor-endpoints.json - script:
scripts/ops/sentry-uptime-monitors.mjs - just recipes:
just sentry-uptime-list,just sentry-uptime-upsert - default org / base URL: root
.sentryclirc(ritsubi,https://sentry.io/) - monitor scope:
Production Storefront readiness,Production Vendure readiness,Production Vendure schema drift
ローカル / CI での使い方¶
- token なしで plan を確認する:
just sentry-uptime-upsert --dry-run
- 現在の uptime monitor 一覧を確認する:
just sentry-uptime-list
- shared catalog をそのまま org へ反映する:
just sentry-uptime-upsert
monitor の意味¶
Production Storefront readinesshttps://order.ritsubi-platform.com/api/health/readyを 5 分ごとに確認する。2xxに加え$.status == "ok"、$.mode != ""を満たしたときのみ success とみなす。- 目的: production の Storefront が request を受け入れられる状態かを Sentry 上で継続監視し、trace / release と直結させる。
Production Vendure readinesshttps://commerce.ritsubi-platform.com/health/readyを 5 分ごとに確認する。2xxに加え$.status == "ok"を満たしたときのみ success とみなす。- 目的: production の Vendure API が upstream request を受け入れられる状態かを継続監視する。
Production Vendure schema drifthttps://commerce.ritsubi-platform.com/health/readyを 5 分ごとに確認する。2xxに加え$.dependencies.schemaDrift == "ok"を満たしたときのみ success とみなす。- 目的: production の schema drift 検知が有効であり、 dependency contract まで含めて正常かを issue / release と相関して継続監視する。
運用上の注意¶
- 監視対象 endpoint の正本は
observability/external-monitor-endpoints.jsonとし、scripts/ops/sentry-uptime-monitors.mjsが runtime に Sentry 向け定義を構築する。 - upsert は
projectSlug + environment + nameで既存 monitor を照合し、同一なら update、無ければ create する。 just sentry-uptime-list/just sentry-uptime-upsertは local では login 済みsentryuser auth、CI ではSENTRY_AUTH_TOKENを使う。403が返る場合、script は org / project どちらの Uptime 権限が足りないかを含むメッセージを返す。毎回 API endpoint を手で辿らず、まずその診断文を確認する。- 現在の実装は Sentry の experimental uptime
endpoint(
/organizations/<org>/uptime/,/projects/<org>/<project>/uptime/)を使う。SaaS 側の互換性変更があれば script 側を更新する。 - WordPress login と version endpoint は Sentry uptime monitor の初期セットに含めず、
scheduled-safe-probes.ymlを manual fallback として残す。WordPress は Sentry project slug が DSN 依存で固定されておらず、version endpoint は availability より deploy metadata 確認の意味合いが強いため。React Dashboard はscheduled-safe-probes.ymlの login probe とscheduled-dashboard-smoke.ymlで補完する。
Sentry live config drift audit¶
- script:
scripts/ops/sentry-live-config-audit.mjs - just recipe:
just sentry-live-config-audit - purpose:
observability/sentry-workflow-alerts.json/observability/external-monitor-endpoints.jsonから導いた checked-in plan と、Sentry 上の live workflow / uptime monitor state を比較する。
ローカルでの使い方¶
- drift を JSON で確認する:
just sentry-live-config-audit --allow-drift --json
- drift を gate として使う:
just sentry-live-config-audit
運用上の注意¶
- workflow 側は
listとupsert --dry-runの比較、uptime 側も同様の比較で repo-managed 設定だけを監査する。 - suppressions の checked-in 正本は
observability/sentry-live-config-audit.jsonとし、accepted drift の理由と issue を明記する。 - 現在は issue
#533の billing / seat block により uptime monitor が live ではdisabledのままなので、現状確認だけしたい場合は--allow-driftを付ける。 - workflow drift が
missingの場合はjust sentry-alerts-upsert-dry-runで planned state を確認し、必要ならobservability/sentry-workflow-alerts.jsonの target / condition を見直す。
Uptime Kuma monitor automation(外部 watchdog)¶
- source catalog:
observability/external-monitor-endpoints.json - script:
scripts/ops/uptime-kuma-monitors.mjs - target instance:
https://milestone-monitoring.exe.xyz - public status page:
https://ritsubi-monitoring.exe.xyz/status/commerce - hosting note: Uptime Kuma は エリアルのサーバー内にホストされている。外部 SaaS と同じ前提で扱わず、疎通・認証・header 要件はその配置を前提に確認する。
- monitor scope:
Production Storefront readiness,Production Vendure readiness,Production Vendure schema drift,Production WordPress login
ローカル / CI での使い方¶
- live auth なしで plan と payload を確認する:
pnpm run uptime:kuma -- upsert --dry-run
- 現在の Uptime Kuma monitor 一覧を確認する:
pnpm run uptime:kuma -- list
- shared catalog をそのまま Uptime Kuma へ反映する:
pnpm run uptime:kuma -- upsert
monitor の意味¶
Production Storefront readinesshttps://order.ritsubi-platform.com/api/health/readyを 5 分ごとに確認する。- Uptime Kuma の
json-querymonitor として$.status == "ok"を満たしたときのみ success とみなす。 - 目的: Storefront の request 受付可否を、Sentry とは独立した watchdog でも継続監視する。
Production Vendure readinesshttps://commerce.ritsubi-platform.com/health/readyを 5 分ごとに確認する。- Uptime Kuma の
json-querymonitor として$.status == "ok"を満たしたときのみ success とみなす。 - 目的: Vendure API の readiness を外部監視として継続確認する。
Production Vendure schema drifthttps://commerce.ritsubi-platform.com/health/readyを 5 分ごとに確認する。- Uptime Kuma の
json-querymonitor として$.dependencies.schemaDrift == "ok"を満たしたときのみ success とみなす。 - 目的: schema drift 検知が
okであることを外部監視として継続確認し、schemaDriftdependency が欠落したケースも down として扱う。 Production WordPress loginhttps://cms.ritsubi-platform.com/wp-login.phpを 5 分ごとに確認する。- HTTP
200を success とみなす。 - 目的: WordPress の public login surface の到達性を継続確認する。
運用上の注意¶
- 監視対象 endpoint の正本は
observability/external-monitor-endpoints.jsonとし、scripts/ops/uptime-kuma-monitors.mjsが runtime に Kuma 向け定義を構築する。 - Uptime Kuma への反映は JSON ファイルの native import ではなく、
scripts/ops/uptime-kuma-monitors.mjsが shared catalog から定義を組み立てて Socket.IO 管理 API へ upsert する。 - upsert は monitor 名で既存定義を照合し、同名があれば update、無ければ create する。
- config に
notificationIDListを明示しない限り、既存 monitor の通知アタッチメントは維持する。 UPTIME_KUMA_TOKENを優先し、代替としてUPTIME_KUMA_USERNAME/UPTIME_KUMA_PASSWORDを使う。milestone-monitoring.exe.xyzは exe.dev ログイン配下のため、CLI からは追加 header が必要になる場合がある。その場合はUPTIME_KUMA_HEADERS_JSONを使うか、到達可能な内部 URL にUPTIME_KUMA_URLを切り替える。- さらに、この Uptime Kuma 自体は エリアルのサーバー内にホストされている。monitor apply や接続障害の切り分けでは、対象 endpoint 側だけでなく、エリアル側サーバーからの疎通 / 認証条件 / ingress 変更有無も先に確認する。
- React Dashboard は専用 health
endpoint が無いため Uptime Kuma monitor 初期セットには含めない。代わりに
scheduled-safe-probes.ymlの login probe とscheduled-dashboard-smoke.ymlで surface を補完する。 /api/health/cmsと version endpoint は診断 / metadata 用途なので継続監視に使わない。
Surface 別の実際の挙動¶
- Storefront
- workflow:
.github/workflows/_deploy-storefront-workers.yml - release:
storefront@<sha> - sourcemap source:
apps/storefront/dist - deploy 前に
.mapを除去するため、公開 artifact へ sourcemap を残さない。 cloudflare-deploy.mjsがSENTRY_ENVIRONMENT/VITE_PUBLIC_SENTRY_ENVIRONMENTとSENTRY_RELEASE/VITE_PUBLIC_SENTRY_RELEASEを Wrangler 設定へ注入する。- deploy workflow は
SENTRY_DSN/VITE_PUBLIC_SENTRY_DSN/SENTRY_AUTH_TOKENが欠けると fail-fast する。 - browser 側の feedback は
@sentry/reactの既存 client init 上で有効化し、 Userback は使用しない。 - Vendure
- workflow:
.github/workflows/_deploy-vendure-fly.yml - release:
vendure@<sha> - release / commit association は自動化済み。
- production は deploy 成功後に
apps/vendure-server/distを CI 上でbuild:productionし、debug-id 注入後に sourcemap を upload する。 - staging は
tsconfig.build.jsonで生成した TypeScript source map を Fly image のNODE_OPTIONS=--enable-source-mapsで runtime 解決する。 - Fly secret validation は
SENTRY_DSNを必須化している。 - recurring task のうち SMILE export / cleanup は
withMonitor(...)ベースの monitor slug (vendure-smile-export-orders,vendure-smile-export-cleanup) を付与している。 - worker process は
vendure-worker-heartbeat-production/vendure-worker-heartbeat-stagingの interval monitor で 5 分ごとに check-in する。 @sentry/profiling-nodeを trace lifecycle 連動で有効化し、trace sample が 0 より大きいときだけ Profile を生成する。- React Dashboard
- workflow:
.github/workflows/_deploy-dashboard-workers.yml - release:
dashboard@<sha> - sourcemap source:
apps/vendure-server/dist/dashboard - build-time に
VITE_SENTRY_*とVITE_ADMIN_API_URLを注入し、browser SDK を初期化する。VITE_SENTRY_DSNは明示必須で、shared/runtime 用SENTRY_DSNへの暗黙 fallback はしない。 - deploy 前に
.mapを除去せず、CI から sourcemap upload 後に Worker へ配備する。 - browser 側の feedback は
@sentry/reactの既存 init に統合している。 - admin API request ごとに
x-request-idを付与し、Vendure backend のrequest_idtag と合わせて browser event / Replay を相関させる。 - auth payload (
me.id,activeAdministrator.id) からactor_id,actor_type,dashboard_admincontext を更新する。 - WordPress CMS
- env source:
staging_wp/production_wp - release:
wordpress@<sha>を推奨。未設定時は release なしで event を送る。 - runtime は
ritsubi-ec-plugin/includes/sentry.phpが bootstrap し、fatal error / uncaught exception を Sentry store endpoint へ直接送信する。 - browser SDK と sourcemap upload は未導入のため、WordPress 直描画ページの JavaScript error は現時点では対象外。
staging / production での確認手順¶
共通の確認手順¶
- 対応する GitHub Actions deploy job の summary で、
Prepare Sentry release contextとFinalize ... Sentry releaseが成功していることを確認する。前段の validate step で required Sentry config が通っていることも確認する。 - Sentry Releases で対象 release(
storefront@<sha>/vendure@<sha>/dashboard@<sha>)が作成済みで、commits が関連付いていることを確認する。 - Event Details の
environmentとreleaseが deploy 対象と一致することを確認する。 - Sentry Uptime Monitors で
Production Storefront readiness/Production Vendure readinessの latest check が success になっていることを確認する。
Storefront の確認手順¶
- staging / production のブラウザまたは server-side でイベントを開き、
storefront_request/storefront_customercontext と GraphQL breadcrumb が入っていることを確認する。 - API / client failure の triage では、まず tag
area,flow,operation,routeを見て、どの surface で失敗したかを絞る。現在は/api/campaigns、/api/auth-bypass、/api/customer-hierarchy、customer-context、consent-context、TanStack Query cache clear on logout failure がこの導線に乗る。 storefront_requestcontext のrequestId,pathname,authStateと、eventextraの surface 固有情報(例:limit,shippingMode, consent 系の追加情報)を GitHub Actions artifact / アプリログと突合する。- Storefront の Feedback widget は常時表示しない。関係者が
?sentry-feedback=1を付けて開き、Sentry Feedback の標準 UI が表示されることを manual に確認する。name / email field は非表示でよい。 - staging では
staging-sentry-smoke-storefront.ymlが non-production 専用の debug route を使って intentional debug event を 1 件発生させ、run marker 付き diagnostics bundle を artifact 化する。triage 時は artifact のsummary.mdとmatched-events.jsonから対象 event を開く。 - non-production では次のどちらかで疎通確認できる。
GET/POST /api/debug-sentryにx-sentry-debug-tokenを付けて 500 を発生させる。scheduled canary はSENTRY_DEBUG_SIGNING_SECRETと marker から導出した token を使い、手動 debug 用の staticSENTRY_DEBUG_TOKENとは分離する。POST /api/client-logsに{"message":"__SENTRY_DEBUG__","context":{"sentryDebug":true}}を送り、browser 系の debug event を発生させる。- production では上記 debug route は 404 / 無効化が正しい。
POST /monitoring(Storefront Sentry tunnel)は browser tunnel transport のため production でも残すが、same-origin request を必須とする。
Vendure の確認手順¶
- deploy workflow の
Verify deployed version metadataが通っていることを確認する。これにより/versionの commit metadata が deploy 対象 SHA と一致している。 - Sentry event では
request,graphql,customer,channel,vendurecontext が確認対象。transaction名は<api-type>-api <operation-type> <operation-name-or-field>形式になる。 uncaughtException/unhandledRejectionによる fatal crash でも、共通の process handler がcaptureException()→flushSentry()→process.exit(1)の順で送信する。request context を持たない restart 直前の障害は、この fatal event を起点に見る。- backend stack trace は TypeScript の file/line を解決できることを確認する。
- Sentry Monitors / Crons で
vendure-smile-export-ordersとvendure-smile-export-cleanupの check-in が入っていることを確認する。 - worker monitor では
vendure-worker-heartbeat-productionまたはvendure-worker-heartbeat-stagingの latest check-in が継続して success になっていることを確認する。 - slow transaction を開き、Profile タブから CPU profile が添付されていることを確認する。
React Dashboard の確認手順¶
- Cloudflare Workers deploy 後に browser event / Replay を確認し、
release=dashboard@<sha>、tagservice=react-dashboard、surface=react-dashboard、api_type=adminが入ることを確認する。 - admin API 起点の event では tag
request_id,actor_id,actor_typeとcontextdashboard_request,dashboard_admin,dashboard_recent_requestsが入ることを確認する。 - 同じ
request_idで Vendure backend event を検索し、browser 側の Replay / event と backend 側の request 文脈を相互に辿れることを確認する。 - イベントが来ない場合は、build-time に
VITE_SENTRY_DSNとVITE_ADMIN_API_URLが明示注入されていたかを最初に確認する。SENTRY_DSNだけでは browser SDK は有効化されない。 - Feedback widget を開き、React Dashboard 上でも Sentry Feedback が表示されることを確認する。
Sentry 上で読める業務コンテキスト¶
Storefront のメトリクス¶
- tags:
storefront.runtime,storefront.route,storefront.auth_state,storefront.customer_status,storefront.customer_role,storefront.sentry_debug_marker,area,flow,operation,route - contexts:
storefront_request(runtime, requestId, pathname, authState)、storefront_customer(customerCode, billingCustomerCode, customerStatus, roleType)、storefront_debug(marker) - breadcrumbs:
graphqlcategory に operation 名、requestId、pathname、authMechanism、cache、result - extras: API / context / vendureQueryClient ごとの補助情報(例:
limit,shippingMode, consent request metadata など)
Vendure のメトリクス¶
- tags:
actor_id,actor_type,api_type,channel_id,customer_code,graphql_field,graphql_operation,graphql_operation_type,request_id,job_kind,job_name,job_monitor_slug - contexts:
actor,channel,customer,graphql,request,vendure - recurring task monitor では
vendure_jobcontext に schedule / description / channel などが入る。 - plugin から
setSentryPluginContext()を使う場合、plugin.<name>context とplugin_<name>_*tag が追加される。
React Dashboard のメトリクス¶
- tags:
service,surface,api_type,request_id,actor_id,actor_type - contexts:
dashboard_request(id, method, path, statusCode, failed)、dashboard_admin(userId, administratorId)、dashboard_recent_requests(直近 5 件の admin API request) - Storefront / Vendure ほどの customer/channel
enrichment はまだ入れていないが、Vendure admin API とは
request_idを軸に相関できる。
アラート / トリアージ / ownership の基本¶
| 事象 | 主担当 | 最初に見る項目 | 次の確認先 |
|---|---|---|---|
| Storefront browser / edge / server error | Storefront 担当 | area, flow, operation, route, storefront_request, storefront_customer, GraphQL breadcrumb |
Cloudflare deploy SHA、Storefront logs |
| Vendure GraphQL / API error | Vendure 担当 | api_type, channel_id, graphql_operation, customer_code, request_id |
Fly deploy SHA、/version metadata、Vendure logs |
Safe probing tiers¶
監視エンドポイントは用途によって 2 つのカテゴリに分かれる。
- Safe Probe(Tier 0) ── アプリ自身の稼働状態のみを返し、外部サービスへの fan-out を一切行わない。外形監視ツールや CI からの継続的な死活確認、Fly.io / Cloudflare の health check に使える。
- CMS 診断エンドポイント(Tier 3) ── CMS 配信経路全体(Vendure 経由の WordPress 疎通を含む)を検査する。呼び出しごとに upstream への fan-out が発生するため、連続ポーリングには使わない。「Safe Probe は全て正常なのに CMS コンテンツが表示されない」という場面で、障害箇所(Vendure 側 / WordPress 側 / 両方)を切り分けるためにワンショットで呼び出す診断専用エンドポイント。
オペレーター向け早見表: どのエンドポイントを使うか
確認したいこと 使うエンドポイント アプリが起動しているか(外形監視 / Fly.io health check) GET /api/health/live(または Vendure/health/live)デプロイ後にトラフィックを受け入れられるか GET /api/health/ready(または Vendure/health/ready)今デプロイされているバージョンは何か GET /api/version(または Vendure/version)CMS コンテンツが表示されない原因を調べたい(診断) GET /api/health/cms(ワンショットのみ)
- Tier 0 Safe Probe:
vendure /health/live,vendure /health/ready,vendure /version,storefront /api/health/live,storefront /api/health/ready,storefront /api/version。外部依存の fan-out がなく継続監視に適したエンドポイント。production の継続監視はobservability/external-monitor-endpoints.jsonを正本に、scripts/ops/sentry-uptime-monitors.mjsが Sentry Uptime へ、scripts/ops/uptime-kuma-monitors.mjsが Uptime Kuma へ反映する。Sentry は Storefront readiness / Vendure readiness / Vendure schema drift、Uptime Kuma は Storefront readiness / Vendure readiness / Vendure schema drift / WordPress login を 5 分ごとに監視する。staging・version endpoint・WordPress admin redirect は.github/workflows/scheduled-safe-probes.ymlに残し、manual fallback として実行する。fallback workflow は HTTP 200 だけでなく レスポンスボディの shape も検証する(health 系はstatus == "ok"、version 系はversion・commit・buildTime・envフィールドの存在と非空文字列を確認)。fallback が失敗した場合、SLACK_WEBHOOK_URLシークレットが設定されていればnotify-slack-webhookaction 経由で Slack に通知される。 - Tier 1 Deploy Verification:
apps/vendure-server/scripts/post-deploy-check.shの schema / CORS / asset delivery と deploy workflow の version verify - Tier 2 Synthetic Journey:
staging-sentry-smoke-storefront.ymlとstaging-smoke-storefront.ymlの browser canary、 およびproduction-sentry-smoke-storefront.ymlの manual observability 切り分け。いずれかの smoke job が失敗した場合も同様に Slack へ通知される。 - Tier 2.5 Sentry diagnostics snapshot:
staging-smoke-storefront.ymlは failure / cancelled 時に deployed release を基準に Sentry issue / event snapshot を artifact 化する。staging-sentry-smoke-storefront.ymlは staging 専用 debug event の run marker に一致する event を取得し、summary.md/matched-events.jsonを triage の最短導線とする。 - Tier 3 Diagnostic / Internal Probe:
storefront /api/health/cms。 「CMS 配信経路のどこが壊れているか」を特定するための診断専用エンドポイント。Vendure CMS API と WordPress upstream 直疎通の 2 系統を個別にチェックし、障害箇所を root-cause レベルで切り分けるための情報を返す。upstream への fan-out を含むため、継続ポーリングには使わない。障害調査・デプロイ後のワンショット確認のみ。レスポンスの top-levelstatusフィールドはok|degraded|failを返す(HTTP 503: 両チェック失敗 / HTTP 200 +degraded: 片方のみ失敗)。詳細は 5.2 Storefront ヘルスチェックエンドポイント を参照。
運用ルール¶
- まず
releaseとenvironmentで「どの配備から発生したか」を確定する。 request_idがあるイベントは、アプリログ側の同一 request と突合する。- Storefront の Vendure failure は
graphql.error_code/graphql.operation_type/http.status_code/dependency.nameを優先して見て、auth/validation/upstream failure を切り分ける。 customer_code/storefront_customer.customerCode/channel_idがある場合は blast radius 判定に使う。- checkout / login / order 系で production に継続影響がある場合は、error 数よりも business impact を優先して即時エスカレーションする。
現状確定の観測サマリ¶
過去ここにあった「将来 enrichment する / threshold tuning する / surface を広げる」系の deferred items は
docs/04-project-management/architecture-improvement-backlog.mdの「Observability backlog」へ移動した。ここには 現状確定の運用形 だけを残す。
- Sentry Logs は 限定採用 とし、repo の system of
record は既存の構造化 stdout ログを維持する。Vendure は Pino →
stdout、Storefront / React Dashboard は platform 側の stdout / browser
console を正本としつつ、JS runtime では
enableLogs: trueと warn/error のconsoleLoggingIntegrationを有効化して issue / trace / feedback / replay と相関できるようにする。 - Storefront の Vendure GraphQL / HTTP failure は
storefront.graphql_operation/graphql.operation_type/graphql.error_code/http.status_code/dependency.name=vendureを Sentry tag/context に残し、checkout / login / order 障害の切り分けを初動から可能にする。 - Storefront / React Dashboard の browser surface は conservative な
beforeSendを使い、browser extension 由来ノイズを drop しつつauthorization/cookie/tokenなどの sensitive field を event / request / breadcrumb から redact する。 - browser surface(Storefront / React Dashboard)では
httpClientIntegration()も有効化し、既定の 5xx failed request を Sentry event として追跡する。4xx を含める必要が出た場合のみ、ノイズとコストを見て別途拡張する。 - Storefront Session Replay は browser client init で有効。sample rate は
VITE_PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATEとVITE_PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATEで調整し、production では既定で session 10% / on-error 100% を使う。 - React Dashboard Session Replay も browser init で有効。sample
rate はコード定数(production: session 10%、non-production: session
100%、on-error 100%)を使い、
VITE_ADMIN_API_URLを正本に trace propagation target とx-request-id相関を構成する。 - Sentry AI monitoring は現時点で非適用。repo 内に OpenAI / Anthropic / LangChain / Vercel AI SDK などの AI SDK 導線が入った時点で再評価する。
- Storefront Worker OTel exporter は active。
apps/storefront/wrangler.tomlのobservability.logs.destinations/observability.traces.destinationsを repo 管理の正本とし、Cloudflare dashboard 側で同名 destination を Sentry の OTLP traces/logs endpoint へ向ける。 - Storefront runtime bootstrap telemetry は mandatory。
sentry.server.config.ts/sentry.edge.config.tsの初期化完了時に「app code まで到達した」ことを build metadata (version,release,commit,deployTarget) 付きで構造化ログに残し、 Cloudflare1101のような Worker 初期化失敗とアプリ内 500 を切り分ける。 - Storefront request guard failure telemetry は mandatory。
server-auth-guard.tsとserver-maintenance-guard.tsは、予期しない例外を未捕捉のまま上げず、requestId,traceId,route,guard,fallbackを含む構造化ログと Sentry capture を残して degrade する。 - browser SDK / Replay / feedback は
@sentry/react、Worker SDK は@sentry/cloudflareで運用し、Cloudflare Observability は Worker runtime telemetry の配送面を担う。 - collector / routing infrastructure を新設する判断や、Prometheus / Grafana / Loki を Fly.io で常時運用する実需要が出るまでは、現行の Sentry + Cloudflare Observability export + Uptime Kuma + manual fallback を正規運用とする。
[!NOTE] 以下の
/metrics、Prometheus、Grafana に関する例は、アプリ側の計測仕様と将来導入用テンプレートを兼ねる。現時点ではこれらを Fly.io の常設監視 app として運用しない。
2. インフラストラクチャ監視¶
2.1 Fly.io メトリクス¶
システムリソース監視¶
// system-metrics.ts
import { register, Gauge } from "prom-client";
import { execSync } from "child_process";
export class SystemMetrics {
private cpuUsageGauge = new Gauge({
name: "system_cpu_usage_percent",
help: "CPU usage percentage",
});
private memoryUsageGauge = new Gauge({
name: "system_memory_usage_bytes",
help: "Memory usage in bytes",
});
private diskUsageGauge = new Gauge({
name: "system_disk_usage_bytes",
help: "Disk usage in bytes",
labelNames: ["mount"],
});
constructor() {
register.registerMetric(this.cpuUsageGauge);
register.registerMetric(this.memoryUsageGauge);
register.registerMetric(this.diskUsageGauge);
// 30秒ごとにメトリクス更新
setInterval(() => this.updateMetrics(), 30000);
}
private updateMetrics() {
try {
// CPU使用率
const cpuUsage = this.getCPUUsage();
this.cpuUsageGauge.set(cpuUsage);
// メモリ使用量
const memoryUsage = this.getMemoryUsage();
this.memoryUsageGauge.set(memoryUsage);
// ディスク使用量
const diskUsage = this.getDiskUsage();
Object.entries(diskUsage).forEach(([mount, usage]) => {
this.diskUsageGauge.labels(mount).set(usage);
});
} catch (error) {
console.error("Failed to update system metrics:", error);
}
}
private getCPUUsage(): number {
try {
const output = execSync("top -bn1 | grep 'Cpu(s)' | awk '{print $2}' | cut -d'%' -f1");
return parseFloat(output.toString().trim());
} catch {
return 0;
}
}
private getMemoryUsage(): number {
try {
const output = execSync("free -b | grep '^Mem:' | awk '{print $3}'");
return parseInt(output.toString().trim());
} catch {
return 0;
}
}
private getDiskUsage(): Record<string, number> {
try {
const output = execSync("df -B1 | tail -n +2");
const lines = output.toString().trim().split("\n");
const usage: Record<string, number> = {};
lines.forEach((line) => {
const parts = line.split(/\s+/);
if (parts.length >= 6) {
const mount = parts[5];
const used = parseInt(parts[2]);
usage[mount] = used;
}
});
return usage;
} catch {
return {};
}
}
}
2.2 データベース監視¶
PostgreSQL 監視¶
// database-metrics.ts
export class DatabaseMetrics {
private connectionPoolGauge = new Gauge({
name: "postgres_connection_pool_size",
help: "PostgreSQL connection pool size",
labelNames: ["state"],
});
private queryDurationHistogram = new Histogram({
name: "postgres_query_duration_seconds",
help: "PostgreSQL query duration",
labelNames: ["operation"],
buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
});
private activeConnectionsGauge = new Gauge({
name: "postgres_active_connections",
help: "Number of active PostgreSQL connections",
});
constructor(private dataSource: DataSource) {
register.registerMetric(this.connectionPoolGauge);
register.registerMetric(this.queryDurationHistogram);
register.registerMetric(this.activeConnectionsGauge);
// 接続プール監視
setInterval(() => this.updateConnectionMetrics(), 30000);
}
trackQuery(operation: string, duration: number) {
this.queryDurationHistogram.labels({ operation }).observe(duration / 1000);
}
private async updateConnectionMetrics() {
try {
if (this.dataSource.isInitialized) {
const pool = (this.dataSource.driver as any).master;
if (pool) {
this.connectionPoolGauge.labels("total").set(pool.totalCount || 0);
this.connectionPoolGauge.labels("idle").set(pool.idleCount || 0);
this.connectionPoolGauge.labels("waiting").set(pool.waitingCount || 0);
}
// アクティブ接続数を取得
const result = await this.dataSource.query(
"SELECT count(*) as active_connections FROM pg_stat_activity WHERE state = $1",
["active"],
);
this.activeConnectionsGauge.set(parseInt(result[0]?.active_connections || "0"));
}
} catch (error) {
console.error("Failed to update database metrics:", error);
}
}
}
Redis 監視¶
// redis-metrics.ts
export class RedisMetrics {
private connectionGauge = new Gauge({
name: "redis_connected_clients",
help: "Number of connected Redis clients",
});
private memoryUsageGauge = new Gauge({
name: "redis_memory_usage_bytes",
help: "Redis memory usage in bytes",
});
private commandsProcessedTotal = new Counter({
name: "redis_commands_processed_total",
help: "Total number of commands processed",
labelNames: ["command"],
});
private keyspaceGauge = new Gauge({
name: "redis_keyspace_keys",
help: "Number of keys in Redis keyspace",
labelNames: ["db"],
});
constructor(private redis: Redis) {
register.registerMetric(this.connectionGauge);
register.registerMetric(this.memoryUsageGauge);
register.registerMetric(this.commandsProcessedTotal);
register.registerMetric(this.keyspaceGauge);
// Redis INFO 監視
setInterval(() => this.updateRedisMetrics(), 30000);
}
private async updateRedisMetrics() {
try {
const info = await this.redis.info();
const sections = this.parseRedisInfo(info);
// 接続数
if (sections.clients?.connected_clients) {
this.connectionGauge.set(parseInt(sections.clients.connected_clients));
}
// メモリ使用量
if (sections.memory?.used_memory) {
this.memoryUsageGauge.set(parseInt(sections.memory.used_memory));
}
// キースペース
Object.entries(sections.keyspace || {}).forEach(([db, info]) => {
const match = info.match(/keys=(\d+)/);
if (match) {
this.keyspaceGauge.labels(db).set(parseInt(match[1]));
}
});
} catch (error) {
console.error("Failed to update Redis metrics:", error);
}
}
private parseRedisInfo(info: string): Record<string, Record<string, string>> {
const sections: Record<string, Record<string, string>> = {};
let currentSection = "";
info.split("\n").forEach((line) => {
line = line.trim();
if (line.startsWith("#")) {
currentSection = line.substring(2).toLowerCase();
sections[currentSection] = {};
} else if (line.includes(":")) {
const [key, value] = line.split(":");
if (sections[currentSection]) {
sections[currentSection][key] = value;
}
}
});
return sections;
}
}
3. ログ管理¶
3.1 構造化ログ¶
現在の実装では、構造化ログは runtime ごとに別 schema を持つのではなく、共通の相関 field を揃えた JSON を正本とする。
- 共通 join key:
requestIdtraceIdspanIdsurfacesource- Storefront / browser log ingest / Vendure logger / Playwright manifest / workflow archive は、同じ field 名で相関する。
- Vendure logger は
pinoを使い、messagekey を正本とする。 - Vendure の logger child binding は
requestId,traceId,spanId,operationName,operationType,fieldName,apiType,channelId,customerCode,actorTypeを保持する。
運用時にどの artifact / log / Sentry event をどの順で開くかは Observability 相関ガイド を参照する。
3.2 ログ集約設定¶
# fluentd-config.yml (オプション)
<source> @type tail path /app/logs/*.log pos_file
/var/log/fluentd-vendure.log.pos tag vendure.* format json time_key timestamp
time_format %Y-%m-%dT%H:%M:%S.%LZ </source>
<match vendure.**> @type datadog api_key "#{ENV['DD_API_KEY']}" service vendure
source nodejs sourcecategory vendure tags
environment:#{ENV['NODE_ENV']},instance:#{ENV['FLY_MACHINE_ID']} </match>
3.3 Storefront ログ方針(構造化 JSON + stdout)¶
Storefront(Vite)は
アプリ側で構造化した JSON ログを stdout に出力する方針とする。
stdout はローカル開発や実行環境での標準出力として扱い、アプリ側は JSON の整形と共通フィールド付与に集中する。
本番の Storefront は Cloudflare 上で稼働するため、ログ管理は Cloudflare 側で行う。
目的¶
- 画面/リクエスト単位で追跡できるログフォーマットに統一する
- Cloudflare / ローカル stdout の両方で追跡しやすい形にそろえる
基本設計¶
- 現在の実装は
apps/storefront/src/lib/logger.tsの console-based logger で、JSON 形式のログを stdout に出力する - 共通フィールドを付与する(以下の項目を標準とする)
- PII はログに残さない(必要な場合はマスク処理を経由する)
共通フィールド(標準)¶
必須¶
service: サービス名(例:storefront/vendure)environment: 実行環境(例:local/staging/production)level: ログレベル(info / warn / error など)message: ログ本文timestamp: ISO8601
推奨¶
requestId: リクエスト単位の相関IDroute: ルートまたはパスmethod: HTTP メソッドstatus: HTTP ステータス(レスポンス時)durationMs: レイテンシ(ミリ秒)
禁止(PII)¶
- 氏名・メール・電話など直接個人を特定できる値
- 生のトークン・セッション・認可情報
実装方針¶
- アプリ側で構造化し、stdout に出力する(転送レイヤの変換は最小限)
- Storefront / Vendure で共通の logger ラッパーを用意し、必須フィールドを自動付与する
- stdout を system of record としつつ、warn/error の console ログは Sentry Logs にも転送し、error / trace / replay / feedback との相関を取りやすくする
- Storefront の warn/error logger と browser client log は、
requestId/route/source/service/environmentを含む Sentry breadcrumb も残し、issue 側から同一 request の stdout / client log を辿りやすくする
4. アラート設定¶
4.1 クリティカルアラート¶
// alert-rules.ts
export const alertRules = {
// アプリケーション可用性
application_down: {
metric: "up",
condition: "== 0",
duration: "2m",
severity: "critical",
message: "Vendure application is down",
channels: ["slack", "email", "pagerduty"],
},
// レスポンス時間
high_response_time: {
metric: "vendure_api_duration_seconds",
condition: "> 2",
duration: "5m",
severity: "warning",
message: "API response time is high (>2s)",
channels: ["slack"],
},
// エラー率
high_error_rate: {
metric: 'rate(vendure_api_requests_total{status=~"5.."}[5m])',
condition: "> 0.05",
duration: "3m",
severity: "critical",
message: "High error rate detected (>5%)",
channels: ["slack", "email"],
},
// データベース接続
database_connection_high: {
metric: "postgres_active_connections",
condition: "> 80",
duration: "5m",
severity: "warning",
message: "High database connection count",
channels: ["slack"],
},
// Redis メモリ使用量
redis_memory_high: {
metric: "redis_memory_usage_bytes",
condition: "> 2.5e9", // 2.5GB
duration: "5m",
severity: "warning",
message: "Redis memory usage is high",
channels: ["slack"],
},
// ディスク使用量
disk_usage_high: {
metric: "system_disk_usage_bytes",
condition: "> 0.85",
duration: "10m",
severity: "warning",
message: "Disk usage is high (>85%)",
channels: ["slack"],
},
// ビジネス指標
order_failure_rate: {
metric: "rate(vendure_orders_failed_total[10m])",
condition: "> 0.1",
duration: "5m",
severity: "critical",
message: "Order failure rate is high (>10%)",
channels: ["slack", "email", "business_team"],
},
};
4.2 アラート通知設定¶
// notification-channels.ts
export class NotificationManager {
private channels: Map<string, NotificationChannel> = new Map();
constructor() {
this.setupChannels();
}
private setupChannels() {
// Slack 通知
this.channels.set(
"slack",
new SlackChannel({
webhookUrl: process.env.SLACK_WEBHOOK_URL!,
channel: "#vendure-alerts",
username: "Vendure Monitor",
}),
);
// Email 通知(SES)
this.channels.set(
"email",
new EmailChannel({
ses: {
region: process.env.AWS_REGION!,
fromAddress: process.env.EMAIL_FROM!,
},
from: "alerts@ritsubi.co.jp",
to: ["admin@ritsubi.co.jp", "dev@ritsubi.co.jp"],
}),
);
// PagerDuty 通知(クリティカル用)
this.channels.set(
"pagerduty",
new PagerDutyChannel({
integrationKey: process.env.PAGERDUTY_INTEGRATION_KEY!,
}),
);
}
async sendAlert(alert: Alert) {
const promises = alert.channels.map(async (channelName) => {
const channel = this.channels.get(channelName);
if (channel) {
try {
await channel.send(alert);
} catch (error) {
console.error(`Failed to send alert to ${channelName}:`, error);
}
}
});
await Promise.allSettled(promises);
}
}
interface Alert {
severity: "info" | "warning" | "critical";
title: string;
message: string;
timestamp: Date;
channels: string[];
metadata?: any;
}
interface NotificationChannel {
send(alert: Alert): Promise<void>;
}
class SlackChannel implements NotificationChannel {
constructor(
private config: {
webhookUrl: string;
channel: string;
username: string;
},
) {}
async send(alert: Alert): Promise<void> {
const color = {
info: "#36a64f",
warning: "#ff9900",
critical: "#ff0000",
}[alert.severity];
const payload = {
channel: this.config.channel,
username: this.config.username,
attachments: [
{
color,
title: alert.title,
text: alert.message,
timestamp: Math.floor(alert.timestamp.getTime() / 1000),
fields: [
{
title: "Severity",
value: alert.severity.toUpperCase(),
short: true,
},
{
title: "Environment",
value: process.env.NODE_ENV || "unknown",
short: true,
},
],
},
],
};
const response = await fetch(this.config.webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Slack notification failed: ${response.statusText}`);
}
}
}
5. ヘルスチェック¶
5.1 Vendure ヘルスチェックエンドポイント¶
Vendure が公開するヘルスチェック / 監視エンドポイントの一覧と用途:
| エンドポイント | 用途 | 連続ポーリング |
|---|---|---|
GET /health/live |
Liveness probe。プロセスが応答できるかだけを確認(外部依存なし)。Fly.io の TCP/HTTP health check に使う。 | ✅ 可 |
GET /health/ready |
Readiness probe。DB・Redis 接続を確認し、トラフィック受け入れ可否を返す。デプロイ後ポーリングに使う。 | ✅ 可 |
GET /health |
/health/ready と同等の後方互換エイリアス。新規ワークフローでは /health/ready を使うこと。 |
✅ 可(非推奨) |
GET /version |
デプロイ済みの git連動 build version / release version / commit SHA を返す。デプロイ検証ステップで使用。 | ✅ 可 |
GET /metrics |
Node.js ランタイムメトリクス(JSON形式)。Fly.io ヘルスチェックおよび ad-hoc 確認用。 | ✅ 可 |
Tier 0 Safe Probe の自動実行:
.github/workflows/scheduled-safe-probes.ymlが staging・production の Tier 0 エンドポイントをスケジュール実行している。Vendure / Storefront については HTTP 200 に加えてレスポンスボディの shape(status == "ok"/ version フィールドの存在)を検証し、WordPress についてはwp-login.phpの200とpost-new.php?post_type=product_detailの未ログイン時302/303を確認する。失敗時は Slack 通知ジョブが起動する。
Storefront(Vite / Cloudflare Workers)が公開する監視・診断エンドポイントの一覧と用途。エンドポイントの選択指針は Safe probing tiers の早見表も参照。
| エンドポイント | 用途 | 連続ポーリング |
|---|---|---|
GET /api/health/live |
Liveness probe。Worker プロセスが応答できるかのみ確認(外部依存なし)。 | ✅ 可 |
GET /api/health/ready |
Readiness probe。依存サービスへの基本疎通を確認し、トラフィック受け入れ可否を返す。 | ✅ 可 |
GET /api/version |
デプロイ済みの git連動 build version / release version / commit SHA を返す。デプロイ検証に使用。 | ✅ 可 |
GET /version |
GET /api/version のエイリアス。Safe probe や手動確認時に短いパスで参照したい場合に使用。 |
✅ 可 |
GET /api/health/cms |
CMS 配信経路診断。production は status-only、non-production は詳細 message を返す。 | ❌ 診断専用 |
POST /monitoring |
Sentry browser tunnel。same-origin リクエストのみ受け入れる(Sec-Fetch-Site 検証)。外部からのアクセスは 403。 |
❌ ブラウザ専用 |
/api/health/cmsを Safe Probe と分けている理由Safe Probe(
/api/health/live,/api/health/ready,/api/version)はアプリ自身の稼働状態のみを返し、外部サービスへのリクエストを一切発生させない。一方、/api/health/cmsは呼び出しごとに Vendure CMS API(Fly.io 内部)および WordPress(外部 upstream)への fan-out リクエストを発生させる。この fan-out を連続ポーリングに使うと次の問題が生じる。
- upstream(Vendure / WordPress)に不必要な負荷がかかる
- upstream の一時的な遅延が誤検知アラートを引き起こす
- Safe Probe とは独立した障害要因を混入させるため、アプリ自体の死活確認として不適切
正しい使い方は「Safe Probe は全て正常なのに CMS コンテンツが表示されない」という場面で、障害箇所(Vendure 側 / WordPress 側 / 両方)の特定を目的としてワンショットで呼び出すこと。
5.3 アプリケーションヘルスチェック実装例¶
// health-check.ts
import { Controller, Get } from "@nestjs/common";
import {
HealthCheck,
HealthCheckService,
TypeOrmHealthIndicator,
MemoryHealthIndicator,
DiskHealthIndicator,
} from "@nestjs/terminus";
@Controller("health")
export class HealthController {
constructor(
private health: HealthCheckService,
private db: TypeOrmHealthIndicator,
private memory: MemoryHealthIndicator,
private disk: DiskHealthIndicator,
private redisHealth: RedisHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
// データベース接続チェック
() => this.db.pingCheck("database"),
// Redis 接続チェック
() => this.redisHealth.pingCheck("redis"),
// メモリ使用量チェック
() => this.memory.checkHeap("memory_heap", 250 * 1024 * 1024),
() => this.memory.checkRSS("memory_rss", 500 * 1024 * 1024),
// ディスク使用量チェック
() =>
this.disk.checkStorage("storage", {
path: "/",
thresholdPercent: 0.85,
}),
// カスタムビジネスロジックチェック
() => this.customBusinessHealthCheck(),
]);
}
@Get("readiness")
@HealthCheck()
readiness() {
return this.health.check([
() => this.db.pingCheck("database"),
() => this.redisHealth.pingCheck("redis"),
]);
}
@Get("liveness")
@HealthCheck()
liveness() {
return this.health.check([() => this.memory.checkHeap("memory_heap", 500 * 1024 * 1024)]);
}
private async customBusinessHealthCheck() {
try {
// 重要なビジネス機能の動作確認
// 例: 商品検索機能、価格計算機能など
const testProduct = await this.productService.findOne("test-product-id");
if (!testProduct) {
throw new Error("Test product not found");
}
const testPricing = await this.pricingService.calculatePrice(testProduct, "test-customer-id");
if (!testPricing) {
throw new Error("Pricing calculation failed");
}
return {
"business-logic": {
status: "up",
details: {
productSearch: "ok",
pricingCalculation: "ok",
},
},
};
} catch (error) {
return {
"business-logic": {
status: "down",
details: {
error: error.message,
},
},
};
}
}
}
class RedisHealthIndicator {
constructor(private redis: Redis) {}
async pingCheck(key: string) {
try {
const result = await this.redis.ping();
return {
[key]: {
status: result === "PONG" ? "up" : "down",
},
};
} catch (error) {
return {
[key]: {
status: "down",
details: error.message,
},
};
}
}
}
6. ダッシュボード設定¶
6.1 Grafana ダッシュボード(テンプレート)¶
現時点で Grafana 自体は Fly.io の正式運用に含めない。以下は
observability/grafana/ を再導入する場合のテンプレートとして保持する。
{
"dashboard": {
"title": "Vendure Production Dashboard",
"panels": [
{
"title": "API Response Time",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.95, rate(vendure_api_duration_seconds_bucket[5m]))",
"legendFormat": "95th percentile"
},
{
"expr": "histogram_quantile(0.50, rate(vendure_api_duration_seconds_bucket[5m]))",
"legendFormat": "50th percentile"
}
],
"yAxes": [
{
"label": "Response Time (seconds)",
"max": 5
}
]
},
{
"title": "Request Rate",
"type": "graph",
"targets": [
{
"expr": "rate(vendure_api_requests_total[5m])",
"legendFormat": "{{endpoint}} - {{method}}"
}
]
},
{
"title": "Error Rate",
"type": "singlestat",
"targets": [
{
"expr": "rate(vendure_api_requests_total{status=~\"5..\"}[5m]) / rate(vendure_api_requests_total[5m]) * 100"
}
],
"valueName": "current",
"format": "percent",
"thresholds": "1,5",
"colorBackground": true
},
{
"title": "Database Connections",
"type": "graph",
"targets": [
{
"expr": "postgres_active_connections",
"legendFormat": "Active Connections"
},
{
"expr": "postgres_connection_pool_size{state=\"idle\"}",
"legendFormat": "Idle Pool"
}
]
},
{
"title": "Redis Memory Usage",
"type": "graph",
"targets": [
{
"expr": "redis_memory_usage_bytes / 1024 / 1024",
"legendFormat": "Memory Usage (MB)"
}
]
},
{
"title": "Business Metrics",
"type": "row",
"panels": [
{
"title": "Orders per Hour",
"type": "graph",
"targets": [
{
"expr": "rate(vendure_orders_created_total[1h]) * 3600",
"legendFormat": "{{customerType}}"
}
]
},
{
"title": "Revenue per Hour",
"type": "graph",
"targets": [
{
"expr": "rate(vendure_order_total_amount_sum[1h]) * 3600",
"legendFormat": "Revenue (JPY/hour)"
}
]
}
]
}
],
"time": {
"from": "now-6h",
"to": "now"
},
"refresh": "30s"
}
}
7. 運用プロセス¶
7.1 インシデント対応¶
// incident-response.ts
export class IncidentResponse {
private static readonly SEVERITY_LEVELS = {
P1: { name: "Critical", responseTime: 15, resolveTime: 4 * 60 }, // 15分以内対応、4時間以内解決
P2: { name: "High", responseTime: 60, resolveTime: 24 * 60 }, // 1時間以内対応、24時間以内解決
P3: { name: "Medium", responseTime: 4 * 60, resolveTime: 72 * 60 }, // 4時間以内対応、72時間以内解決
P4: { name: "Low", responseTime: 24 * 60, resolveTime: 168 * 60 }, // 24時間以内対応、1週間以内解決
};
static async handleIncident(alert: Alert) {
const severity = this.determineSeverity(alert);
const incident = await this.createIncident(alert, severity);
await this.notifyOnCall(incident);
await this.executeRunbook(incident);
return incident;
}
private static determineSeverity(alert: Alert): string {
// アラートタイプに基づく重要度判定
const criticalPatterns = [
"application_down",
"database_connection_failed",
"order_failure_rate",
];
if (criticalPatterns.some((pattern) => alert.title.includes(pattern))) {
return "P1";
}
if (alert.severity === "critical") {
return "P1";
} else if (alert.severity === "warning") {
return "P2";
} else {
return "P3";
}
}
private static async executeRunbook(incident: Incident) {
const runbook = this.getRunbook(incident.type);
if (runbook) {
await runbook.execute(incident);
}
}
}
// 自動復旧処理
export class AutoRecovery {
static async attemptRecovery(alert: Alert): Promise<boolean> {
switch (alert.title) {
case "high_memory_usage":
return await this.restartApplication();
case "redis_connection_failed":
return await this.reconnectRedis();
case "database_connection_high":
return await this.killIdleConnections();
default:
return false;
}
}
private static async restartApplication(): Promise<boolean> {
try {
// Fly.io アプリケーション再起動
execSync("flyctl machine restart", { timeout: 30000 });
// ヘルスチェック待機
await this.waitForHealthy(60000);
return true;
} catch (error) {
console.error("Auto recovery failed:", error);
return false;
}
}
private static async waitForHealthy(timeout: number): Promise<void> {
const start = Date.now();
while (Date.now() - start < timeout) {
try {
const response = await fetch("/health/ready"); // `/health` は後方互換エイリアス。新規実装では `/health/ready` を使うこと
if (response.ok) {
return;
}
} catch {
// 接続エラーは無視して継続
}
await new Promise((resolve) => setTimeout(resolve, 5000));
}
throw new Error("Health check timeout");
}
}
7.2 定期メンテナンス¶
#!/bin/bash
# maintenance.sh - 定期メンテナンススクリプト
# データベースメンテナンス
postgres_maintenance() {
echo "Running PostgreSQL maintenance..."
flyctl postgres connect -a ritsubi-vendure-db << EOF
VACUUM ANALYZE;
REINDEX DATABASE ritsubi_vendure;
-- 古いログの削除
DELETE FROM vendure_session WHERE expires_at < NOW() - INTERVAL '7 days';
DELETE FROM vendure_job_record WHERE finished_at < NOW() - INTERVAL '30 days';
EOF
}
# Redis メンテナンス
redis_maintenance() {
echo "Running Redis maintenance..."
# 期限切れキーの削除
redis-cli --scan --pattern "expired:*" | xargs redis-cli del
# メモリ最適化
redis-cli MEMORY PURGE
}
# ログローテーション
log_rotation() {
echo "Rotating application logs..."
# 古いログファイルの圧縮・削除
find /app/logs -name "*.log" -mtime +7 -exec gzip {} \;
find /app/logs -name "*.log.gz" -mtime +30 -delete
}
# メトリクスクリーンアップ
metrics_cleanup() {
echo "Cleaning up old metrics..."
# 古いPrometheusメトリクスの削除
# (通常はPrometheusサーバー側で設定)
}
# バックアップ検証
backup_verification() {
echo "Verifying backups..."
# 現行の正本は docs/05-delivery/maintenance/backup-and-restore.md
gh run list --workflow backup-postgres-prd.yml --limit 12
echo "Check latest objects under:"
echo " s3://ritsubi-ecommerce-backup/postgres/"
echo " s3://ritsubi-ecommerce-backup/wordpress/production/db/"
echo "Expected cadence: hourly with 720h retention."
echo "Alerts: BACKUP_NOTIFY_WEBHOOK_URL posts as Ritsubi Backup Alert with :warning:."
echo "Fallback: Postgres failed R2 uploads may have postgres-backup-local-fallback artifacts; WordPress keeps /var/backups/ritsubi-wordpress/ for 72h."
echo "Use AWS_REGION=auto / AWS_DEFAULT_REGION=auto for R2 verification."
}
# メイン実行
main() {
echo "Starting maintenance at $(date)"
postgres_maintenance
redis_maintenance
log_rotation
metrics_cleanup
backup_verification
echo "Maintenance completed at $(date)"
}
main "$@"
文書バージョン: 1.0 作成日: 2025年9月17日 定期レビュー: 月次で監視設定とアラート閾値を見直し ��ー**: 月次で監視設定とアラート閾値を見直し ��