Skip to content

Commit aaf5116

Browse files
committed
fix: Use correct paths for express transactions and unify express integration
1 parent df6ce68 commit aaf5116

File tree

2 files changed

+71
-89
lines changed

2 files changed

+71
-89
lines changed

packages/node/src/handlers.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/* eslint-disable @typescript-eslint/no-explicit-any */
33
import { captureException, getCurrentHub, startTransaction, withScope } from '@sentry/core';
44
import { extractTraceparentData, Span } from '@sentry/tracing';
5-
import { Event } from '@sentry/types';
5+
import { Event, Transaction } from '@sentry/types';
66
import {
77
extractNodeRequestData,
88
forget,
@@ -21,6 +21,16 @@ import { flush } from './sdk';
2121

2222
const DEFAULT_SHUTDOWN_TIMEOUT = 2000;
2323

24+
interface ExpressRequest {
25+
route?: {
26+
path: string;
27+
};
28+
method: string;
29+
originalUrl: string;
30+
baseUrl: string;
31+
query: string;
32+
}
33+
2434
/**
2535
* Express-compatible tracing handler.
2636
* @see Exposed as `Handlers.tracingHandler`
@@ -65,6 +75,7 @@ export function tracingHandler(): (
6575
res.once('finish', () => {
6676
// We schedule the immediate execution of the `finish` to let all the spans being closed first.
6777
setImmediate(() => {
78+
addExpressReqToTransaction(transaction, (req as unknown) as ExpressRequest);
6879
transaction.setHttpStatus(res.statusCode);
6980
transaction.finish();
7081
});
@@ -74,6 +85,20 @@ export function tracingHandler(): (
7485
};
7586
}
7687

88+
/**
89+
* Set parameterized as transaction name e.g.: `GET /users/:id`
90+
* Also adds more context data on the transaction from the request
91+
*/
92+
function addExpressReqToTransaction(transaction: Transaction | undefined, req: ExpressRequest): void {
93+
if (!transaction) return;
94+
if (req.route) {
95+
transaction.name = `${req.method} ${req.baseUrl}${req.route.path}`;
96+
}
97+
transaction.setData('url', req.originalUrl);
98+
transaction.setData('baseUrl', req.baseUrl);
99+
transaction.setData('query', req.query);
100+
}
101+
77102
type TransactionTypes = 'path' | 'methodPath' | 'handler';
78103

79104
/** JSDoc */
Lines changed: 45 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable @typescript-eslint/no-explicit-any */
21
import { Integration, Transaction } from '@sentry/types';
32
import { logger } from '@sentry/utils';
43

@@ -30,27 +29,13 @@ type Method =
3029
| 'use';
3130

3231
type Router = {
33-
[method in Method]: (...args: any) => any;
32+
[method in Method]: (...args: unknown[]) => unknown;
3433
};
3534

36-
type ErrorRequestHandler = (...args: any) => any;
37-
type RequestHandler = (...args: any) => any;
38-
type NextFunction = (...args: any) => any;
39-
40-
interface Response {
35+
interface ExpressResponse {
4136
once(name: string, callback: () => void): void;
4237
}
4338

44-
interface Request {
45-
route?: {
46-
path: string;
47-
};
48-
method: string;
49-
originalUrl: string;
50-
baseUrl: string;
51-
query: string;
52-
}
53-
5439
/**
5540
* Internal helper for `__sentry_transaction`
5641
* @hidden
@@ -62,8 +47,7 @@ interface SentryTracingResponse {
6247
/**
6348
* Express integration
6449
*
65-
* Provides an request and error handler for Express framework
66-
* as well as tracing capabilities
50+
* Provides an request and error handler for Express framework as well as tracing capabilities
6751
*/
6852
export class Express implements Integration {
6953
/**
@@ -79,26 +63,26 @@ export class Express implements Integration {
7963
/**
8064
* Express App instance
8165
*/
82-
private readonly _app?: Router;
66+
private readonly _router?: Router;
8367
private readonly _methods?: Method[];
8468

8569
/**
8670
* @inheritDoc
8771
*/
88-
public constructor(options: { app?: Router; methods?: Method[] } = {}) {
89-
this._app = options.app;
72+
public constructor(options: { app?: Router; router?: Router; methods?: Method[] } = {}) {
73+
this._router = options.router || options.app;
9074
this._methods = (Array.isArray(options.methods) ? options.methods : []).concat('use');
9175
}
9276

9377
/**
9478
* @inheritDoc
9579
*/
9680
public setupOnce(): void {
97-
if (!this._app) {
81+
if (!this._router) {
9882
logger.error('ExpressIntegration is missing an Express instance');
9983
return;
10084
}
101-
instrumentMiddlewares(this._app, this._methods);
85+
instrumentMiddlewares(this._router, this._methods);
10286
}
10387
}
10488

@@ -113,16 +97,17 @@ export class Express implements Integration {
11397
* app.use(function (req, res, next) { ... })
11498
* // error handler
11599
* app.use(function (err, req, res, next) { ... })
100+
*
101+
* They all internally delegate to the `router[method]` of the given application instance.
116102
*/
117-
// eslint-disable-next-line @typescript-eslint/ban-types
118-
function wrap(fn: Function, method: Method): RequestHandler | ErrorRequestHandler {
103+
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any
104+
function wrap(fn: Function, method: Method): (...args: any[]) => void {
119105
const arity = fn.length;
120106

121107
switch (arity) {
122108
case 2: {
123-
return function(this: NodeJS.Global, req: Request, res: Response & SentryTracingResponse): any {
109+
return function(this: NodeJS.Global, req: unknown, res: ExpressResponse & SentryTracingResponse): void {
124110
const transaction = res.__sentry_transaction;
125-
addExpressReqToTransaction(transaction, req);
126111
if (transaction) {
127112
const span = transaction.startChild({
128113
description: fn.name,
@@ -132,56 +117,43 @@ function wrap(fn: Function, method: Method): RequestHandler | ErrorRequestHandle
132117
span.finish();
133118
});
134119
}
135-
// eslint-disable-next-line prefer-rest-params
136-
return fn.apply(this, arguments);
120+
return fn.call(this, req, res);
137121
};
138122
}
139123
case 3: {
140124
return function(
141125
this: NodeJS.Global,
142-
req: Request,
143-
res: Response & SentryTracingResponse,
144-
next: NextFunction,
145-
): any {
126+
req: unknown,
127+
res: ExpressResponse & SentryTracingResponse,
128+
next: () => void,
129+
): void {
146130
const transaction = res.__sentry_transaction;
147-
addExpressReqToTransaction(transaction, req);
148-
const span =
149-
transaction &&
150-
transaction.startChild({
151-
description: fn.name,
152-
op: `middleware.${method}`,
153-
});
154-
fn.call(this, req, res, function(this: NodeJS.Global): any {
155-
if (span) {
156-
span.finish();
157-
}
158-
// eslint-disable-next-line prefer-rest-params
159-
return next.apply(this, arguments);
131+
const span = transaction?.startChild({
132+
description: fn.name,
133+
op: `middleware.${method}`,
134+
});
135+
fn.call(this, req, res, function(this: NodeJS.Global, ...args: unknown[]): void {
136+
span?.finish();
137+
next.call(this, ...args);
160138
});
161139
};
162140
}
163141
case 4: {
164142
return function(
165143
this: NodeJS.Global,
166-
err: any,
144+
err: Error,
167145
req: Request,
168146
res: Response & SentryTracingResponse,
169-
next: NextFunction,
170-
): any {
147+
next: () => void,
148+
): void {
171149
const transaction = res.__sentry_transaction;
172-
addExpressReqToTransaction(transaction, req);
173-
const span =
174-
transaction &&
175-
transaction.startChild({
176-
description: fn.name,
177-
op: `middleware.${method}`,
178-
});
179-
fn.call(this, err, req, res, function(this: NodeJS.Global): any {
180-
if (span) {
181-
span.finish();
182-
}
183-
// eslint-disable-next-line prefer-rest-params
184-
return next.apply(this, arguments);
150+
const span = transaction?.startChild({
151+
description: fn.name,
152+
op: `middleware.${method}`,
153+
});
154+
fn.call(this, err, req, res, function(this: NodeJS.Global, ...args: unknown[]): void {
155+
span?.finish();
156+
next.call(this, ...args);
185157
});
186158
};
187159
}
@@ -192,7 +164,7 @@ function wrap(fn: Function, method: Method): RequestHandler | ErrorRequestHandle
192164
}
193165

194166
/**
195-
* Takes all the function arguments passed to the original `app.use` call
167+
* Takes all the function arguments passed to the original `app` or `router` method, eg. `app.use` or `router.use`
196168
* and wraps every function, as well as array of functions with a call to our `wrap` method.
197169
* We have to take care of the arrays as well as iterate over all of the arguments,
198170
* as `app.use` can accept middlewares in few various forms.
@@ -221,36 +193,21 @@ function wrapMiddlewareArgs(args: unknown[], method: Method): unknown[] {
221193
}
222194

223195
/**
224-
* Patches original App to utilize our tracing functionality
196+
* Patches original router to utilize our tracing functionality
225197
*/
226-
function patchMiddleware(app: Router, method: Method): Router {
227-
const originalAppCallback = app[method];
198+
function patchMiddleware(router: Router, method: Method): Router {
199+
const originalCallback = router[method];
228200

229-
app[method] = function(...args: unknown[]): any {
230-
return originalAppCallback.apply(this, wrapMiddlewareArgs(args, method));
201+
router[method] = function(...args: unknown[]): void {
202+
return originalCallback.call(this, ...wrapMiddlewareArgs(args, method));
231203
};
232204

233-
return app;
205+
return router;
234206
}
235207

236208
/**
237-
* Patches original application methods
209+
* Patches original router methods
238210
*/
239-
function instrumentMiddlewares(app: Router, methods: Method[] = []): void {
240-
methods.forEach((method: Method) => patchMiddleware(app, method));
241-
}
242-
243-
/**
244-
* Set parameterized as transaction name e.g.: `GET /users/:id`
245-
* Also adds more context data on the transaction from the request
246-
*/
247-
function addExpressReqToTransaction(transaction: Transaction | undefined, req: Request): void {
248-
if (transaction) {
249-
if (req.route && req.route.path) {
250-
transaction.name = `${req.method} ${req.route.path}`;
251-
}
252-
transaction.setData('url', req.originalUrl);
253-
transaction.setData('baseUrl', req.baseUrl);
254-
transaction.setData('query', req.query);
255-
}
211+
function instrumentMiddlewares(router: Router, methods: Method[] = []): void {
212+
methods.forEach((method: Method) => patchMiddleware(router, method));
256213
}

0 commit comments

Comments
 (0)