diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/instrument.mjs b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/instrument.mjs index 5fb6bd039fdb..b52053445456 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/instrument.mjs +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/instrument.mjs @@ -6,6 +6,5 @@ Sentry.init({ environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.E2E_TEST_DSN, tunnel: 'http://localhost:3031/', // proxy server - autoInstrumentRemix: true, // auto instrument Remix integrations: [Sentry.nativeNodeFetchIntegration()], }); diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/instrument.mjs b/dev-packages/e2e-tests/test-applications/create-remix-app-express/instrument.mjs index f2e7d35fab80..0cbfe8ec4e5b 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express/instrument.mjs +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/instrument.mjs @@ -7,7 +7,6 @@ Sentry.init({ dsn: process.env.E2E_TEST_DSN, tunnel: 'http://localhost:3031/', // proxy server sendDefaultPii: true, // Testing the FormData - autoInstrumentRemix: true, // auto instrument Remix captureActionFormDataKeys: { file: true, text: true, diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/instrument.server.cjs b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/instrument.server.cjs index 5b80ca7b8695..6d211cac4592 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/instrument.server.cjs +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/instrument.server.cjs @@ -5,5 +5,4 @@ Sentry.init({ environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.E2E_TEST_DSN, tunnel: 'http://localhost:3031/', // proxy server - autoInstrumentRemix: true, // auto instrument Remix }); diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/instrument.server.cjs b/dev-packages/e2e-tests/test-applications/create-remix-app/instrument.server.cjs index 5b80ca7b8695..6d211cac4592 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app/instrument.server.cjs +++ b/dev-packages/e2e-tests/test-applications/create-remix-app/instrument.server.cjs @@ -5,5 +5,4 @@ Sentry.init({ environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.E2E_TEST_DSN, tunnel: 'http://localhost:3031/', // proxy server - autoInstrumentRemix: true, // auto instrument Remix }); diff --git a/packages/remix/package.json b/packages/remix/package.json index 9987c886454e..00aad3a28a7b 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -52,7 +52,6 @@ "access": "public" }, "dependencies": { - "@opentelemetry/api": "^1.9.0", "@remix-run/router": "1.x", "@sentry/cli": "^2.39.1", "@sentry/core": "8.45.0", @@ -91,15 +90,10 @@ "fix": "eslint . --format stylish --fix", "lint": "eslint . --format stylish", "test": "yarn test:unit", - "test:integration": "run-s test:integration:otel test:integration:legacy", - "test:integration:otel": "export USE_OTEL=1 && run-s test:integration:v1 test:integration:v2", - "test:integration:legacy": "export USE_OTEL=0 && run-s test:integration:v1 test:integration:v2", + "test:integration": "run-s test:integration:v1 test:integration:v2", "test:integration:v1": "run-s test:integration:clean test:integration:prepare test:integration:client test:integration:server", "test:integration:v2": "export REMIX_VERSION=2 && yarn test:integration:v1", - "test:integration:ci": "run-s test:integration:ci:otel test:integration:ci:legacy", - "test:integration:ci:otel": "USE_OTEL=1 yarn test:integration:ci:common", - "test:integration:ci:legacy": "USE_OTEL=0 yarn test:integration:ci:common", - "test:integration:ci:common": "run-s test:integration:clean test:integration:prepare test:integration:client:ci test:integration:server", + "test:integration:ci": "run-s test:integration:clean test:integration:prepare test:integration:client:ci test:integration:server", "test:integration:prepare": "(cd test/integration && yarn install)", "test:integration:clean": "(cd test/integration && rimraf .cache node_modules build)", "test:integration:client": "yarn playwright install-deps && yarn playwright test test/integration/test/client/ --project='chromium'", diff --git a/packages/remix/playwright.config.ts b/packages/remix/playwright.config.ts index 142272a44740..dbe5d2968bec 100644 --- a/packages/remix/playwright.config.ts +++ b/packages/remix/playwright.config.ts @@ -14,7 +14,7 @@ const config: PlaywrightTestConfig = { workers: process.env.CI ? 3 : undefined, webServer: { env: { - NODE_OPTIONS: process.env.USE_OTEL === '1' ? '--require ./instrument.server.cjs' : '', + NODE_OPTIONS: '--require ./instrument.server.cjs', }, command: '(cd test/integration/ && yarn build && yarn start)', port: 3000, diff --git a/packages/remix/src/client/performance.tsx b/packages/remix/src/client/performance.tsx index 32c6226e463e..72cd4413744f 100644 --- a/packages/remix/src/client/performance.tsx +++ b/packages/remix/src/client/performance.tsx @@ -24,19 +24,19 @@ import * as React from 'react'; import { DEBUG_BUILD } from '../utils/debug-build'; import { getFutureFlagsBrowser, readRemixVersionFromLoader } from '../utils/futureFlags'; -export type Params = { +type Params = { readonly [key in Key]: string | undefined; }; -export interface RouteMatch { +interface RouteMatch { params: Params; pathname: string; id: string; handle: unknown; } -export type UseEffect = (cb: () => void, deps: unknown[]) => void; +type UseEffect = (cb: () => void, deps: unknown[]) => void; -export type UseLocation = () => { +type UseLocation = () => { pathname: string; search?: string; hash?: string; @@ -44,7 +44,7 @@ export type UseLocation = () => { key?: unknown; }; -export type UseMatches = () => RouteMatch[] | null; +type UseMatches = () => RouteMatch[] | null; export type RemixBrowserTracingIntegrationOptions = Partial[0]> & { useEffect?: UseEffect; diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index 04b9a3859351..129690c0381f 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -153,8 +153,7 @@ export function getRemixDefaultIntegrations(options: RemixOptions): Integration[ return [ ...getDefaultNodeIntegrations(options as NodeOptions).filter(integration => integration.name !== 'Http'), httpIntegration(), - // eslint-disable-next-line deprecation/deprecation - options.autoInstrumentRemix ? remixIntegration() : undefined, + remixIntegration(), ].filter(int => int) as Integration[]; } @@ -186,7 +185,7 @@ export function init(options: RemixOptions): NodeClient | undefined { const client = nodeInit(options as NodeOptions); - instrumentServer(options); + instrumentServer(); return client; } diff --git a/packages/remix/src/utils/errors.ts b/packages/remix/src/utils/errors.ts index 94c1c8755ad4..ec959705e9e4 100644 --- a/packages/remix/src/utils/errors.ts +++ b/packages/remix/src/utils/errors.ts @@ -1,20 +1,16 @@ -import type { AppData, DataFunctionArgs, EntryContext, HandleDocumentRequestFunction } from '@remix-run/node'; +import type { AppData, DataFunctionArgs } from '@remix-run/node'; import { addExceptionMechanism, captureException, - getClient, handleCallbackErrors, - isPrimitive, logger, objectify, winterCGRequestToRequestData, } from '@sentry/core'; -import type { RequestEventData, Span } from '@sentry/core'; +import type { RequestEventData } from '@sentry/core'; import { DEBUG_BUILD } from './debug-build'; -import type { RemixOptions } from './remixOptions'; -import { storeFormDataKeys } from './utils'; import { extractData, isResponse, isRouteErrorResponse } from './vendor/response'; -import type { DataFunction, RemixRequest } from './vendor/types'; +import type { DataFunction } from './vendor/types'; /** * Captures an exception happened in the Remix server. @@ -76,48 +72,6 @@ export async function captureRemixServerException( return scope; }); } - -/** - * Wraps the original `HandleDocumentRequestFunction` with error handling. - * - * @param origDocumentRequestFunction The original `HandleDocumentRequestFunction`. - * @param requestContext The request context. - * @param isRemixV2 Whether the Remix version is v2 or not. - * - * @returns The wrapped `HandleDocumentRequestFunction`. - */ -export function errorHandleDocumentRequestFunction( - this: unknown, - origDocumentRequestFunction: HandleDocumentRequestFunction, - requestContext: { - request: RemixRequest; - responseStatusCode: number; - responseHeaders: Headers; - context: EntryContext; - loadContext?: Record; - }, - isRemixV2: boolean, -): HandleDocumentRequestFunction { - const { request, responseStatusCode, responseHeaders, context, loadContext } = requestContext; - - return handleCallbackErrors( - () => { - return origDocumentRequestFunction.call(this, request, responseStatusCode, responseHeaders, context, loadContext); - }, - err => { - // This exists to capture the server-side rendering errors on Remix v1 - // On Remix v2, we capture SSR errors at `handleError` - // We also skip primitives here, as we can't dedupe them, and also we don't expect any primitive SSR errors. - if (!isRemixV2 && !isPrimitive(err)) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - captureRemixServerException(err, 'documentRequest', request, isRemixV2); - } - - throw err; - }, - ); -} - /** * Wraps the original `DataFunction` with error handling. * This function also stores the form data keys if the action is being called. @@ -135,26 +89,15 @@ export async function errorHandleDataFunction( origFn: DataFunction, name: string, args: DataFunctionArgs, - isRemixV2: boolean, - span?: Span, ): Promise { return handleCallbackErrors( async () => { - if (name === 'action' && span) { - const options = getClient()?.getOptions() as RemixOptions; - - if (options.sendDefaultPii && options.captureActionFormDataKeys) { - await storeFormDataKeys(args, span); - } - } - return origFn.call(this, args); }, err => { - // On Remix v2, we capture all unexpected errors (except the `Route Error Response`s / Thrown Responses) in `handleError` function. + // We capture all unexpected errors (except the `Route Error Response`s / Thrown Responses) in `handleError` function. // This is both for consistency and also avoid duplicates such as primitives like `string` or `number` being captured twice. - // Remix v1 does not have a `handleError` function, so we capture all errors here. - if (isRemixV2 ? isResponse(err) : true) { + if (isResponse(err)) { // eslint-disable-next-line @typescript-eslint/no-floating-promises captureRemixServerException(err, name, args.request, true); } diff --git a/packages/remix/src/utils/futureFlags.ts b/packages/remix/src/utils/futureFlags.ts index 63c9c5dff2ef..ba1ac47fbe54 100644 --- a/packages/remix/src/utils/futureFlags.ts +++ b/packages/remix/src/utils/futureFlags.ts @@ -2,7 +2,7 @@ import { GLOBAL_OBJ } from '@sentry/core'; import type { FutureConfig, ServerBuild } from './vendor/types'; -export type EnhancedGlobal = typeof GLOBAL_OBJ & { +type EnhancedGlobal = typeof GLOBAL_OBJ & { __remixContext?: { future?: FutureConfig; state?: { @@ -30,17 +30,6 @@ export function getFutureFlagsBrowser(): FutureConfig | undefined { return window.__remixContext.future; } -/** - * Get the future flags from the Remix server build - * - * @param build The Remix server build - * - * @returns The future flags - */ -export function getFutureFlagsServer(build: ServerBuild): FutureConfig | undefined { - return build.future; -} - /** * Learn Remix version from the server build object * V2 Server builds have a non-optional `mode` property diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index 1d67403209bc..fadb094952af 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -1,30 +1,19 @@ -/* eslint-disable max-lines */ -import type { RequestEventData, Span, TransactionSource, WrappedFunction } from '@sentry/core'; +import type { RequestEventData, WrappedFunction } from '@sentry/core'; import { - SEMANTIC_ATTRIBUTE_SENTRY_OP, - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, continueTrace, fill, - getActiveSpan, getClient, - getRootSpan, getTraceData, hasTracingEnabled, isNodeEnv, loadModule, logger, - setHttpStatus, - spanToJSON, - startSpan, winterCGRequestToRequestData, withIsolationScope, } from '@sentry/core'; import { DEBUG_BUILD } from './debug-build'; -import { captureRemixServerException, errorHandleDataFunction, errorHandleDocumentRequestFunction } from './errors'; -import { getFutureFlagsServer, getRemixVersionFromBuild } from './futureFlags'; -import type { RemixOptions } from './remixOptions'; -import { createRoutes, getTransactionName } from './utils'; +import { captureRemixServerException, errorHandleDataFunction } from './errors'; +import { getRemixVersionFromBuild } from './futureFlags'; import { extractData, isDeferredData, isResponse, isRouteErrorResponse, json } from './vendor/response'; import type { AppData, @@ -32,18 +21,12 @@ import type { CreateRequestHandlerFunction, DataFunction, DataFunctionArgs, - EntryContext, - FutureConfig, - HandleDocumentRequestFunction, RemixRequest, RequestHandler, ServerBuild, - ServerRoute, ServerRouteManifest, } from './vendor/types'; -let FUTURE_FLAGS: FutureConfig | undefined; - const redirectStatusCodes = new Set([301, 302, 303, 307, 308]); function isRedirectResponse(response: Response): boolean { return redirectStatusCodes.has(response.status); @@ -99,106 +82,22 @@ export function wrapHandleErrorWithSentry( }; } -function makeWrappedDocumentRequestFunction(autoInstrumentRemix?: boolean, remixVersion?: number) { - return function (origDocumentRequestFunction: HandleDocumentRequestFunction): HandleDocumentRequestFunction { - return async function ( - this: unknown, - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - context: EntryContext, - loadContext?: Record, - ): Promise { - const documentRequestContext = { - request, - responseStatusCode, - responseHeaders, - context, - loadContext, - }; - - const isRemixV2 = FUTURE_FLAGS?.v2_errorBoundary || remixVersion === 2; - - if (!autoInstrumentRemix) { - const activeSpan = getActiveSpan(); - const rootSpan = activeSpan && getRootSpan(activeSpan); - - const name = rootSpan ? spanToJSON(rootSpan).description : undefined; - - return startSpan( - { - // If we don't have a root span, `onlyIfParent` will lead to the span not being created anyhow - // So we don't need to care too much about the fallback name, it's just for typing purposes.... - name: name || '', - onlyIfParent: true, - attributes: { - method: request.method, - url: request.url, - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.remix', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.remix.document_request', - }, - }, - () => { - return errorHandleDocumentRequestFunction.call( - this, - origDocumentRequestFunction, - documentRequestContext, - isRemixV2, - ); - }, - ); - } else { - return errorHandleDocumentRequestFunction.call( - this, - origDocumentRequestFunction, - documentRequestContext, - isRemixV2, - ); - } - }; - }; -} - -function makeWrappedDataFunction( - origFn: DataFunction, - id: string, - name: 'action' | 'loader', - remixVersion: number, - autoInstrumentRemix?: boolean, -): DataFunction { +function makeWrappedDataFunction(origFn: DataFunction, name: 'action' | 'loader'): DataFunction { return async function (this: unknown, args: DataFunctionArgs): Promise { - const isRemixV2 = FUTURE_FLAGS?.v2_errorBoundary || remixVersion === 2; - - if (!autoInstrumentRemix) { - return startSpan( - { - op: `function.remix.${name}`, - name: id, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.remix', - name, - }, - }, - (span: Span) => { - return errorHandleDataFunction.call(this, origFn, name, args, isRemixV2, span); - }, - ); - } else { - return errorHandleDataFunction.call(this, origFn, name, args, isRemixV2); - } + return errorHandleDataFunction.call(this, origFn, name, args); }; } const makeWrappedAction = - (id: string, remixVersion: number, autoInstrumentRemix?: boolean) => + () => (origAction: DataFunction): DataFunction => { - return makeWrappedDataFunction(origAction, id, 'action', remixVersion, autoInstrumentRemix); + return makeWrappedDataFunction(origAction, 'action'); }; const makeWrappedLoader = - (id: string, remixVersion: number, autoInstrumentRemix?: boolean) => + () => (origLoader: DataFunction): DataFunction => { - return makeWrappedDataFunction(origLoader, id, 'loader', remixVersion, autoInstrumentRemix); + return makeWrappedDataFunction(origLoader, 'loader'); }; function getTraceAndBaggage(): { @@ -262,16 +161,7 @@ function makeWrappedRootLoader(remixVersion: number) { }; } -function wrapRequestHandler( - origRequestHandler: RequestHandler, - build: ServerBuild | (() => ServerBuild | Promise), - autoInstrumentRemix: boolean, -): RequestHandler { - let resolvedBuild: ServerBuild; - let routes: ServerRoute[]; - let name: string; - let source: TransactionSource; - +function wrapRequestHandler(origRequestHandler: RequestHandler): RequestHandler { return async function (this: unknown, request: RemixRequest, loadContext?: AppLoadContext): Promise { const upperCaseMethod = request.method.toUpperCase(); // We don't want to wrap OPTIONS and HEAD requests @@ -279,16 +169,6 @@ function wrapRequestHandler( return origRequestHandler.call(this, request, loadContext); } - if (!autoInstrumentRemix) { - if (typeof build === 'function') { - resolvedBuild = await build(); - } else { - resolvedBuild = build; - } - - routes = createRoutes(resolvedBuild.routes); - } - return withIsolationScope(async isolationScope => { const options = getClient()?.getOptions(); @@ -300,13 +180,6 @@ function wrapRequestHandler( DEBUG_BUILD && logger.warn('Failed to normalize Remix request'); } - if (!autoInstrumentRemix) { - const url = new URL(request.url); - [name, source] = getTransactionName(routes, url); - - isolationScope.setTransactionName(name); - } - isolationScope.setSDKProcessingMetadata({ normalizedRequest }); if (!options || !hasTracingEnabled(options)) { @@ -319,29 +192,6 @@ function wrapRequestHandler( baggage: request.headers.get('baggage') || '', }, async () => { - if (!autoInstrumentRemix) { - return startSpan( - { - name, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.remix', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', - method: request.method, - }, - }, - async span => { - const res = (await origRequestHandler.call(this, request, loadContext)) as Response; - - if (isResponse(res)) { - setHttpStatus(span, res.status); - } - - return res; - }, - ); - } - return (await origRequestHandler.call(this, request, loadContext)) as Response; }, ); @@ -349,31 +199,22 @@ function wrapRequestHandler( }; } -function instrumentBuildCallback(build: ServerBuild, autoInstrumentRemix: boolean): ServerBuild { +function instrumentBuildCallback(build: ServerBuild): ServerBuild { const routes: ServerRouteManifest = {}; const remixVersion = getRemixVersionFromBuild(build); const wrappedEntry = { ...build.entry, module: { ...build.entry.module } }; - // Not keeping boolean flags like it's done for `requestHandler` functions, - // Because the build can change between build and runtime. - // So if there is a new `loader` or`action` or `documentRequest` after build. - // We should be able to wrap them, as they may not be wrapped before. - const defaultExport = wrappedEntry.module.default as undefined | WrappedFunction; - if (defaultExport && !defaultExport.__sentry_original__) { - fill(wrappedEntry.module, 'default', makeWrappedDocumentRequestFunction(autoInstrumentRemix, remixVersion)); - } - for (const [id, route] of Object.entries(build.routes)) { const wrappedRoute = { ...route, module: { ...route.module } }; const routeAction = wrappedRoute.module.action as undefined | WrappedFunction; if (routeAction && !routeAction.__sentry_original__) { - fill(wrappedRoute.module, 'action', makeWrappedAction(id, remixVersion, autoInstrumentRemix)); + fill(wrappedRoute.module, 'action', makeWrappedAction()); } const routeLoader = wrappedRoute.module.loader as undefined | WrappedFunction; if (routeLoader && !routeLoader.__sentry_original__) { - fill(wrappedRoute.module, 'loader', makeWrappedLoader(id, remixVersion, autoInstrumentRemix)); + fill(wrappedRoute.module, 'loader', makeWrappedLoader()); } // Entry module should have a loader function to provide `sentry-trace` and `baggage` @@ -396,48 +237,37 @@ function instrumentBuildCallback(build: ServerBuild, autoInstrumentRemix: boolea /** * Instruments `remix` ServerBuild for performance tracing and error tracking. */ -export function instrumentBuild( +function instrumentBuild( build: ServerBuild | (() => ServerBuild | Promise), - options: RemixOptions, ): ServerBuild | (() => ServerBuild | Promise) { - // eslint-disable-next-line deprecation/deprecation - const autoInstrumentRemix = options?.autoInstrumentRemix || false; - if (typeof build === 'function') { return function () { const resolvedBuild = build(); if (resolvedBuild instanceof Promise) { return resolvedBuild.then(build => { - FUTURE_FLAGS = getFutureFlagsServer(build); - - return instrumentBuildCallback(build, autoInstrumentRemix); + return instrumentBuildCallback(build); }); } else { - FUTURE_FLAGS = getFutureFlagsServer(resolvedBuild); - - return instrumentBuildCallback(resolvedBuild, autoInstrumentRemix); + return instrumentBuildCallback(resolvedBuild); } }; } else { - FUTURE_FLAGS = getFutureFlagsServer(build); - - return instrumentBuildCallback(build, autoInstrumentRemix); + return instrumentBuildCallback(build); } } -const makeWrappedCreateRequestHandler = (options: RemixOptions) => +const makeWrappedCreateRequestHandler = () => function (origCreateRequestHandler: CreateRequestHandlerFunction): CreateRequestHandlerFunction { return function ( this: unknown, build: ServerBuild | (() => Promise), ...args: unknown[] ): RequestHandler { - const newBuild = instrumentBuild(build, options); + const newBuild = instrumentBuild(build); const requestHandler = origCreateRequestHandler.call(this, newBuild, ...args); - // eslint-disable-next-line deprecation/deprecation - return wrapRequestHandler(requestHandler, newBuild, options.autoInstrumentRemix || false); + return wrapRequestHandler(requestHandler); }; }; @@ -445,7 +275,7 @@ const makeWrappedCreateRequestHandler = (options: RemixOptions) => * Monkey-patch Remix's `createRequestHandler` from `@remix-run/server-runtime` * which Remix Adapters (https://remix.run/docs/en/v1/api/remix) use underneath. */ -export function instrumentServer(options: RemixOptions): void { +export function instrumentServer(): void { const pkg = loadModule<{ createRequestHandler: CreateRequestHandlerFunction; }>('@remix-run/server-runtime'); @@ -456,5 +286,5 @@ export function instrumentServer(options: RemixOptions): void { return; } - fill(pkg, 'createRequestHandler', makeWrappedCreateRequestHandler(options)); + fill(pkg, 'createRequestHandler', makeWrappedCreateRequestHandler()); } diff --git a/packages/remix/src/utils/remixOptions.ts b/packages/remix/src/utils/remixOptions.ts index 58e8ae74b1d2..e65305c7696f 100644 --- a/packages/remix/src/utils/remixOptions.ts +++ b/packages/remix/src/utils/remixOptions.ts @@ -4,22 +4,4 @@ import type { BrowserOptions } from '@sentry/react'; export type RemixOptions = (Options | BrowserOptions | NodeOptions) & { captureActionFormDataKeys?: Record; -} & ( - | { - /** - * Enables OpenTelemetry Remix instrumentation. - * - * Note: This option will be the default behavior and will be removed in the next major version. - */ - autoInstrumentRemix?: true; - } - | { - /** - * Enables OpenTelemetry Remix instrumentation - * - * @deprecated Setting this option to `false` is deprecated as the next major version will default to behaving as if this option were `true` and the option itself will be removed. - * It is recommended to set this option to `true`. - */ - autoInstrumentRemix?: false; - } - ); +}; diff --git a/packages/remix/src/utils/utils.ts b/packages/remix/src/utils/utils.ts deleted file mode 100644 index 221ae4cf8e6c..000000000000 --- a/packages/remix/src/utils/utils.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { DataFunctionArgs } from '@remix-run/node'; -import { logger } from '@sentry/core'; -import type { Span, TransactionSource } from '@sentry/core'; -import { DEBUG_BUILD } from './debug-build'; -import { getRequestMatch, matchServerRoutes } from './vendor/response'; -import type { ServerRoute, ServerRouteManifest } from './vendor/types'; - -/** - * - */ -export async function storeFormDataKeys(args: DataFunctionArgs, span: Span): Promise { - try { - // We clone the request for Remix be able to read the FormData later. - const clonedRequest = args.request.clone(); - - // This only will return the last name of multiple file uploads in a single FormData entry. - // We can switch to `unstable_parseMultipartFormData` when it's stable. - // https://remix.run/docs/en/main/utils/parse-multipart-form-data#unstable_parsemultipartformdata - const formData = await clonedRequest.formData(); - - formData.forEach((value, key) => { - span.setAttribute(`remix.action_form_data.${key}`, typeof value === 'string' ? value : '[non-string value]'); - }); - } catch (e) { - DEBUG_BUILD && logger.warn('Failed to read FormData from request', e); - } -} - -/** - * Get transaction name from routes and url - */ -export function getTransactionName(routes: ServerRoute[], url: URL): [string, TransactionSource] { - const matches = matchServerRoutes(routes, url.pathname); - const match = matches && getRequestMatch(url, matches); - return match === null ? [url.pathname, 'url'] : [match.route.id || 'no-route-id', 'route']; -} - -/** - * Creates routes from the server route manifest - * - * @param manifest - * @param parentId - */ -export function createRoutes(manifest: ServerRouteManifest, parentId?: string): ServerRoute[] { - return Object.entries(manifest) - .filter(([, route]) => route.parentId === parentId) - .map(([id, route]) => ({ - ...route, - children: createRoutes(manifest, id), - })); -} diff --git a/packages/remix/test/integration/app_v1/entry.server.tsx b/packages/remix/test/integration/app_v1/entry.server.tsx index 9ecf5f467588..a4f964662ff3 100644 --- a/packages/remix/test/integration/app_v1/entry.server.tsx +++ b/packages/remix/test/integration/app_v1/entry.server.tsx @@ -1,6 +1,4 @@ -if (process.env.USE_OTEL !== '1') { - require('../instrument.server.cjs'); -} +require('../instrument.server.cjs'); import type { EntryContext } from '@remix-run/node'; import { RemixServer } from '@remix-run/react'; diff --git a/packages/remix/test/integration/app_v2/entry.server.tsx b/packages/remix/test/integration/app_v2/entry.server.tsx index 968ec19a5f59..a0e9e8fd7e3a 100644 --- a/packages/remix/test/integration/app_v2/entry.server.tsx +++ b/packages/remix/test/integration/app_v2/entry.server.tsx @@ -1,6 +1,4 @@ -if (process.env.USE_OTEL !== '1') { - require('../instrument.server.cjs'); -} +require('../instrument.server.cjs'); import * as Sentry from '@sentry/remix'; diff --git a/packages/remix/test/integration/instrument.server.cjs b/packages/remix/test/integration/instrument.server.cjs index d33b155f2d50..9c6544065fa9 100644 --- a/packages/remix/test/integration/instrument.server.cjs +++ b/packages/remix/test/integration/instrument.server.cjs @@ -4,5 +4,4 @@ Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1, tracePropagationTargets: ['example.org'], - autoInstrumentRemix: process.env.USE_OTEL === '1', }); diff --git a/packages/remix/test/integration/test/server/instrumentation-legacy/action.test.ts b/packages/remix/test/integration/test/server/instrumentation-legacy/action.test.ts deleted file mode 100644 index d9e91088cb8b..000000000000 --- a/packages/remix/test/integration/test/server/instrumentation-legacy/action.test.ts +++ /dev/null @@ -1,499 +0,0 @@ -import { describe, it } from 'vitest'; -import { RemixTestEnv, assertSentryEvent, assertSentryTransaction } from '../utils/helpers'; - -const useV2 = process.env.REMIX_VERSION === '2'; - -describe('Remix API Actions', () => { - it('correctly instruments a parameterized Remix API action', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/action-json-response/123123`; - const envelope = await env.getEnvelopeRequest({ - url, - method: 'post', - envelopeType: 'transaction', - }); - const transaction = envelope[2]!; - - assertSentryTransaction(transaction, { - transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - spans: [ - { - description: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - op: 'function.remix.action', - }, - { - description: 'root', - op: 'function.remix.loader', - }, - { - description: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - op: 'function.remix.loader', - }, - { - description: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - op: 'function.remix.document_request', - }, - ], - request: { - method: 'POST', - url, - cookies: expect.any(Object), - headers: { - 'user-agent': expect.any(String), - host: expect.stringContaining('localhost:'), - }, - }, - }); - }); - - it('reports an error thrown from the action', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/action-json-response/-1`; - - const envelopes = await env.getMultipleEnvelopeRequest({ - url, - count: 2, - method: 'post', - envelopeType: ['transaction', 'event'], - }); - - const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - - assertSentryTransaction(transaction![2]!, { - contexts: { - trace: { - status: 'internal_error', - data: { - 'http.response.status_code': 500, - }, - }, - }, - }); - - assertSentryEvent(event![2]!, { - transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/), - exception: { - values: [ - { - type: 'Error', - value: 'Unexpected Server Error', - stacktrace: expect.any(Object), - mechanism: { - data: { - function: useV2 ? 'remix.server.handleError' : 'action', - }, - handled: false, - type: 'instrument', - }, - }, - ], - }, - }); - }); - - it('includes request data in transaction and error events', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/action-json-response/-1`; - - const envelopes = await env.getMultipleEnvelopeRequest({ - url, - count: 2, - method: 'post', - envelopeType: ['transaction', 'event'], - }); - - const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - - assertSentryTransaction(transaction![2]!, { - transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - request: { - method: 'POST', - url, - cookies: expect.any(Object), - headers: { - 'user-agent': expect.any(String), - host: expect.stringContaining('localhost:'), - }, - }, - }); - - assertSentryEvent(event![2]!, { - transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/), - exception: { - values: [ - { - type: 'Error', - value: 'Unexpected Server Error', - }, - ], - }, - request: { - method: 'POST', - url, - cookies: expect.any(Object), - headers: { - 'user-agent': expect.any(String), - host: expect.stringContaining('localhost:'), - }, - }, - }); - }); - - it('handles an error-throwing redirection target', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/action-json-response/-2`; - - const envelopes = await env.getMultipleEnvelopeRequest({ - url, - count: 3, - method: 'post', - envelopeType: ['transaction', 'event'], - }); - - const [transaction_1, transaction_2] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - assertSentryTransaction(transaction_1![2]!, { - contexts: { - trace: { - op: 'http.server', - status: 'ok', - data: { - method: 'POST', - 'http.response.status_code': 302, - }, - }, - }, - transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - }); - - assertSentryTransaction(transaction_2![2]!, { - contexts: { - trace: { - op: 'http.server', - status: 'internal_error', - data: { - method: 'GET', - 'http.response.status_code': 500, - }, - }, - }, - transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - }); - - assertSentryEvent(event![2]!, { - transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/), - exception: { - values: [ - { - type: 'Error', - value: 'Unexpected Server Error', - stacktrace: expect.any(Object), - mechanism: { - data: { - function: useV2 ? 'remix.server.handleError' : 'loader', - }, - handled: false, - type: 'instrument', - }, - }, - ], - }, - }); - }); - - it('handles a thrown `json()` error response with `statusText`', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/action-json-response/-3`; - - const envelopes = await env.getMultipleEnvelopeRequest({ - url, - count: 2, - method: 'post', - envelopeType: ['transaction', 'event'], - }); - - const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - - assertSentryTransaction(transaction![2]!, { - contexts: { - trace: { - op: 'http.server', - status: 'internal_error', - data: { - method: 'POST', - 'http.response.status_code': 500, - }, - }, - }, - transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - }); - - assertSentryEvent(event![2]!, { - transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/), - exception: { - values: [ - { - type: 'Error', - value: 'Sentry Test Error', - stacktrace: expect.any(Object), - mechanism: { - data: { - function: 'action', - }, - handled: false, - type: 'instrument', - }, - }, - ], - }, - }); - }); - - it('handles a thrown `json()` error response without `statusText`', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/action-json-response/-4`; - - const envelopes = await env.getMultipleEnvelopeRequest({ - url, - count: 2, - method: 'post', - envelopeType: ['transaction', 'event'], - }); - - const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - - assertSentryTransaction(transaction![2]!, { - contexts: { - trace: { - op: 'http.server', - status: 'internal_error', - data: { - method: 'POST', - 'http.response.status_code': 500, - }, - }, - }, - transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - }); - - assertSentryEvent(event![2]!, { - transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/), - exception: { - values: [ - { - type: 'Error', - value: 'Object captured as exception with keys: data', - stacktrace: expect.any(Object), - mechanism: { - data: { - function: 'action', - }, - handled: false, - type: 'instrument', - }, - }, - ], - }, - }); - }); - - it('handles a thrown `json()` error response with string body', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/action-json-response/-5`; - - const envelopes = await env.getMultipleEnvelopeRequest({ - url, - count: 2, - method: 'post', - envelopeType: ['transaction', 'event'], - }); - - const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - - assertSentryTransaction(transaction![2]!, { - contexts: { - trace: { - op: 'http.server', - status: 'internal_error', - data: { - method: 'POST', - 'http.response.status_code': 500, - }, - }, - }, - transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - }); - - assertSentryEvent(event![2]!, { - transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/), - exception: { - values: [ - { - type: 'Error', - value: 'Sentry Test Error [string body]', - stacktrace: expect.any(Object), - mechanism: { - data: { - function: 'action', - }, - handled: false, - type: 'instrument', - }, - }, - ], - }, - }); - }); - - it('handles a thrown `json()` error response with an empty object', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/action-json-response/-6`; - - const envelopes = await env.getMultipleEnvelopeRequest({ - url, - count: 2, - method: 'post', - envelopeType: ['transaction', 'event'], - }); - - const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - - assertSentryTransaction(transaction![2]!, { - contexts: { - trace: { - op: 'http.server', - status: 'internal_error', - data: { - method: 'POST', - 'http.response.status_code': 500, - }, - }, - }, - transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - }); - - assertSentryEvent(event![2]!, { - transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/), - exception: { - values: [ - { - type: 'Error', - value: 'Object captured as exception with keys: [object has no keys]', - stacktrace: expect.any(Object), - mechanism: { - data: { - function: 'action', - }, - handled: false, - type: 'instrument', - }, - }, - ], - }, - }); - }); - - it('handles thrown string (primitive) from an action', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/server-side-unexpected-errors/-1`; - - const envelopes = await env.getMultipleEnvelopeRequest({ - url, - count: 2, - method: 'post', - envelopeType: ['event', 'transaction'], - }); - - const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - - assertSentryTransaction(transaction![2]!, { - contexts: { - trace: { - op: 'http.server', - status: 'internal_error', - data: { - method: 'POST', - 'http.response.status_code': 500, - }, - }, - }, - transaction: `routes/server-side-unexpected-errors${useV2 ? '.' : '/'}$id`, - }); - - assertSentryEvent(event![2]!, { - transaction: expect.stringMatching(/routes\/server-side-unexpected-errors(\/|\.)\$id/), - exception: { - values: [ - { - type: 'Error', - value: 'Thrown String Error', - stacktrace: expect.any(Object), - mechanism: { - data: { - function: useV2 ? 'remix.server.handleError' : 'action', - }, - handled: false, - type: 'instrument', - }, - }, - ], - }, - }); - }); - - it('handles thrown object from an action', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/server-side-unexpected-errors/-2`; - - const envelopes = await env.getMultipleEnvelopeRequest({ - url, - count: 2, - method: 'post', - envelopeType: ['event', 'transaction'], - }); - - const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - - assertSentryTransaction(transaction![2]!, { - contexts: { - trace: { - op: 'http.server', - status: 'internal_error', - data: { - method: 'POST', - 'http.response.status_code': 500, - }, - }, - }, - transaction: `routes/server-side-unexpected-errors${useV2 ? '.' : '/'}$id`, - }); - - assertSentryEvent(event![2]!, { - transaction: expect.stringMatching(/routes\/server-side-unexpected-errors(\/|\.)\$id/), - exception: { - values: [ - { - type: 'Error', - value: 'Thrown Object Error', - stacktrace: expect.any(Object), - mechanism: { - data: { - function: useV2 ? 'remix.server.handleError' : 'action', - }, - handled: false, - type: 'instrument', - }, - }, - ], - }, - }); - }); -}); diff --git a/packages/remix/test/integration/test/server/instrumentation-legacy/loader.test.ts b/packages/remix/test/integration/test/server/instrumentation-legacy/loader.test.ts deleted file mode 100644 index bf69b5e7e35b..000000000000 --- a/packages/remix/test/integration/test/server/instrumentation-legacy/loader.test.ts +++ /dev/null @@ -1,287 +0,0 @@ -import { Event } from '@sentry/core'; -import { describe, expect, it } from 'vitest'; -import { RemixTestEnv, assertSentryEvent, assertSentryTransaction } from '../utils/helpers'; - -const useV2 = process.env.REMIX_VERSION === '2'; - -describe('Remix API Loaders', () => { - it('reports an error thrown from the loader', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/loader-json-response/-2`; - - const envelopes = await env.getMultipleEnvelopeRequest({ url, count: 2, envelopeType: ['transaction', 'event'] }); - - const event = envelopes[0]?.[2]?.type === 'transaction' ? envelopes[1]?.[2] : envelopes[0]?.[2]; - const transaction = envelopes[0]?.[2]?.type === 'transaction' ? envelopes[0]?.[2] : envelopes[1]?.[2]; - - assertSentryTransaction(transaction!, { - contexts: { - trace: { - status: 'internal_error', - data: { - 'http.response.status_code': 500, - }, - }, - }, - }); - - assertSentryEvent(event!, { - transaction: expect.stringMatching(/routes\/loader-json-response(\/|\.)\$id/), - exception: { - values: [ - { - type: 'Error', - value: 'Unexpected Server Error', - stacktrace: expect.any(Object), - mechanism: { - data: { - function: useV2 ? 'remix.server.handleError' : 'loader', - }, - handled: false, - type: 'instrument', - }, - }, - ], - }, - }); - }); - - it('reports a thrown error response the loader', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/loader-throw-response/-1`; - - // We also wait for the transaction, even though we don't care about it for this test - // but otherwise this may leak into another test - const envelopes = await env.getMultipleEnvelopeRequest({ url, count: 2, envelopeType: ['event', 'transaction'] }); - - const event = envelopes[0]?.[2]?.type === 'transaction' ? envelopes[1]?.[2] : envelopes[0]?.[2]; - const transaction = envelopes[0]?.[2]?.type === 'transaction' ? envelopes[0]?.[2] : envelopes[1]?.[2]; - - assertSentryTransaction(transaction!, { - contexts: { - trace: { - status: 'internal_error', - data: { - 'http.response.status_code': 500, - }, - }, - }, - }); - - assertSentryEvent(event!, { - transaction: expect.stringMatching(/routes\/loader-throw-response(\/|\.)\$id/), - exception: { - values: [ - { - type: 'Error', - value: 'Not found', - stacktrace: expect.any(Object), - mechanism: { - data: { - function: 'loader', - }, - handled: false, - type: 'instrument', - }, - }, - ], - }, - }); - }); - - it('correctly instruments a parameterized Remix API loader', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/loader-json-response/123123`; - const envelope = await env.getEnvelopeRequest({ url, envelopeType: 'transaction' }); - const transaction = envelope[2]!; - - assertSentryTransaction(transaction, { - transaction: `routes/loader-json-response${useV2 ? '.' : '/'}$id`, - transaction_info: { - source: 'route', - }, - spans: [ - { - description: 'root', - op: 'function.remix.loader', - }, - { - description: `routes/loader-json-response${useV2 ? '.' : '/'}$id`, - op: 'function.remix.loader', - }, - { - description: `routes/loader-json-response${useV2 ? '.' : '/'}$id`, - op: 'function.remix.document_request', - }, - ], - }); - }); - - it('handles an error-throwing redirection target', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/loader-json-response/-1`; - - const envelopes = await env.getMultipleEnvelopeRequest({ - url, - count: 3, - envelopeType: ['transaction', 'event'], - }); - - const [transaction_1, transaction_2] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - - assertSentryTransaction(transaction_1![2]!, { - contexts: { - trace: { - op: 'http.server', - status: 'ok', - data: { - method: 'GET', - 'http.response.status_code': 302, - }, - }, - }, - transaction: `routes/loader-json-response${useV2 ? '.' : '/'}$id`, - }); - - assertSentryTransaction(transaction_2![2]!, { - contexts: { - trace: { - op: 'http.server', - status: 'internal_error', - data: { - method: 'GET', - 'http.response.status_code': 500, - }, - }, - }, - transaction: `routes/loader-json-response${useV2 ? '.' : '/'}$id`, - }); - - assertSentryEvent(event![2]!, { - transaction: expect.stringMatching(/routes\/loader-json-response(\/|\.)\$id/), - exception: { - values: [ - { - type: 'Error', - value: 'Unexpected Server Error', - stacktrace: expect.any(Object), - mechanism: { - data: { - function: useV2 ? 'remix.server.handleError' : 'loader', - }, - handled: false, - type: 'instrument', - }, - }, - ], - }, - }); - }); - - it('makes sure scope does not bleed between requests', async () => { - const env = await RemixTestEnv.init(); - - const envelopes = await Promise.all([ - env.getEnvelopeRequest({ url: `${env.url}/scope-bleed/1`, endServer: false, envelopeType: 'transaction' }), - env.getEnvelopeRequest({ url: `${env.url}/scope-bleed/2`, endServer: false, envelopeType: 'transaction' }), - env.getEnvelopeRequest({ url: `${env.url}/scope-bleed/3`, endServer: false, envelopeType: 'transaction' }), - env.getEnvelopeRequest({ url: `${env.url}/scope-bleed/4`, endServer: false, envelopeType: 'transaction' }), - ]); - - await new Promise(resolve => env.server.close(resolve)); - - envelopes.forEach(envelope => { - const tags = envelope[2]?.tags as NonNullable; - const customTagArr = Object.keys(tags).filter(t => t.startsWith('tag')); - expect(customTagArr).toHaveLength(1); - - const key = customTagArr[0]!; - const val = key[key.length - 1]; - expect(tags[key]).toEqual(val); - }); - }); - - it('continues transaction from sentry-trace header and baggage', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/loader-json-response/3`; - - // send sentry-trace and baggage headers to loader - env.setAxiosConfig({ - headers: { - 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1', - baggage: 'sentry-version=1.0,sentry-environment=production,sentry-trace_id=12312012123120121231201212312012', - }, - }); - const envelope = await env.getEnvelopeRequest({ url, envelopeType: 'transaction' }); - - expect(envelope[0]?.trace).toMatchObject({ - trace_id: '12312012123120121231201212312012', - }); - - assertSentryTransaction(envelope![2]!, { - contexts: { - trace: { - trace_id: '12312012123120121231201212312012', - parent_span_id: '1121201211212012', - }, - }, - }); - }); - - it('correctly instruments a deferred loader', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/loader-defer-response/123123`; - const envelope = await env.getEnvelopeRequest({ url, envelopeType: 'transaction' }); - const transaction = envelope[2]!; - - assertSentryTransaction(transaction, { - transaction: useV2 ? 'routes/loader-defer-response.$id' : 'routes/loader-defer-response/$id', - transaction_info: { - source: 'route', - }, - spans: useV2 - ? [ - { - description: 'root', - op: 'function.remix.loader', - }, - { - description: 'routes/loader-defer-response.$id', - op: 'function.remix.loader', - }, - { - description: 'routes/loader-defer-response.$id', - op: 'function.remix.document_request', - }, - ] - : [ - { - description: 'root', - op: 'function.remix.loader', - }, - { - description: 'routes/loader-defer-response/$id', - op: 'function.remix.loader', - }, - { - description: 'routes/loader-defer-response/$id', - op: 'function.remix.document_request', - }, - ], - }); - }); - - it('does not capture thrown redirect responses', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/throw-redirect`; - - const envelopesCount = await env.countEnvelopes({ - url, - envelopeType: 'event', - timeout: 3000, - }); - - expect(envelopesCount).toBe(0); - }); -}); diff --git a/packages/remix/test/integration/test/server/instrumentation-legacy/ssr.test.ts b/packages/remix/test/integration/test/server/instrumentation-legacy/ssr.test.ts deleted file mode 100644 index 9fafe0a70056..000000000000 --- a/packages/remix/test/integration/test/server/instrumentation-legacy/ssr.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { RemixTestEnv, assertSentryEvent, assertSentryTransaction } from '../utils/helpers'; - -const useV2 = process.env.REMIX_VERSION === '2'; - -describe('Server Side Rendering', () => { - it('correctly reports a server side rendering error', async () => { - const env = await RemixTestEnv.init(); - const url = `${env.url}/ssr-error`; - const envelopes = await env.getMultipleEnvelopeRequest({ url, count: 2, envelopeType: ['transaction', 'event'] }); - const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction'); - const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event'); - assertSentryTransaction(transaction![2]!, { - contexts: { - trace: { - status: 'internal_error', - data: { - 'http.response.status_code': 500, - }, - }, - }, - ...(useV2 && { - tags: { - // Testing that the wrapped `handleError` correctly adds tags - 'remix-test-tag': 'remix-test-value', - }, - }), - }); - - assertSentryEvent(event![2]!, { - transaction: 'routes/ssr-error', - exception: { - values: [ - { - type: 'Error', - value: 'Sentry SSR Test Error', - stacktrace: expect.any(Object), - mechanism: { - data: { - function: useV2 ? 'remix.server.handleError' : 'documentRequest', - }, - handled: false, - type: 'instrument', - }, - }, - ], - }, - }); - }); -}); diff --git a/packages/remix/test/integration/test/server/instrumentation-otel/action.test.ts b/packages/remix/test/integration/test/server/instrumentation/action.test.ts similarity index 100% rename from packages/remix/test/integration/test/server/instrumentation-otel/action.test.ts rename to packages/remix/test/integration/test/server/instrumentation/action.test.ts diff --git a/packages/remix/test/integration/test/server/instrumentation-otel/loader.test.ts b/packages/remix/test/integration/test/server/instrumentation/loader.test.ts similarity index 100% rename from packages/remix/test/integration/test/server/instrumentation-otel/loader.test.ts rename to packages/remix/test/integration/test/server/instrumentation/loader.test.ts diff --git a/packages/remix/test/integration/test/server/instrumentation-otel/ssr.test.ts b/packages/remix/test/integration/test/server/instrumentation/ssr.test.ts similarity index 100% rename from packages/remix/test/integration/test/server/instrumentation-otel/ssr.test.ts rename to packages/remix/test/integration/test/server/instrumentation/ssr.test.ts diff --git a/packages/remix/vitest.config.ts b/packages/remix/vitest.config.ts index 23c2383b9e8b..15190e85713f 100644 --- a/packages/remix/vitest.config.ts +++ b/packages/remix/vitest.config.ts @@ -1,13 +1,11 @@ import { defineConfig } from 'vitest/config'; -const useOtel = process.env.USE_OTEL === '1'; - export default defineConfig({ test: { globals: true, disableConsoleIntercept: true, silent: false, - setupFiles: useOtel ? './test/integration/instrument.server.cjs' : undefined, - include: useOtel ? ['**/instrumentation-otel/*.test.ts'] : ['**/instrumentation-legacy/*.test.ts'], + setupFiles: './test/integration/instrument.server.cjs', + include: ['**/instrumentation/*.test.ts'], }, });