Skip to content

Add PostHog exception capture and reporting#94

Merged
willwashburn merged 1 commit intomainfrom
posthog-logging
Mar 18, 2026
Merged

Add PostHog exception capture and reporting#94
willwashburn merged 1 commit intomainfrom
posthog-logging

Conversation

@willwashburn
Copy link
Copy Markdown
Member

@willwashburn willwashburn commented Mar 18, 2026

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.


Open with Devin

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.
Copy link
Copy Markdown
Contributor

@barryonthecape barryonthecape left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 2 additional findings in Devin Review.

Open in Devin Review

}
}
// PostHog expects frames in caller-first order (outermost first).
return frames;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Suggested change
return frames;
return frames.reverse();
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +283 to +299
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;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) to packages/server/src/lib/logger.ts.
  • Report 5xx request errors from app.onError to PostHog using waitUntil when 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.

Comment on lines +329 to +333
export async function captureException(
env: CloudflareBindings,
error: unknown,
options: CaptureExceptionOptions = {},
): Promise<void> {
Comment on lines +282 to +285
// 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, {
Comment on lines +337 to +339
void captureException(env, error, {
properties: { source: 'worker.queue', event_type: (msg.body as { type?: string }).type },
});
Comment on lines +297 to +299
// PostHog expects frames in caller-first order (outermost first).
return frames;
}
@github-actions
Copy link
Copy Markdown

Preview deployed!

Environment URL
API https://pr94-api.relaycast.dev
Health https://pr94-api.relaycast.dev/health
Observer https://pr94-observer.relaycast.dev/observer

This preview shares the staging database and will be cleaned up when the PR is merged or closed.

Run E2E tests

npm run e2e -- https://pr94-api.relaycast.dev --ci

Open observer dashboard

https://pr94-observer.relaycast.dev/observer

@willwashburn willwashburn merged commit 17b8125 into main Mar 18, 2026
8 checks passed
@willwashburn willwashburn deleted the posthog-logging branch March 18, 2026 18:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants