Skip to content

Commit

Permalink
fix(v8/node): Ensure express requests are properly handled (#14851)
Browse files Browse the repository at this point in the history
Backport of #14850

Fixes #14847
  • Loading branch information
mydea authored Dec 30, 2024
1 parent 576a1ad commit 960dd9b
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
const Sentry = require('@sentry/node');

Sentry.init({
dsn: 'https://[email protected]/1337',
release: '1.0',
transport: loggingTransport,
debug: true,
});

// express must be required after Sentry is initialized
const express = require('express');
const cors = require('cors');
const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests');

const app = express();

app.use(cors());

app.use((req, _res, next) => {
// We simulate this, which would in other cases be done by some middleware
req.user = {
id: '1',
email: '[email protected]',
};

next();
});

app.get('/test1', (_req, _res) => {
throw new Error('error_1');
});

app.use((_req, _res, next) => {
Sentry.setUser({
id: '2',
email: '[email protected]',
});

next();
});

app.get('/test2', (_req, _res) => {
throw new Error('error_2');
});

Sentry.setupExpressErrorHandler(app);

startExpressServerAndSendPortToRunner(app);
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';

describe('express user handling', () => {
afterAll(() => {
cleanupChildProcesses();
});

test('picks user from request', done => {
createRunner(__dirname, 'server.js')
.expect({
event: {
user: {
id: '1',
email: '[email protected]',
},
exception: {
values: [
{
value: 'error_1',
},
],
},
},
})
.start(done)
.makeRequest('get', '/test1', { expectError: true });
});

test('setUser overwrites user from request', done => {
createRunner(__dirname, 'server.js')
.expect({
event: {
user: {
id: '2',
email: '[email protected]',
},
exception: {
values: [
{
value: 'error_2',
},
],
},
},
})
.start(done)
.makeRequest('get', '/test2', { expectError: true });
});
});
2 changes: 1 addition & 1 deletion packages/core/src/utils-hoist/requestdata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,8 @@ export function addNormalizedRequestDataToEvent(

if (Object.keys(extractedUser).length) {
event.user = {
...event.user,
...extractedUser,
...event.user,
};
}
}
Expand Down
28 changes: 24 additions & 4 deletions packages/node/src/integrations/tracing/express.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ interface MiddlewareError extends Error {
};
}

type ExpressMiddleware = (
type ExpressMiddleware = (req: http.IncomingMessage, res: http.ServerResponse, next: () => void) => void;

type ExpressErrorMiddleware = (
error: MiddlewareError,
req: http.IncomingMessage,
res: http.ServerResponse,
Expand All @@ -111,13 +113,17 @@ interface ExpressHandlerOptions {
/**
* An Express-compatible error handler.
*/
export function expressErrorHandler(options?: ExpressHandlerOptions): ExpressMiddleware {
export function expressErrorHandler(options?: ExpressHandlerOptions): ExpressErrorMiddleware {
return function sentryErrorMiddleware(
error: MiddlewareError,
_req: http.IncomingMessage,
request: http.IncomingMessage,
res: http.ServerResponse,
next: (error: MiddlewareError) => void,
): void {
// Ensure we use the express-enhanced request here, instead of the plain HTTP one
// When an error happens, the `expressRequestHandler` middleware does not run, so we set it here too
getIsolationScope().setSDKProcessingMetadata({ request });

const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError;

if (shouldHandleError(error)) {
Expand Down Expand Up @@ -152,6 +158,19 @@ export function expressErrorHandler(options?: ExpressHandlerOptions): ExpressMid
};
}

function expressRequestHandler(): ExpressMiddleware {
return function sentryRequestMiddleware(
request: http.IncomingMessage,
_res: http.ServerResponse,
next: () => void,
): void {
// Ensure we use the express-enhanced request here, instead of the plain HTTP one
getIsolationScope().setSDKProcessingMetadata({ request });

next();
};
}

/**
* Add an Express error handler to capture errors to Sentry.
*
Expand All @@ -177,9 +196,10 @@ export function expressErrorHandler(options?: ExpressHandlerOptions): ExpressMid
* ```
*/
export function setupExpressErrorHandler(
app: { use: (middleware: ExpressMiddleware) => unknown },
app: { use: (middleware: ExpressMiddleware | ExpressErrorMiddleware) => unknown },
options?: ExpressHandlerOptions,
): void {
app.use(expressRequestHandler());
app.use(expressErrorHandler(options));
ensureIsWrapped(app.use, 'express');
}
Expand Down

0 comments on commit 960dd9b

Please sign in to comment.