diff --git a/packages/node/src/requestdata.ts b/packages/node/src/requestdata.ts index ace3ee5e7526..d72880df9680 100644 --- a/packages/node/src/requestdata.ts +++ b/packages/node/src/requestdata.ts @@ -169,7 +169,7 @@ export function extractRequestData( // koa, nextjs: req.url const originalUrl = req.originalUrl || req.url || ''; // absolute url - const absoluteUrl = `${protocol}://${host}${originalUrl}`; + const absoluteUrl = originalUrl.startsWith(protocol) ? originalUrl : `${protocol}://${host}${originalUrl}`; include.forEach(key => { switch (key) { case 'headers': { diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index 5c44d3802ff4..ba659d67bbe8 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -70,7 +70,7 @@ function extractData(response: Response): Promise { return responseClone.text(); } -function captureRemixServerException(err: Error, name: string): void { +function captureRemixServerException(err: Error, name: string, request: Request): void { // Skip capturing if the thrown error is not a 5xx response // https://remix.run/docs/en/v1/api/conventions#throwing-responses-in-loaders if (isResponse(err) && err.status < 500) { @@ -78,6 +78,8 @@ function captureRemixServerException(err: Error, name: string): void { } captureException(isResponse(err) ? extractData(err) : err, scope => { + scope.setSDKProcessingMetadata({ request }); + scope.addEventProcessor(event => { addExceptionMechanism(event, { type: 'instrument', @@ -127,7 +129,7 @@ function makeWrappedDocumentRequestFunction( span?.finish(); } catch (err) { - captureRemixServerException(err, 'documentRequest'); + captureRemixServerException(err, 'documentRequest', request); throw err; } @@ -164,7 +166,7 @@ function makeWrappedDataFunction(origFn: DataFunction, id: string, name: 'action currentScope.setSpan(activeTransaction); span?.finish(); } catch (err) { - captureRemixServerException(err, name); + captureRemixServerException(err, name, args.request); throw err; } @@ -353,6 +355,11 @@ function wrapRequestHandler(origRequestHandler: RequestHandler, build: ServerBui return local.bind(async () => { const hub = getCurrentHub(); const options = hub.getClient()?.getOptions(); + const scope = hub.getScope(); + + if (scope) { + scope.setSDKProcessingMetadata({ request }); + } if (!options || !hasTracingEnabled(options)) { return origRequestHandler.call(this, request, loadContext); diff --git a/packages/remix/src/utils/serverAdapters/express.ts b/packages/remix/src/utils/serverAdapters/express.ts index 747abadc6a2a..136246e60dde 100644 --- a/packages/remix/src/utils/serverAdapters/express.ts +++ b/packages/remix/src/utils/serverAdapters/express.ts @@ -60,6 +60,11 @@ function wrapExpressRequestHandler( const request = extractRequestData(req); const hub = getCurrentHub(); const options = hub.getClient()?.getOptions(); + const scope = hub.getScope(); + + if (scope) { + scope.setSDKProcessingMetadata({ request }); + } if (!options || !hasTracingEnabled(options) || !request.url || !request.method) { return origRequestHandler.call(this, req, res, next); diff --git a/packages/remix/test/integration/test/server/action.test.ts b/packages/remix/test/integration/test/server/action.test.ts index ada76c62929c..d7a611c45ea8 100644 --- a/packages/remix/test/integration/test/server/action.test.ts +++ b/packages/remix/test/integration/test/server/action.test.ts @@ -30,6 +30,10 @@ describe.each(['builtin', 'express'])('Remix API Actions with adapter = %s', ada op: 'function.remix.document_request', }, ], + request: { + method: 'POST', + url, + }, }); }); @@ -78,6 +82,44 @@ describe.each(['builtin', 'express'])('Remix API Actions with adapter = %s', ada }); }); + it('includes request data in transaction and error events', async () => { + const env = await RemixTestEnv.init(adapter); + 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/$id', + request: { + method: 'POST', + url, + }, + }); + + assertSentryEvent(event[2], { + exception: { + values: [ + { + type: 'Error', + value: 'Unexpected Server Error', + }, + ], + }, + request: { + method: 'POST', + url, + }, + }); + }); + it('handles a thrown 500 response', async () => { const env = await RemixTestEnv.init(adapter); const url = `${env.url}/action-json-response/-2`; diff --git a/packages/types/src/scope.ts b/packages/types/src/scope.ts index a2a14ffd4664..acc53471a899 100644 --- a/packages/types/src/scope.ts +++ b/packages/types/src/scope.ts @@ -175,4 +175,9 @@ export interface Scope { * Clears attachments from the scope */ clearAttachments(): this; + + /** + * Add data which will be accessible during event processing but won't get sent to Sentry + */ + setSDKProcessingMetadata(newData: { [key: string]: unknown }): this; }