Skip to content

Commit 3c1ebe0

Browse files
committed
fixup! feat(core, node): portable Express integration
1 parent f2d05ef commit 3c1ebe0

File tree

5 files changed

+81
-12
lines changed

5 files changed

+81
-12
lines changed

packages/core/src/integrations/express/index.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@
3030

3131
import { debug } from '../../utils/debug-logger';
3232
import { captureException } from '../../exports';
33-
import { getIsolationScope } from '../../currentScopes';
34-
import { httpRequestToRequestData } from '../../utils/request';
3533
import { DEBUG_BUILD } from '../../debug-build';
3634
import type {
3735
ExpressApplication,
@@ -58,6 +56,7 @@ import {
5856
} from './utils';
5957
import { wrapMethod } from '../../utils/object';
6058
import { patchLayer } from './patch-layer';
59+
import { setSDKProcessingMetadata } from './set-sdk-processing-metadata';
6160

6261
const getExpressExport = (express: ExpressModuleExport): ExpressExport =>
6362
hasDefaultProp(express) ? express.default : (express as ExpressExport);
@@ -166,11 +165,8 @@ export function expressErrorHandler(options?: ExpressHandlerOptions): ExpressErr
166165
res: ExpressResponse,
167166
next: (error: MiddlewareError) => void,
168167
): void {
169-
const normalizedRequest = httpRequestToRequestData(request);
170-
// Ensure we use the express-enhanced request here, instead of the plain HTTP one
171168
// When an error happens, the `expressRequestHandler` middleware does not run, so we set it here too
172-
getIsolationScope().setSDKProcessingMetadata({ normalizedRequest });
173-
169+
setSDKProcessingMetadata(request);
174170
const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError;
175171

176172
if (shouldHandleError(error)) {
@@ -223,10 +219,7 @@ export function setupExpressErrorHandler(
223219

224220
function expressRequestHandler(): ExpressMiddleware {
225221
return function sentryRequestMiddleware(request: ExpressRequest, _res: ExpressResponse, next: () => void): void {
226-
const normalizedRequest = httpRequestToRequestData(request);
227-
// Ensure we use the express-enhanced request here, instead of the plain HTTP one
228-
getIsolationScope().setSDKProcessingMetadata({ normalizedRequest });
229-
222+
setSDKProcessingMetadata(request);
230223
next();
231224
};
232225
}

packages/core/src/integrations/express/patch-layer.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,16 @@ import {
2626
import { getIsolationScope } from '../../currentScopes';
2727
import { getDefaultIsolationScope } from '../../defaultScopes';
2828
import { getOriginalFunction, markFunctionWrapped } from '../../utils/object';
29+
import { setSDKProcessingMetadata } from './set-sdk-processing-metadata';
2930

3031
export type ExpressPatchLayerOptions = Pick<
3132
ExpressIntegrationOptions,
3233
'onRouteResolved' | 'ignoreLayers' | 'ignoreLayersType'
3334
>;
3435

36+
// exported for testing
37+
export const kDidSetSDKMetadata = Symbol('sentry.DidSetSDKMetadata');
38+
3539
export function patchLayer(options: ExpressPatchLayerOptions, maybeLayer?: ExpressLayer, layerPath?: string): void {
3640
if (!maybeLayer?.handle) {
3741
return;
@@ -52,11 +56,18 @@ export function patchLayer(options: ExpressPatchLayerOptions, maybeLayer?: Expre
5256

5357
function layerHandlePatched(
5458
this: ExpressLayer,
55-
req: ExpressRequest,
59+
req: ExpressRequest & { [kDidSetSDKMetadata]?: boolean },
5660
res: ExpressResponse,
5761
//oxlint-disable-next-line no-explicit-any
5862
...otherArgs: any[]
5963
) {
64+
// Set normalizedRequest here because expressRequestHandler middleware
65+
// (registered via setupExpressErrorHandler) is added after routes and
66+
// therefore never runs for successful requests — route handlers typically
67+
// send a response without calling next(). It would be safe to set this
68+
// multiple times, since the data is identical, but more performant not to.
69+
setSDKProcessingMetadata(req);
70+
6071
// Only create spans when there's an active parent span
6172
// Without a parent span, this request is being ignored, so skip it
6273
const parentSpan = getActiveSpan();
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Abstract this out because we call it in multiple places, and it's cheaper to
3+
* only do one time for any given request.
4+
*/
5+
6+
import type { ExpressRequest } from './types';
7+
import { getIsolationScope } from '../../currentScopes';
8+
import { httpRequestToRequestData } from '../../utils/request';
9+
10+
const didSetProcessingMetadata = new WeakMap<ExpressRequest, boolean>();
11+
12+
export function setSDKProcessingMetadata(request: ExpressRequest) {
13+
if (!didSetProcessingMetadata.get(request)) {
14+
const normalizedRequest = httpRequestToRequestData(request);
15+
getIsolationScope().setSDKProcessingMetadata({ normalizedRequest });
16+
didSetProcessingMetadata.set(request, true);
17+
}
18+
}

packages/core/test/lib/integrations/express/patch-layer.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { type StartSpanOptions } from '../../../../src/types-hoist/startSpanOpti
1010
import { type Span } from '../../../../src/types-hoist/span';
1111
import { EventEmitter } from 'node:events';
1212
import { getOriginalFunction, markFunctionWrapped } from '../../../../src';
13+
import { setSDKProcessingMetadata } from '../../../../src/integrations/express/set-sdk-processing-metadata';
1314

1415
let DEBUG_BUILD = false;
1516
beforeEach(() => (DEBUG_BUILD = true));
@@ -36,8 +37,11 @@ const notDefaultIsolationScope = {
3637
setTransactionName(name: string) {
3738
transactionNames.push(name);
3839
},
40+
setSDKProcessingMetadata() {},
41+
};
42+
const defaultIsolationScope = {
43+
setSDKProcessingMetadata() {},
3944
};
40-
const defaultIsolationScope = {};
4145
vi.mock('../../../../src/currentScopes', () => ({
4246
getIsolationScope() {
4347
return inDefaultIsolationScope ? defaultIsolationScope : notDefaultIsolationScope;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { vi, beforeEach, describe, it, expect } from 'vitest';
2+
import { setSDKProcessingMetadata } from '../../../../src/integrations/express/set-sdk-processing-metadata';
3+
4+
const sdkProcessingMetadatas: unknown[] = [];
5+
beforeEach(() => (sdkProcessingMetadatas.length = 0));
6+
const isolationScope = {
7+
setSDKProcessingMetadata(data: unknown) {
8+
sdkProcessingMetadatas.push(data);
9+
},
10+
};
11+
vi.mock('../../../../src/currentScopes', () => ({
12+
getIsolationScope() {
13+
return isolationScope;
14+
},
15+
}));
16+
17+
describe('setSDKProcessingMetadata', () => {
18+
it('sets the normalized request data', () => {
19+
const request = {
20+
originalUrl: '/a/b/c',
21+
route: '/a/:boo/:car',
22+
method: 'POST',
23+
headers: {
24+
'Content-Type': 'application/x-www-form-urlencoded',
25+
},
26+
};
27+
setSDKProcessingMetadata(request);
28+
// call it again to cover no-op branch
29+
setSDKProcessingMetadata(request);
30+
expect(JSON.stringify(sdkProcessingMetadatas)).toBe(
31+
JSON.stringify([
32+
{
33+
normalizedRequest: {
34+
method: 'POST',
35+
headers: {
36+
'Content-Type': 'application/x-www-form-urlencoded',
37+
},
38+
},
39+
},
40+
]),
41+
);
42+
});
43+
});

0 commit comments

Comments
 (0)