Skip to content

Commit c15967e

Browse files
authored
ref: Revamp express route info extraction (#3084)
1 parent 3e10bd1 commit c15967e

File tree

1 file changed

+57
-71
lines changed

1 file changed

+57
-71
lines changed

packages/node/src/handlers.ts

+57-71
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,28 @@ import {
1414
import * as domain from 'domain';
1515
import * as http from 'http';
1616
import * as os from 'os';
17-
import * as url from 'url';
1817

1918
import { NodeClient } from './client';
2019
import { flush } from './sdk';
2120

2221
const DEFAULT_SHUTDOWN_TIMEOUT = 2000;
2322

24-
interface ExpressRequest {
23+
interface ExpressRequest extends http.IncomingMessage {
24+
[key: string]: any;
25+
baseUrl?: string;
26+
ip?: string;
27+
originalUrl?: string;
2528
route?: {
2629
path: string;
30+
stack: [
31+
{
32+
name: string;
33+
},
34+
];
35+
};
36+
user?: {
37+
[key: string]: any;
2738
};
28-
method: string;
29-
originalUrl: string;
30-
baseUrl: string;
31-
query: string;
3239
}
3340

3441
/**
@@ -45,19 +52,14 @@ export function tracingHandler(): (
4552
res: http.ServerResponse,
4653
next: (error?: any) => void,
4754
): void {
48-
// TODO: At this point `req.route.path` (which we use in `extractTransaction`) is not available
49-
// but `req.path` or `req.url` should do the job as well. We could unify this here.
50-
const reqMethod = (req.method || '').toUpperCase();
51-
const reqUrl = req.url && stripUrlQueryAndFragment(req.url);
52-
5355
// If there is a trace header set, we extract the data from it (parentSpanId, traceId, and sampling decision)
5456
let traceparentData;
5557
if (req.headers && isString(req.headers['sentry-trace'])) {
5658
traceparentData = extractTraceparentData(req.headers['sentry-trace'] as string);
5759
}
5860

5961
const transaction = startTransaction({
60-
name: `${reqMethod} ${reqUrl}`,
62+
name: extractRouteInfo(req, { path: true, method: true }),
6163
op: 'http.server',
6264
...traceparentData,
6365
});
@@ -75,7 +77,7 @@ export function tracingHandler(): (
7577
res.once('finish', () => {
7678
// We schedule the immediate execution of the `finish` to let all the spans being closed first.
7779
setImmediate(() => {
78-
addExpressReqToTransaction(transaction, (req as unknown) as ExpressRequest);
80+
addExpressReqToTransaction(transaction, req);
7981
transaction.setHttpStatus(res.statusCode);
8082
transaction.finish();
8183
});
@@ -91,56 +93,56 @@ export function tracingHandler(): (
9193
*/
9294
function addExpressReqToTransaction(transaction: Transaction | undefined, req: ExpressRequest): void {
9395
if (!transaction) return;
94-
if (req.route) {
95-
transaction.name = `${req.method} ${req.baseUrl}${req.route.path}`;
96-
}
96+
transaction.name = extractRouteInfo(req, { path: true, method: true });
9797
transaction.setData('url', req.originalUrl);
9898
transaction.setData('baseUrl', req.baseUrl);
9999
transaction.setData('query', req.query);
100100
}
101101

102+
/**
103+
* Extracts complete generalized path from the request object.
104+
* eg. /mountpoint/user/:id
105+
*/
106+
function extractRouteInfo(req: ExpressRequest, options: { path?: boolean; method?: boolean } = {}): string {
107+
const method = req.method?.toUpperCase();
108+
let path;
109+
if (req.baseUrl && req.route) {
110+
path = `${req.baseUrl}${req.route.path}`;
111+
} else if (req.originalUrl || req.url) {
112+
path = stripUrlQueryAndFragment(req.originalUrl || req.url || '');
113+
} else {
114+
path = req.route?.path || '';
115+
}
116+
117+
let info = '';
118+
if (options.method && method) {
119+
info += method;
120+
}
121+
if (options.method && options.path) {
122+
info += ` `;
123+
}
124+
if (options.path && path) {
125+
info += path;
126+
}
127+
128+
return info;
129+
}
130+
102131
type TransactionTypes = 'path' | 'methodPath' | 'handler';
103132

104133
/** JSDoc */
105-
function extractTransaction(req: { [key: string]: any }, type: boolean | TransactionTypes): string | undefined {
106-
try {
107-
// Express.js shape
108-
const request = req as {
109-
url: string;
110-
originalUrl: string;
111-
method: string;
112-
route: {
113-
path: string;
114-
stack: [
115-
{
116-
name: string;
117-
},
118-
];
119-
};
120-
};
121-
122-
let routePath;
123-
try {
124-
routePath = url.parse(request.originalUrl || request.url).pathname;
125-
} catch (_oO) {
126-
routePath = request.route.path;
134+
function extractTransaction(req: ExpressRequest, type: boolean | TransactionTypes): string {
135+
switch (type) {
136+
case 'path': {
137+
return extractRouteInfo(req, { path: true });
127138
}
128-
129-
switch (type) {
130-
case 'path': {
131-
return routePath;
132-
}
133-
case 'handler': {
134-
return request.route.stack[0].name;
135-
}
136-
case 'methodPath':
137-
default: {
138-
const method = request.method.toUpperCase();
139-
return `${method} ${routePath}`;
140-
}
139+
case 'handler': {
140+
return req.route?.stack[0].name || '<anonymous>';
141+
}
142+
case 'methodPath':
143+
default: {
144+
return extractRouteInfo(req, { path: true, method: true });
141145
}
142-
} catch (_oO) {
143-
return undefined;
144146
}
145147
}
146148

@@ -186,20 +188,7 @@ export interface ParseRequestOptions {
186188
* @param options object containing flags to enable functionality
187189
* @hidden
188190
*/
189-
export function parseRequest(
190-
event: Event,
191-
req: {
192-
[key: string]: any;
193-
user?: {
194-
[key: string]: any;
195-
};
196-
ip?: string;
197-
connection?: {
198-
remoteAddress?: string;
199-
};
200-
},
201-
options?: ParseRequestOptions,
202-
): Event {
191+
export function parseRequest(event: Event, req: ExpressRequest, options?: ParseRequestOptions): Event {
203192
// eslint-disable-next-line no-param-reassign
204193
options = {
205194
ip: false,
@@ -261,10 +250,7 @@ export function parseRequest(
261250
}
262251

263252
if (options.transaction && !event.transaction) {
264-
const transaction = extractTransaction(req, options.transaction);
265-
if (transaction) {
266-
event.transaction = transaction;
267-
}
253+
event.transaction = extractTransaction(req, options.transaction);
268254
}
269255

270256
return event;

0 commit comments

Comments
 (0)