From 8d0e1194f21569b8d2c308f73ab92d17a6429b89 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Wed, 28 Feb 2024 09:34:21 +0000 Subject: [PATCH 01/12] feat(remix): Set `formData` as span data. --- .../create-remix-app-express/.eslintrc.cjs | 83 ++++++ .../create-remix-app-express/.gitignore | 6 + .../create-remix-app-express/.npmrc | 2 + .../create-remix-app-express/README.md | 36 +++ .../app/entry.client.tsx | 53 ++++ .../app/entry.server.tsx | 134 ++++++++++ .../create-remix-app-express/app/root.tsx | 80 ++++++ .../app/routes/_index.tsx | 21 ++ .../app/routes/action-formdata.tsx | 21 ++ .../app/routes/client-error.tsx | 13 + .../app/routes/loader-error.tsx | 16 ++ .../app/routes/navigate.tsx | 20 ++ .../app/routes/user.$id.tsx | 3 + .../event-proxy-server.ts | 253 ++++++++++++++++++ .../create-remix-app-express/globals.d.ts | 7 + .../create-remix-app-express/package.json | 54 ++++ .../playwright.config.ts | 65 +++++ .../create-remix-app-express/remix.config.ts | 9 + .../create-remix-app-express/remix.env.d.ts | 2 + .../create-remix-app-express/server.js | 107 ++++++++ .../start-event-proxy.ts | 7 + .../create-remix-app-express/static/test.txt | 1 + .../tests/behaviour-client.test.ts | 236 ++++++++++++++++ .../tests/behaviour-server.test.ts | 37 +++ .../create-remix-app-express/tsconfig.json | 30 +++ .../create-remix-app-v2/package.json | 5 +- .../create-remix-app/package.json | 3 - packages/remix/src/utils/instrumentServer.ts | 39 ++- 28 files changed, 1336 insertions(+), 7 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/.eslintrc.cjs create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/README.md create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/app/entry.client.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/app/entry.server.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/app/root.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/app/routes/_index.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/app/routes/action-formdata.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/app/routes/client-error.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/app/routes/loader-error.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/app/routes/navigate.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/app/routes/user.$id.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/event-proxy-server.ts create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/globals.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/playwright.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/remix.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/remix.env.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/server.js create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/start-event-proxy.ts create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/static/test.txt create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/tests/behaviour-client.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/tests/behaviour-server.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/.eslintrc.cjs b/dev-packages/e2e-tests/test-applications/create-remix-app-express/.eslintrc.cjs new file mode 100644 index 000000000000..6bd1baae98f9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/.eslintrc.cjs @@ -0,0 +1,83 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + +/** @type {import('eslint').Linter.Config} */ +module.exports = { + root: true, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + commonjs: true, + es6: true, + }, + + // Base config + extends: ["eslint:recommended"], + + overrides: [ + // React + { + files: ["**/*.{js,jsx,ts,tsx}"], + plugins: ["react", "jsx-a11y"], + extends: [ + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", + ], + settings: { + react: { + version: "detect", + }, + formComponents: ["Form"], + linkComponents: [ + { name: "Link", linkAttribute: "to" }, + { name: "NavLink", linkAttribute: "to" }, + ], + "import/resolver": { + typescript: {}, + }, + }, + }, + + // Typescript + { + files: ["**/*.{ts,tsx}"], + plugins: ["@typescript-eslint", "import"], + parser: "@typescript-eslint/parser", + settings: { + "import/internal-regex": "^~/", + "import/resolver": { + node: { + extensions: [".ts", ".tsx"], + }, + typescript: { + alwaysTryTypes: true, + }, + }, + }, + extends: [ + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", + ], + }, + + // Node + { + files: [".eslintrc.cjs", "server.js"], + env: { + node: true, + }, + }, + ], +}; diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/.gitignore b/dev-packages/e2e-tests/test-applications/create-remix-app-express/.gitignore new file mode 100644 index 000000000000..3f7bf98da3e1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/.gitignore @@ -0,0 +1,6 @@ +node_modules + +/.cache +/build +/public/build +.env diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/.npmrc b/dev-packages/e2e-tests/test-applications/create-remix-app-express/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/README.md b/dev-packages/e2e-tests/test-applications/create-remix-app-express/README.md new file mode 100644 index 000000000000..4f569a538647 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/README.md @@ -0,0 +1,36 @@ +# Welcome to Remix! + +- [Remix Docs](https://remix.run/docs) + +## Development + +Start the Remix development asset server and the Express server by running: + +```sh +npm run dev +``` + +This starts your app in development mode, which will purge the server require cache when Remix rebuilds assets so you don't need a process manager restarting the express server. + +## Deployment + +First, build your app for production: + +```sh +npm run build +``` + +Then run the app in production mode: + +```sh +npm start +``` + +Now you'll need to pick a host to deploy it to. + +### DIY + +If you're familiar with deploying express applications you should be right at home just make sure to deploy the output of `remix build` + +- `build/` +- `public/build/` diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/app/entry.client.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express/app/entry.client.tsx new file mode 100644 index 000000000000..f43f14cb66e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/app/entry.client.tsx @@ -0,0 +1,53 @@ +/** + * By default, Remix will handle hydrating your app on the client for you. + * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ + * For more information, see https://remix.run/file-conventions/entry.client + */ + +import { RemixBrowser, useLocation, useMatches } from '@remix-run/react'; +import * as Sentry from '@sentry/remix'; +import { StrictMode, startTransition, useEffect } from 'react'; +import { hydrateRoot } from 'react-dom/client'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: window.ENV.SENTRY_DSN, + tunnel: `http://localhost:3031/`, // proxy server + integrations: [ + Sentry.browserTracingIntegration({ + useEffect, + useLocation, + useMatches, + }), + new Sentry.Replay(), + ], + // Performance Monitoring + tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production! + // Session Replay + replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. + replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. +}); + +Sentry.addEventProcessor(event => { + if ( + event.type === 'transaction' && + (event.contexts?.trace?.op === 'pageload' || event.contexts?.trace?.op === 'navigation') + ) { + const eventId = event.event_id; + if (eventId) { + window.recordedTransactions = window.recordedTransactions || []; + window.recordedTransactions.push(eventId); + } + } + + return event; +}); + +startTransition(() => { + hydrateRoot( + document, + + + , + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/app/entry.server.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express/app/entry.server.tsx new file mode 100644 index 000000000000..d594035b2ebe --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/app/entry.server.tsx @@ -0,0 +1,134 @@ +/** + * By default, Remix will handle generating the HTTP Response for you. + * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ + * For more information, see https://remix.run/file-conventions/entry.server + */ + +import { PassThrough } from 'node:stream'; + +import type { AppLoadContext, EntryContext } from '@remix-run/node'; +import { createReadableStreamFromReadable } from '@remix-run/node'; +import { installGlobals } from '@remix-run/node'; +import { RemixServer } from '@remix-run/react'; +import * as Sentry from '@sentry/remix'; +import { isbot } from 'isbot'; +import { renderToPipeableStream } from 'react-dom/server'; + +installGlobals(); + +const ABORT_DELAY = 5_000; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.E2E_TEST_DSN, + // Performance Monitoring + tunnel: 'http://localhost:3031/', // proxy server + tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production! + sendDefaultPii: true, +}); + +export const handleError = Sentry.wrapRemixHandleError; + +export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, + loadContext: AppLoadContext, +) { + return isbot(request.headers.get('user-agent')) + ? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext) + : handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext); +} + +function handleBotRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + , + { + onAllReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set('Content-Type', 'text/html'); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }), + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + }, + ); + + setTimeout(abort, ABORT_DELAY); + }); +} + +function handleBrowserRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + , + { + onShellReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set('Content-Type', 'text/html'); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }), + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + }, + ); + + setTimeout(abort, ABORT_DELAY); + }); +} diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/app/root.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express/app/root.tsx new file mode 100644 index 000000000000..517a37a9d76b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/app/root.tsx @@ -0,0 +1,80 @@ +import { cssBundleHref } from '@remix-run/css-bundle'; +import { LinksFunction, MetaFunction, json } from '@remix-run/node'; +import { + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration, + useLoaderData, + useRouteError, +} from '@remix-run/react'; +import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix'; +import type { SentryMetaArgs } from '@sentry/remix'; + +export const links: LinksFunction = () => [...(cssBundleHref ? [{ rel: 'stylesheet', href: cssBundleHref }] : [])]; + +export const loader = () => { + return json({ + ENV: { + SENTRY_DSN: process.env.E2E_TEST_DSN, + }, + }); +}; + +export const meta = ({ data }: SentryMetaArgs>) => { + return [ + { + env: data.ENV, + }, + { + name: 'sentry-trace', + content: data.sentryTrace, + }, + { + name: 'baggage', + content: data.sentryBaggage, + }, + ]; +}; + +export function ErrorBoundary() { + const error = useRouteError(); + const eventId = captureRemixErrorBoundaryError(error); + + return ( +
+ ErrorBoundary Error + {eventId} +
+ ); +} + +function App() { + const { ENV } = useLoaderData(); + + return ( + + + + +