Add PostHog exception capture and reporting#94
Conversation
Introduce captureException in logger.ts to send $exception events to PostHog Error Tracking. Includes stack parsing (parseStackFrames), exception structures, and CaptureExceptionOptions (properties and distinctId). Wire up captureException in worker.ts: report server errors (5xx) from app.onError using waitUntil when available, and best-effort reporting for queue handler failures. The implementation is non-blocking and safe to fire-and-forget; HTTP errors during telemetry are swallowed to avoid affecting request handling.
barryonthecape
left a comment
There was a problem hiding this comment.
Nice addition—this is a clean, low-risk way to add PostHog exception tracking without impacting request flow. I reviewed logger + worker wiring and didn’t find blockers. Non-blocking follow-up idea: consider validating frame order against PostHog docs with one real captured stack to ensure grouping looks as expected.
| } | ||
| } | ||
| // PostHog expects frames in caller-first order (outermost first). | ||
| return frames; |
There was a problem hiding this comment.
🟡 Stack frames not reversed — sent in wrong order to PostHog
V8 stack traces list frames from innermost (most recent call) to outermost (entry point), but the comment at line 297 correctly notes that PostHog expects frames in caller-first order (outermost first). The parseStackFrames function returns frames in V8's original innermost-first order without reversing them, so stack traces will display with the wrong frame order in PostHog Error Tracking. The fix is to add frames.reverse() before returning.
| return frames; | |
| return frames.reverse(); |
Was this helpful? React with 👍 or 👎 to provide feedback.
| const status = error.status || 500; | ||
| if (status >= 500) { | ||
| const exceptionPromise = captureException(c.env, error, { | ||
| properties: { | ||
| path: c.req.path, | ||
| method: c.req.method, | ||
| status_code: status, | ||
| error_code: error.code ?? 'internal_error', | ||
| request_id: c.get('requestId'), | ||
| }, | ||
| }); | ||
| try { | ||
| c.executionCtx?.waitUntil(exceptionPromise); | ||
| } catch { | ||
| void exceptionPromise; | ||
| } | ||
| } |
There was a problem hiding this comment.
🟡 JSON parse errors falsely reported as 500 exceptions to PostHog
The const status = error.status || 500 declaration was moved above the error.message?.includes('JSON') check. JSON parse errors (e.g. SyntaxError) don't have a status property, so status defaults to 500, which satisfies status >= 500 and fires captureException with status_code: 500. The handler then correctly returns 400 to the client, but the damage is done — the error has already been sent to PostHog Error Tracking as a false server error. In the old code (packages/server/src/worker.ts:282-285 pre-change), status was computed after the JSON check, so these client errors never reached the exception capture path.
Prompt for agents
In packages/server/src/worker.ts, in the app.onError handler (around lines 271-311), move the JSON-error early return (lines 301-303) above the captureException block (lines 283-299), so JSON parse errors return 400 before status is checked and before captureException can fire. Alternatively, compute status before the captureException block and skip captureException if error.message?.includes('JSON'). The key point is to avoid sending JSON parse errors (which are client 400s) to PostHog Error Tracking as 500 exceptions.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Pull request overview
Adds PostHog Error Tracking support to the server by introducing a $exception capture helper and wiring it into the worker’s global error handler and queue consumer.
Changes:
- Add
captureException(plus stack parsing / exception payload shaping) topackages/server/src/lib/logger.ts. - Report 5xx request errors from
app.onErrorto PostHog usingwaitUntilwhen available. - Best-effort exception reporting for webhook queue handler failures.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| packages/server/src/worker.ts | Wires captureException into request error handling and queue failure handling. |
| packages/server/src/lib/logger.ts | Implements $exception payload building and stack frame parsing for PostHog capture endpoint. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| export async function captureException( | ||
| env: CloudflareBindings, | ||
| error: unknown, | ||
| options: CaptureExceptionOptions = {}, | ||
| ): Promise<void> { |
| // Send to PostHog Error Tracking (fire-and-forget via waitUntil when available) | ||
| const status = error.status || 500; | ||
| if (status >= 500) { | ||
| const exceptionPromise = captureException(c.env, error, { |
| void captureException(env, error, { | ||
| properties: { source: 'worker.queue', event_type: (msg.body as { type?: string }).type }, | ||
| }); |
| // PostHog expects frames in caller-first order (outermost first). | ||
| return frames; | ||
| } |
|
Preview deployed!
This preview shares the staging database and will be cleaned up when the PR is merged or closed. Run E2E testsnpm run e2e -- https://pr94-api.relaycast.dev --ciOpen observer dashboard |
Introduce captureException in logger.ts to send $exception events to PostHog Error Tracking. Includes stack parsing (parseStackFrames), exception structures, and CaptureExceptionOptions (properties and distinctId). Wire up captureException in worker.ts: report server errors (5xx) from app.onError using waitUntil when available, and best-effort reporting for queue handler failures. The implementation is non-blocking and safe to fire-and-forget; HTTP errors during telemetry are swallowed to avoid affecting request handling.