| @@ -2,25 +2,16 @@ import * as Sentry from "@sentry/cloudflare"; · 1 unmodified line |
| 2 | 2 | import handler from "@tanstack/react-start/server-entry"; |
| 3 | 3 | import { instrument, type ResolveConfigFn } from "@microlabs/otel-cf-workers"; |
| 4 | 4 | |
| 5 | | −// --------------------------------------------------------------------------- |
| 6 | | −// DO export |
| 7 | | −// --------------------------------------------------------------------------- |
| 8 | | −// Telemetry for the DO is handled from inside the DO via a self-contained |
| 9 | | −// WebSdk layer (see `services/telemetry.ts#DoTelemetryLive`). We deliberately |
| 10 | | −// do NOT wrap the DO class with `instrumentDO` from `otel-cf-workers` — it |
| 11 | | −// breaks `this` binding on `WorkerTransport`'s streaming primitives and the |
| 12 | | −// whole MCP session 500s with "Illegal invocation" DOMExceptions. |
| 13 | | −// |
| 14 | | −// Sentry's DO wrapper has the same failure mode in our setup, so DO errors |
| 15 | | −// are captured manually via `Sentry.captureException` inside the DO's catch |
| 16 | | −// blocks rather than by class wrapping. |
| 17 | | −// --------------------------------------------------------------------------- |
| 18 | | − |
| 19 | | −export { McpSessionDO } from "./mcp-session"; |
| 5 | +import { McpSessionDO as McpSessionDOBase } from "./mcp-session"; |
| 20 | 6 | |
| 21 | 7 | // --------------------------------------------------------------------------- |
| 22 | 8 | // OTEL config for the main fetch handler — `otel-cf-workers` owns the global |
| 23 | 9 | // TracerProvider and flushes via `ctx.waitUntil` at the end of each request. |
| 10 | +// The DO runs in a separate isolate and uses its own self-contained WebSdk |
| 11 | +// (see `services/telemetry.ts#DoTelemetryLive`); `instrumentDO` from |
| 12 | +// otel-cf-workers is NOT used because it breaks `this` binding on |
| 13 | +// `WorkerTransport`'s stream primitives and crashes every MCP request with |
| 14 | +// DOMException "Illegal invocation". |
| 24 | 15 | // --------------------------------------------------------------------------- |
| 25 | 16 | |
| 26 | 17 | type OtelEnv = { |
| @@ -39,19 +30,39 @@ const resolveOtelConfig: ResolveConfigFn<OtelEnv> = (env) => ({ · 12 unmodified lines |
| 39 | 30 | }, |
| 40 | 31 | }); |
| 41 | 32 | |
| 42 | | −const instrumentedHandler = instrument({ fetch: handler.fetch }, resolveOtelConfig); |
| 33 | +// otel-cf-workers owns the global TracerProvider. Sentry's OTEL compat shim |
| 34 | +// registers a ProxyTracerProvider of its own, which prevents otel-cf-workers |
| 35 | +// from finding its WorkerTracer and breaks the whole request path with |
| 36 | +// "global tracer is not of type WorkerTracer". |
| 37 | +const sentryOptions = (env: Env) => ({ |
| 38 | + dsn: (env as unknown as { SENTRY_DSN?: string }).SENTRY_DSN, |
| 39 | + tracesSampleRate: 0, |
| 40 | + enableLogs: true, |
| 41 | + sendDefaultPii: true, |
| 42 | + skipOpenTelemetrySetup: true, |
| 43 | + // Our DO methods (init/handleRequest/alarm) live on the prototype, not on |
| 44 | + // the instance. Sentry's default DO auto-wrap only visits own properties, |
| 45 | + // which misses prototype methods — so errors thrown inside init() never |
| 46 | + // reach Sentry. This flag opts into prototype-method instrumentation. |
| 47 | + instrumentPrototypeMethods: true, |
| 48 | +}); |
| 43 | 49 | |
| 44 | | −export default Sentry.withSentry( |
| 45 | | − (env: Record<string, string>) => ({ |
| 46 | | − dsn: env.SENTRY_DSN, |
| 47 | | − tracesSampleRate: 0, |
| 48 | | − enableLogs: true, |
| 49 | | − sendDefaultPii: true, |
| 50 | | − // otel-cf-workers owns the global TracerProvider. Sentry's OTEL compat |
| 51 | | − // shim registers a ProxyTracerProvider of its own, which prevents |
| 52 | | − // otel-cf-workers from finding its WorkerTracer and breaks the whole |
| 53 | | − // request path with "global tracer is not of type WorkerTracer". |
| 54 | | − skipOpenTelemetrySetup: true, |
| 55 | | − }), |
| 56 | | − instrumentedHandler, |
| 50 | +// --------------------------------------------------------------------------- |
| 51 | +// Durable Object — wrapped with Sentry so DO errors land in Sentry (inits the |
| 52 | +// client inside the DO isolate, which plain `Sentry.captureException` cannot |
| 53 | +// do on its own). We deliberately do NOT wrap with otel-cf-workers' |
| 54 | +// `instrumentDO` (see note above). |
| 55 | +// --------------------------------------------------------------------------- |
| 56 | + |
| 57 | +export const McpSessionDO = Sentry.instrumentDurableObjectWithSentry( |
| 58 | + sentryOptions, |
| 59 | + McpSessionDOBase, |
| 57 | 60 | ); |
| 61 | + |
| 62 | +// --------------------------------------------------------------------------- |
| 63 | +// Worker fetch handler |
| 64 | +// --------------------------------------------------------------------------- |
| 65 | + |
| 66 | +const instrumentedHandler = instrument({ fetch: handler.fetch }, resolveOtelConfig); |
| 67 | + |
| 68 | +export default Sentry.withSentry(sentryOptions, instrumentedHandler); |