diff --git a/app/components/error-boundary.tsx b/app/components/error-boundary.tsx index 5829994..f2b22a7 100644 --- a/app/components/error-boundary.tsx +++ b/app/components/error-boundary.tsx @@ -1,4 +1,5 @@ -import { type ReactElement } from 'react' +import * as Sentry from '@sentry/react' +import { useEffect, type ReactElement } from 'react' import { Link, isRouteErrorResponse, @@ -40,6 +41,10 @@ export const GeneralErrorBoundary = ({ console.error(error) } + useEffect(() => { + Sentry.captureException(error) + }, [error]) + return (
{isRouteErrorResponse(error) diff --git a/app/entry.server.backup.tsx b/app/entry.server.backup.tsx deleted file mode 100644 index a3cddbc..0000000 --- a/app/entry.server.backup.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { PassThrough } from 'node:stream' - -import { createReadableStreamFromReadable } from '@react-router/node' -import { captureException, captureRemixServerException } from '@sentry/remix' -import chalk from 'chalk' -import { isbot } from 'isbot' -import { renderToPipeableStream } from 'react-dom/server' -import { - ServerRouter, - type ActionFunctionArgs, - type HandleDocumentRequestFunction, - type LoaderFunctionArgs, -} from 'react-router' -import { env, forceEnvValidation } from '#app/utils/env.server.ts' -import { NonceProvider } from './utils/nonce-provider.tsx' - -forceEnvValidation() - -const streamTimeout = 5_000 - -type DocRequestArgs = Parameters -export default function handleRequest( - ...[ - request, - responseStatusCode, - responseHeaders, - reactRouterContext, - loadContext, - ]: DocRequestArgs -) { - if (env.NODE_ENV === 'production' && env.SENTRY_DSN) { - responseHeaders.append('Document-Policy', 'js-profiling') - } - - const nonce = String(loadContext.cspNonce) - const callbackName = isbot(request.headers.get('user-agent')) - ? 'onAllReady' - : 'onShellReady' - - return new Promise((resolve, reject) => { - let didError = false - - const { pipe, abort } = renderToPipeableStream( - - - , - - { - [callbackName]: () => { - const body = new PassThrough() - const stream = createReadableStreamFromReadable(body) - responseHeaders.set('Content-Type', 'text/html') - - resolve( - new Response(stream, { - headers: responseHeaders, - status: didError ? 500 : responseStatusCode, - }), - ) - pipe(body) - }, - onShellError: (err) => { - reject(err) - }, - onError: () => { - didError = true - }, - nonce, - }, - ) - - setTimeout(abort, streamTimeout + 5000) - }) -} - -export function handleError( - error: unknown, - { request }: LoaderFunctionArgs | ActionFunctionArgs, -) { - if (request.signal.aborted) { - return - } - - if (error instanceof Error) { - console.error(chalk.red(error.stack)) - void captureRemixServerException(error, 'remix.server', request) - } else { - console.error(chalk.red(error)) - captureException(error) - } -} diff --git a/app/entry.server.tsx b/app/entry.server.tsx index d3820f2..6b37d9c 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -1,7 +1,7 @@ import { PassThrough } from 'node:stream' import { createReadableStreamFromReadable } from '@react-router/node' -import { captureException, captureRemixServerException } from '@sentry/remix' +import * as Sentry from '@sentry/node' import chalk from 'chalk' import { isbot } from 'isbot' import { renderToPipeableStream } from 'react-dom/server' @@ -87,9 +87,9 @@ export function handleError( if (error instanceof Error) { console.error(chalk.red(error.stack)) - void captureRemixServerException(error, 'remix.server', request) + Sentry.captureException(error) } else { console.error(chalk.red(error)) - captureException(error) + Sentry.captureException(error) } } diff --git a/app/root.tsx b/app/root.tsx index 7c5b098..087c773 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,4 +1,4 @@ -import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix' +import * as Sentry from '@sentry/react' import clsx from 'clsx' import { data, @@ -212,7 +212,7 @@ function AppWithProviders() { ) } -export default withSentry(AppWithProviders) +export default Sentry.wrapUseRoutesV7(AppWithProviders) export function Layout({ children }: { children: React.ReactNode }) { const data = useRouteLoaderData('root') @@ -226,9 +226,4 @@ export function Layout({ children }: { children: React.ReactNode }) { ) } -export function ErrorBoundary() { - const error = useRouteError() - captureRemixErrorBoundaryError(error) - - return -} +export const ErrorBoundary = GeneralErrorBoundary diff --git a/app/utils/monitoring.client.ts b/app/utils/monitoring.client.ts index 80a8cd5..fa0844c 100644 --- a/app/utils/monitoring.client.ts +++ b/app/utils/monitoring.client.ts @@ -1,14 +1,14 @@ -import { - init as sentryInit, - browserTracingIntegration, - replayIntegration, - browserProfilingIntegration, -} from '@sentry/remix' +import * as Sentry from '@sentry/react' import { useEffect } from 'react' -import { useLocation, useMatches } from 'react-router' +import { + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, +} from 'react-router' export function init() { - sentryInit({ + Sentry.init({ dsn: window.ENV.SENTRY_DSN, tracesSampleRate: 1, profilesSampleRate: 1, @@ -16,13 +16,15 @@ export function init() { replaysOnErrorSampleRate: 1, integrations: [ - browserTracingIntegration({ + Sentry.replayIntegration(), + Sentry.browserProfilingIntegration(), + Sentry.reactRouterV7BrowserTracingIntegration({ useEffect, useLocation, - useMatches, + useNavigationType, + createRoutesFromChildren, + matchRoutes, }), - replayIntegration(), - browserProfilingIntegration(), ], }) } diff --git a/package.json b/package.json index 7398798..50bfe3e 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,9 @@ "@react-router/remix-config-routes-adapter": "0.0.0-nightly-bf7ecb711-20240911", "@react-router/remix-routes-option-adapter": "7.1.1", "@react-router/serve": "^7.0.0", + "@sentry/node": "8.50.0", "@sentry/profiling-node": "8.47.0", + "@sentry/react": "8.50.0", "@sentry/remix": "8.47.0", "@sentry/vite-plugin": "2.22.7", "@testing-library/dom": "10.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48499bb..fd0a8e3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,9 +91,15 @@ importers: '@react-router/serve': specifier: ^7.0.0 version: 7.1.1(react-router@7.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(typescript@5.7.2) + '@sentry/node': + specifier: 8.50.0 + version: 8.50.0 '@sentry/profiling-node': specifier: 8.47.0 version: 8.47.0 + '@sentry/react': + specifier: 8.50.0 + version: 8.50.0(react@19.0.0) '@sentry/remix': specifier: 8.47.0 version: 8.47.0(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.28.0)(@remix-run/node@2.15.2(typescript@5.7.2))(@remix-run/react@2.15.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(react@19.0.0) @@ -2464,18 +2470,34 @@ packages: resolution: {integrity: sha512-vOXzYzHTKkahTLDzWWIA4EiVCQ+Gk+7xGWUlNcR2ZiEPBqYZVb5MjsUozAcc7syrSUy6WicyFjcomZ3rlCVQhg==} engines: {node: '>=14.18'} + '@sentry-internal/browser-utils@8.50.0': + resolution: {integrity: sha512-hZm6ngWTEzZhaMHpLIKB4wWp0Od1MdCZdvR5FRdIThUMLa1P8rXeolovTRfOE81NE755EiwJHzj4O7rq3EjA+A==} + engines: {node: '>=14.18'} + '@sentry-internal/feedback@8.47.0': resolution: {integrity: sha512-IAiIemTQIalxAOYhUENs9bZ8pMNgJnX3uQSuY7v0gknEqClOGpGkG04X/cxCmtJUj1acZ9ShTGDxoh55a+ggAQ==} engines: {node: '>=14.18'} + '@sentry-internal/feedback@8.50.0': + resolution: {integrity: sha512-79WlvSJYCXL/D0PBC8AIT4JbyS44AE3h6lP05IESXMqzTZl3KeSqCx317rwJw1KaxzeFd/JQwkFq95jaKAcLhg==} + engines: {node: '>=14.18'} + '@sentry-internal/replay-canvas@8.47.0': resolution: {integrity: sha512-M4W9UGouEeELbGbP3QsXLDVtGiQSZoWJlKwqMWyqdQgZuLoKw0S33+60t6teLVMhuQZR0UI9VJTF5coiXysnnA==} engines: {node: '>=14.18'} + '@sentry-internal/replay-canvas@8.50.0': + resolution: {integrity: sha512-Hv1bBaPpe62xFPLpuaUxVBUHd/Ed9bnGndeqN4hueeEGDT9T6NyVokgm35O5xE9/op6Yodm/3NfUkEg8oE++Aw==} + engines: {node: '>=14.18'} + '@sentry-internal/replay@8.47.0': resolution: {integrity: sha512-G/S40ZBORj0HSMLw/uVC6YDEPN/dqVk901vf4VYfml686DEhJrZesfAfp5SydJumQ0NKZQrdtvny+BWnlI5H1w==} engines: {node: '>=14.18'} + '@sentry-internal/replay@8.50.0': + resolution: {integrity: sha512-mhRPujzO6n+mb6ZR+wQNkSpjqIqDriR0hZEvdzHQdyXu9zVdCHUJ3sINkzpT1XwiypQVCEfxB6Oh9y/NmcQfGg==} + engines: {node: '>=14.18'} + '@sentry/babel-plugin-component-annotate@2.22.7': resolution: {integrity: sha512-aa7XKgZMVl6l04NY+3X7BP7yvQ/s8scn8KzQfTLrGRarziTlMGrsCOBQtCNWXOPEbtxAIHpZ9dsrAn5EJSivOQ==} engines: {node: '>= 14'} @@ -2484,6 +2506,10 @@ packages: resolution: {integrity: sha512-K6BzHisykmbFy/wORtGyfsAlw7ShevLALzu3ReZZZ18dVubO1bjSNjkZQU9MJD5Jcb9oLwkq89n3N9XIBfvdRA==} engines: {node: '>=14.18'} + '@sentry/browser@8.50.0': + resolution: {integrity: sha512-aGJSpuKiHVKkLvd1VklJSZ2oCsl4wcKUVxKIa8dhJC8KjDY0vREQCywrlWuS5KYP0xFy4k28pg6PPR3HKkUlNw==} + engines: {node: '>=14.18'} + '@sentry/bundler-plugin-core@2.22.7': resolution: {integrity: sha512-ouQh5sqcB8vsJ8yTTe0rf+iaUkwmeUlGNFi35IkCFUQlWJ22qS6OfvNjOqFI19e6eGUXks0c/2ieFC4+9wJ+1g==} engines: {node: '>= 14'} @@ -2538,10 +2564,18 @@ packages: resolution: {integrity: sha512-iSEJZMe3DOcqBFZQAqgA3NB2lCWBc4Gv5x/SCri/TVg96wAlss4VrUunSI2Mp0J4jJ5nJcJ2ChqHSBAU48k3FA==} engines: {node: '>=14.18'} + '@sentry/core@8.50.0': + resolution: {integrity: sha512-q71m8Ha9YGwqn4Gd7sWvcFTRgbHXxEfU4QeIFtwMBpwHfq2Q+9koiF8DOoOHqIEOsnlvZWRQgGggIOdHzajnVw==} + engines: {node: '>=14.18'} + '@sentry/node@8.47.0': resolution: {integrity: sha512-tMzeU3KkmDi2OVvSu+Ah5pwoi7srsSyc1DovBbRQU96RFf/lOFzGe9JERa1MyDUqqLH95NqnPTNsa4Amb8/Vxg==} engines: {node: '>=14.18'} + '@sentry/node@8.50.0': + resolution: {integrity: sha512-I9eGIdcoWKVy4O8a1f2t0jGVTdN1z9McxbGW8aWwDE5Vd9gpuNjFh9qGapmBEPzysWBX8rjsemDdSa3TcijJMw==} + engines: {node: '>=14.18'} + '@sentry/opentelemetry@8.47.0': resolution: {integrity: sha512-wunyBIUPeY6Kx3SFhOQqOPs+hyRADO5bztpo8aZ3N3xfzhefSTOdrgUroKvHx1DvoQO6MAlykcuUFps3yfaqmg==} engines: {node: '>=14.18'} @@ -2552,6 +2586,16 @@ packages: '@opentelemetry/sdk-trace-base': ^1.29.0 '@opentelemetry/semantic-conventions': ^1.28.0 + '@sentry/opentelemetry@8.50.0': + resolution: {integrity: sha512-uAZjAMPAulFHL88ThK2k+XPx2QzvZ/I7e7sP1In28Tb/yLH0mi+51AUH+zcnLELIPC86m1aDYl8uwYcP6tV4dA==} + engines: {node: '>=14.18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/core': ^1.29.0 + '@opentelemetry/instrumentation': ^0.56.0 + '@opentelemetry/sdk-trace-base': ^1.29.0 + '@opentelemetry/semantic-conventions': ^1.28.0 + '@sentry/profiling-node@8.47.0': resolution: {integrity: sha512-YRta5UpsIOs3F/Px0XCWWybouZ62/5I+y/3Qh1u1x0PqBI42CReQTzRPGxwleXonEv5PT+P9WQoYjxRKGQCx9w==} engines: {node: '>=14.18'} @@ -2563,6 +2607,12 @@ packages: peerDependencies: react: ^16.14.0 || 17.x || 18.x || 19.x + '@sentry/react@8.50.0': + resolution: {integrity: sha512-qkDW5dieROPDf0uk1usXib/SLZTEveN5jvKgBFd+HKWz5JNu+M7L53t9KdZ7ryn4T68utI/LWs4qR3QhmXzUbQ==} + engines: {node: '>=14.18'} + peerDependencies: + react: ^16.14.0 || 17.x || 18.x || 19.x + '@sentry/remix@8.47.0': resolution: {integrity: sha512-815EEP4tNN7//RlXlA9Qs0/jHNx52iOVB3OuiEgpVx9IP39atz57EoHhjAI6QWQ3zmC13H626sWemP2c3VLLsA==} engines: {node: '>=14.18'} @@ -9147,20 +9197,38 @@ snapshots: dependencies: '@sentry/core': 8.47.0 + '@sentry-internal/browser-utils@8.50.0': + dependencies: + '@sentry/core': 8.50.0 + '@sentry-internal/feedback@8.47.0': dependencies: '@sentry/core': 8.47.0 + '@sentry-internal/feedback@8.50.0': + dependencies: + '@sentry/core': 8.50.0 + '@sentry-internal/replay-canvas@8.47.0': dependencies: '@sentry-internal/replay': 8.47.0 '@sentry/core': 8.47.0 + '@sentry-internal/replay-canvas@8.50.0': + dependencies: + '@sentry-internal/replay': 8.50.0 + '@sentry/core': 8.50.0 + '@sentry-internal/replay@8.47.0': dependencies: '@sentry-internal/browser-utils': 8.47.0 '@sentry/core': 8.47.0 + '@sentry-internal/replay@8.50.0': + dependencies: + '@sentry-internal/browser-utils': 8.50.0 + '@sentry/core': 8.50.0 + '@sentry/babel-plugin-component-annotate@2.22.7': {} '@sentry/browser@8.47.0': @@ -9171,6 +9239,14 @@ snapshots: '@sentry-internal/replay-canvas': 8.47.0 '@sentry/core': 8.47.0 + '@sentry/browser@8.50.0': + dependencies: + '@sentry-internal/browser-utils': 8.50.0 + '@sentry-internal/feedback': 8.50.0 + '@sentry-internal/replay': 8.50.0 + '@sentry-internal/replay-canvas': 8.50.0 + '@sentry/core': 8.50.0 + '@sentry/bundler-plugin-core@2.22.7': dependencies: '@babel/core': 7.26.0 @@ -9227,6 +9303,8 @@ snapshots: '@sentry/core@8.47.0': {} + '@sentry/core@8.50.0': {} + '@sentry/node@8.47.0': dependencies: '@opentelemetry/api': 1.9.0 @@ -9267,6 +9345,46 @@ snapshots: transitivePeerDependencies: - supports-color + '@sentry/node@8.50.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-amqplib': 0.45.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-connect': 0.42.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-dataloader': 0.15.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-express': 0.46.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fastify': 0.43.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fs': 0.18.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-generic-pool': 0.42.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-graphql': 0.46.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-hapi': 0.44.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-ioredis': 0.46.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-kafkajs': 0.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-knex': 0.43.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-koa': 0.46.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-lru-memoizer': 0.43.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongodb': 0.50.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongoose': 0.45.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql': 0.44.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql2': 0.44.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-nestjs-core': 0.43.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-pg': 0.49.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-redis-4': 0.45.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-tedious': 0.17.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-undici': 0.9.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + '@prisma/instrumentation': 5.22.0 + '@sentry/core': 8.50.0 + '@sentry/opentelemetry': 8.50.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.28.0) + import-in-the-middle: 1.11.2 + transitivePeerDependencies: + - supports-color + '@sentry/opentelemetry@8.47.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.28.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -9276,6 +9394,15 @@ snapshots: '@opentelemetry/semantic-conventions': 1.28.0 '@sentry/core': 8.47.0 + '@sentry/opentelemetry@8.50.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.28.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + '@sentry/core': 8.50.0 + '@sentry/profiling-node@8.47.0': dependencies: '@sentry/core': 8.47.0 @@ -9292,6 +9419,13 @@ snapshots: hoist-non-react-statics: 3.3.2 react: 19.0.0 + '@sentry/react@8.50.0(react@19.0.0)': + dependencies: + '@sentry/browser': 8.50.0 + '@sentry/core': 8.50.0 + hoist-non-react-statics: 3.3.2 + react: 19.0.0 + '@sentry/remix@8.47.0(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.28.0)(@remix-run/node@2.15.2(typescript@5.7.2))(@remix-run/react@2.15.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(react@19.0.0)': dependencies: '@opentelemetry/api': 1.9.0 diff --git a/server-monitoring.js b/server-monitoring.js index 06bc8b7..dc2ff9b 100644 --- a/server-monitoring.js +++ b/server-monitoring.js @@ -1,12 +1,12 @@ +import * as Sentry from '@sentry/node' import { nodeProfilingIntegration } from '@sentry/profiling-node' -import { init as sentryInit } from '@sentry/remix' export function init() { - sentryInit({ + Sentry.init({ dsn: process.env.SENTRY_DSN, tracesSampleRate: 1, profilesSampleRate: 1, - autoInstrumentRemix: true, + integrations: [nodeProfilingIntegration()], }) }