History

feat(cloud): wire Sentry error capture for HTTP + MCP DO

HTTP path: TanStack Start + our Effect error handlers swallow throws and return 500 responses, so `Sentry.withSentry` (which only sees uncaught exceptions) never captures them. Adds explicit `Sentry.captureException` in the places that turn errors into 500s: - `toErrorServerResponse` / `toErrorResponse` for the Effect API paths - `handleMcpRequest_POST`'s catch for the MCP proxy path DO path: wraps McpSessionDO with `Sentry.instrumentDurableObjectWithSentry`. Unlike otel-cf-workers' `instrumentDO` (which broke `this` binding on WorkerTransport's stream primitives), Sentry's wrapper uses `Reflect.apply` and is safe. Sets `instrumentPrototypeMethods: true` because the DO's `init` / `handleRequest` live on the prototype — the default auto-wrap only visits own properties, which misses them, and errors thrown inside `init()` never reach Sentry without this flag. Verified end-to-end: debug smoke tests captured both an explicit `captureException` call (HTTP path) and a DO init() throw, both landing in Sentry issues on the node-cloudflare-workers project. Debug endpoints removed before merge.

Rhys Sullivan committed 7 months ago

Commit ef5eb1b1 parent c0e64f93 files · +48 −30Browse files

Showing the changes to apps/cloud/src/api (1 of 3 files). Show all

Unified · Split

apps/cloud/src/api/error-response.ts+6−1
@@ -1,3 +1,4 @@
1+import * as Sentry from "@sentry/cloudflare";
12 import { Data } from "effect";
23 import { HttpServerResponse } from "@effect/platform";
34
@@ -20,12 +21,16 @@ export const isServerError = (error: unknown): boolean => toHttpResponseError(er · 16 unmodified lines
2021
2122 export const toErrorResponse = (error: unknown): Response => {
2223 const mapped = toHttpResponseError(error);
24+ if (mapped.status >= 500) Sentry.captureException(error);
2325 return Response.json({ error: mapped.message, code: mapped.code }, { status: mapped.status });
2426 };
2527
2628 export const toErrorServerResponse = (error: unknown): HttpServerResponse.HttpServerResponse => {
27− console.error("[api] toErrorServerResponse error:", error instanceof Error ? error.stack : error);
2829 const mapped = toHttpResponseError(error);
30+ if (mapped.status >= 500) {
31+ console.error("[api] toErrorServerResponse error:", error instanceof Error ? error.stack : error);
32+ Sentry.captureException(error);
33+ }
2934 return HttpServerResponse.unsafeJson(
3035 { error: mapped.message, code: mapped.code },
3136 { status: mapped.status },