| @@ -2,25 +2,16 @@ import * as Sentry from "@sentry/cloudflare"; · 1 unmodified line |
| 2 | import handler from "@tanstack/react-start/server-entry"; | 2 | import handler from "@tanstack/react-start/server-entry"; |
| 3 | import { instrument, type ResolveConfigFn } from "@microlabs/otel-cf-workers"; | 3 | import { instrument, type ResolveConfigFn } from "@microlabs/otel-cf-workers"; |
| 4 | | 4 | |
| 5 | // --------------------------------------------------------------------------- | 5 | import { McpSessionDO as McpSessionDOBase } from "./mcp-session"; |
| 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"; | | |
| 20 | | 6 | |
| 21 | // --------------------------------------------------------------------------- | 7 | // --------------------------------------------------------------------------- |
| 22 | // OTEL config for the main fetch handler — `otel-cf-workers` owns the global | 8 | // OTEL config for the main fetch handler — `otel-cf-workers` owns the global |
| 23 | // TracerProvider and flushes via `ctx.waitUntil` at the end of each request. | 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 | type OtelEnv = { | 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( | 50 | // --------------------------------------------------------------------------- |
| 45 | (env: Record<string, string>) => ({ | 51 | // Durable Object — wrapped with Sentry so DO errors land in Sentry (inits the |
| 46 | dsn: env.SENTRY_DSN, | 52 | // client inside the DO isolate, which plain `Sentry.captureException` cannot |
| 47 | tracesSampleRate: 0, | 53 | // do on its own). We deliberately do NOT wrap with otel-cf-workers' |
| 48 | enableLogs: true, | 54 | // `instrumentDO` (see note above). |
| 49 | sendDefaultPii: true, | 55 | // --------------------------------------------------------------------------- |
| 50 | // otel-cf-workers owns the global TracerProvider. Sentry's OTEL compat | 56 | |
| 51 | // shim registers a ProxyTracerProvider of its own, which prevents | 57 | export const McpSessionDO = Sentry.instrumentDurableObjectWithSentry( |
| 52 | // otel-cf-workers from finding its WorkerTracer and breaks the whole | 58 | sentryOptions, |
| 53 | // request path with "global tracer is not of type WorkerTracer". | 59 | McpSessionDOBase, |
| 54 | skipOpenTelemetrySetup: true, | | |
| 55 | }), | | |
| 56 | instrumentedHandler, | | |
| 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); |