feat: クライアントエラーのGA計測とWorkersエラー観測性を追加 - #83
Conversation
API では補足できないクライアント側エラーと、サーバ側の未捕捉エラーを
観測できるようにする。
フロント (GA4 client_error イベント):
- 画像レンダリング失敗 (<img> の R2 GET 失敗)、画像処理/プレビュー生成失敗、
presigned PUT 失敗、未捕捉例外/未処理reject/チャンクロード失敗を計測
- 本番のみ送信・開発は debugLog・署名URL/handle等をサニタイズ・デデュープ
(lib/analytics.ts)
Workers (Workers Logs):
- app.onError で未捕捉例外を {error:{code:INTERNAL}} 500 に統一しログ記録
- Cron を全ステップ個別 try/catch + per-item 分離、失敗を集約 throw
- アカウント削除の背景 R2 削除の失敗をログ化 (lib/logger.ts)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughフロントエンドにクライアントエラー計測を追加し、画像読み込み失敗、アップロード失敗、起動時のグローバル例外をGA4へ送信するようにした。Workers側では構造化ロギング、未捕捉例外応答、cron継続処理、アカウント削除失敗記録、クォータ減算の分離を追加した。 Changesフロントエンドのクライアントエラー計測
Estimated code review effort: 3 (Moderate) | ~25 minutes Workers側エラーハンドリングとロギング
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Window
participant initAnalytics
participant trackClientError
participant gtag as window.gtag
Window->>initAnalytics: error / unhandledrejection
initAnalytics->>trackClientError: error_kind, context="global"
trackClientError->>trackClientError: サニタイズ・重複抑止
trackClientError->>gtag: client_error イベント送信(本番のみ)
sequenceDiagram
participant Client
participant Hono as OpenAPIHono
participant onError as app.onError
participant logger as logError
Client->>Hono: リクエスト
Hono--xonError: 未捕捉例外
onError->>logger: logError("unhandled", err)
onError-->>Client: JSON {code: INTERNAL} status 500
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
workers/src/cron/cleanup.ts (1)
36-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value汎用
Errorではなく構造化エラーの使用を検討。
runCleanupの集約エラーは通常のErrorとして投げられています。このエラーはHTTPレスポンスには変換されずscheduled内でログ・再throwされるのみなので影響は限定的ですが、リポジトリ全体の一貫性の観点からは構造化エラーが望ましいです。As per coding guidelines,
workers/**/*.{ts,tsx,js,jsx}: "Throw structured errors with status codes instead of generic errors."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@workers/src/cron/cleanup.ts` around lines 36 - 38, `runCleanup` currently throws a generic Error when `failedSteps > 0`; update this to use the repo’s structured error pattern with a status code instead of a plain Error. Keep the same failure condition and message context, but replace the throw in `cleanup` so the scheduled job can propagate a structured error consistently with the `workers/**/*` guideline and existing error-handling conventions.Source: Coding guidelines
frontend/src/pages/PhotoDetailPage.tsx (1)
175-196: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value任意: 画像読み込み失敗時にフォールバックUIへ切り替える仕組みがない
onErrorはGA4計測のみで、失敗後もUIは壊れた<img>のまま(view_url/thumb_urlが存在する限り、200-204行目の「画像を読み込めません」表示には遷移しない)。計測自体は妥当だが、必要であればonError内で state を更新しフォールバック表示に切り替えることも検討の余地あり。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/PhotoDetailPage.tsx` around lines 175 - 196, `PhotoDetailPage` の画像 `onError` は計測のみで、読み込み失敗後も `photo.thumb_url` / `photo.view_url` がある限り壊れた `<img>` が表示され続けます。`onImageError` か `PhotoDetailPage` 内の表示ロジックでエラー状態を state 管理し、`thumb`/`original` の失敗時に「画像を読み込めません」系のフォールバックUIへ切り替えるようにしてください。`onImageError`、`viewLoaded`、画像表示の条件分岐を目印に修正してください。frontend/src/pages/send/UploadingPage.tsx (1)
473-478: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win任意: R2直PUTにタイムアウト設定がない
presigned URLへの直PUTに
signalが指定されておらず、失敗しない限りブラウザ既定のタイムアウト(Chromeで約300秒など)までハングし得ます。モバイル回線での長時間停止を避けるため、AbortSignal.timeout()の付与を検討してください。ただし画像サイズ(最大20MB)を考慮し、極端に短い値は誤検知の原因になるため妥当な猶予(例: 60〜120秒)を設定するのが望ましいです。♻️ 提案: フェッチにタイムアウトを追加
try { res = await fetch(url, { method: "PUT", body: blob, headers: { "Content-Type": "image/jpeg" }, + signal: AbortSignal.timeout(120_000), });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/send/UploadingPage.tsx` around lines 473 - 478, The direct R2 PUT in UploadingPage’s fetch call does not set a timeout, so the upload can hang until the browser default kicks in. Update the upload logic around the existing fetch(url, { method: "PUT", body: blob, headers: { "Content-Type": "image/jpeg" } }) call to pass a reasonable AbortSignal.timeout via signal, using a conservative duration that fits up to 20MB images (for example, 60–120 seconds). Keep the timeout handling localized to the upload path so failures abort cleanly and can be surfaced through the existing res handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/lib/analytics.ts`:
- Around line 130-152: The GA4 config is currently sending the full default page
URL, including query parameters, so update the gtag initialization to override
page_location with a value built from window.location.origin plus
window.location.pathname and exclude the query string. Apply this in the
gtag('config', 'G-BE16TKNVZ5') setup used for client analytics, keeping the
change focused on the page_location/page_path handling so signed or access-key
URLs are not reported.
In `@frontend/src/pages/send/UploadingPage.tsx`:
- Around line 470-495: `putBlob` の `trackClientError` 呼び出しで、`upload_put` の重複抑止が
`original` と `thumb` を同一扱いにしてしまっています。`trackClientError` の dedupe key に
`target`(必要なら `http_status` も)を含めるよう、`putBlob` 内の fetch 失敗時と `!res.ok`
時のイベント送信を修正してください。
In `@workers/src/cron/cleanup.ts`:
- Around line 99-114: The cleanup flow in cleanupExpiredPhotos is making two
separate DB writes after the R2 deletes, which can leave storage usage and photo
rows out of sync on partial failure. Update the subtractStorageUsage and DELETE
FROM photos steps to run together through env.DB.batch() so they are applied
atomically, while keeping the existing error handling and logging around the
cleanup loop.
---
Nitpick comments:
In `@frontend/src/pages/PhotoDetailPage.tsx`:
- Around line 175-196: `PhotoDetailPage` の画像 `onError` は計測のみで、読み込み失敗後も
`photo.thumb_url` / `photo.view_url` がある限り壊れた `<img>` が表示され続けます。`onImageError` か
`PhotoDetailPage` 内の表示ロジックでエラー状態を state 管理し、`thumb`/`original`
の失敗時に「画像を読み込めません」系のフォールバックUIへ切り替えるようにしてください。`onImageError`、`viewLoaded`、画像表示の条件分岐を目印に修正してください。
In `@frontend/src/pages/send/UploadingPage.tsx`:
- Around line 473-478: The direct R2 PUT in UploadingPage’s fetch call does not
set a timeout, so the upload can hang until the browser default kicks in. Update
the upload logic around the existing fetch(url, { method: "PUT", body: blob,
headers: { "Content-Type": "image/jpeg" } }) call to pass a reasonable
AbortSignal.timeout via signal, using a conservative duration that fits up to
20MB images (for example, 60–120 seconds). Keep the timeout handling localized
to the upload path so failures abort cleanly and can be surfaced through the
existing res handling.
In `@workers/src/cron/cleanup.ts`:
- Around line 36-38: `runCleanup` currently throws a generic Error when
`failedSteps > 0`; update this to use the repo’s structured error pattern with a
status code instead of a plain Error. Keep the same failure condition and
message context, but replace the throw in `cleanup` so the scheduled job can
propagate a structured error consistently with the `workers/**/*` guideline and
existing error-handling conventions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f382dfe9-4ed7-4eb5-ab48-612881218add
📒 Files selected for processing (15)
docs/architecture.mdfrontend/index.htmlfrontend/src/lib/analytics.tsfrontend/src/main.tsxfrontend/src/pages/DashboardPage.tsxfrontend/src/pages/GalleryPage.tsxfrontend/src/pages/PhotoDetailPage.tsxfrontend/src/pages/send/DonePage.tsxfrontend/src/pages/send/LandingPage.tsxfrontend/src/pages/send/UploadPage.tsxfrontend/src/pages/send/UploadingPage.tsxworkers/src/cron/cleanup.tsworkers/src/index.tsworkers/src/lib/logger.tsworkers/src/routes/auth.ts
- index.html: gtag config に page_location=origin+pathname を設定し /send/:handle?k=KEY のアクセスキーが GA4 page_view に載るのを防止 - analytics.ts: client_error の dedupe key に target/http_status を追加し upload_put の original/thumb を別カウント - cleanup.ts + quota.ts: cleanupExpiredPhotos の storage_used 減算と photos 削除を DB.batch() で原子化 (subtractStorageUsageStmt を追加) - UploadingPage.tsx: R2 直 PUT に AbortSignal.timeout(120s) を付与 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@coderabbitai レビュー本文の nitpick 3件に対応しました(commit 5fb26d5):
|
|
Tip For best results, initiate chat on the files or code changes.
いずれも妥当な判断だと思います。ありがとうございました! 🐰✨ |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
frontend/src/lib/analytics.ts (1)
130-152: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valuededupeKey の拡張は妥当。
target/http_statusを追加したことで、original/thumbnail のupload_putエラーが正しく別カウントされるようになっています。PR の意図通りです。一点補足として、
sentCountsはページのライフタイム全体で保持される Map で、error_messageがキーに含まれるため、異なるスタックトレース等でメッセージが変化すると distinct キーが増え続けます。DEDUP_LIMITは単一キーの送信回数のみを制限しており、Map 自体のエントリ数上限は無いため、長時間開いたままのタブでは緩やかにメモリが増加します。実害は小さいですが、必要であれば Map のサイズ上限や定期リセットを検討してください。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/lib/analytics.ts` around lines 130 - 152, The dedupeKey expansion in trackClientError is correct, but sentCounts can grow without bound because error_message makes each distinct message create a new Map entry. Update the trackClientError flow to cap or recycle sentCounts entries in a bounded way, such as adding a maximum size or periodic reset/eviction policy around the sentCounts Map, while keeping the existing dedupe behavior for target and http_status.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@frontend/src/lib/analytics.ts`:
- Around line 130-152: The dedupeKey expansion in trackClientError is correct,
but sentCounts can grow without bound because error_message makes each distinct
message create a new Map entry. Update the trackClientError flow to cap or
recycle sentCounts entries in a bounded way, such as adding a maximum size or
periodic reset/eviction policy around the sentCounts Map, while keeping the
existing dedupe behavior for target and http_status.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 7373f328-71fe-4531-b50d-9468bd7a8958
📒 Files selected for processing (5)
frontend/index.htmlfrontend/src/lib/analytics.tsfrontend/src/pages/send/UploadingPage.tsxworkers/src/cron/cleanup.tsworkers/src/lib/quota.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- workers/src/cron/cleanup.ts
- frontend/src/pages/send/UploadingPage.tsx
概要
API (Workers) ログには現れないクライアント側エラーと、サーバ側の未捕捉エラーを観測できるようにする。既存の GA4 (gtag.js) と Workers Logs を使い、追加の外部依存なしで実装。
フロント — GA4
client_errorイベント (frontend/src/lib/analytics.ts新規)<img>6箇所にonError(ギャラリー/詳細/ダッシュボード/送信完了/アバター)window.onerror/unhandledrejection/ 動的 import チャンクロード失敗debugLogのみ・署名付き URL / handle 等をサニタイズ・同一エラーはデデュープWorkers — Workers Logs で観測 (
workers/src/lib/logger.ts新規)app.onErrorで未捕捉例外を{error:{code:"INTERNAL"}}の 500 に統一し構造化ログ記録(従来はプレーンテキスト・ログ無し)Test plan
client_error(error_kind=image_render)が届く?debug=trueでdebugLogに出るのみupload_put、HEIC 等の変換失敗でimage_processingが届く{error:{code:"INTERNAL"}}で返り、Workers Logs に構造化ログが出る補足(運用)
GA4 レポートで
error_kind/context等を分解表示するには、GA4 管理画面「カスタム定義」でイベントスコープのカスタムディメンション登録が必要(DebugView / BigQuery には登録なしでも生値が届く)。🤖 Generated with Claude Code
Summary by CodeRabbit