Skip to content

Commit f70b24f

Browse files
committed
move request data functions to utils package
1 parent a8badb8 commit f70b24f

File tree

3 files changed

+427
-294
lines changed

3 files changed

+427
-294
lines changed

packages/node/src/handlers.ts

Lines changed: 9 additions & 294 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,20 @@
1-
/* eslint-disable max-lines */
21
/* eslint-disable @typescript-eslint/no-explicit-any */
32
import { captureException, getCurrentHub, startTransaction, withScope } from '@sentry/core';
4-
import { Event, ExtractedNodeRequestData, Span, Transaction } from '@sentry/types';
3+
import { Event, Span } from '@sentry/types';
54
import {
5+
AddRequestDataToEventOptions,
6+
addRequestDataToTransaction,
7+
extractPathForTransaction,
68
extractTraceparentData,
7-
isPlainObject,
89
isString,
910
logger,
10-
normalize,
1111
parseBaggageString,
12-
stripUrlQueryAndFragment,
1312
} from '@sentry/utils';
14-
import * as cookie from 'cookie';
1513
import * as domain from 'domain';
1614
import * as http from 'http';
17-
import * as url from 'url';
1815

1916
import { NodeClient } from './client';
20-
import { flush, isAutoSessionTrackingEnabled } from './sdk';
21-
22-
export interface ExpressRequest {
23-
baseUrl?: string;
24-
connection?: {
25-
remoteAddress?: string;
26-
};
27-
ip?: string;
28-
method?: string;
29-
originalUrl?: string;
30-
route?: {
31-
path: string;
32-
stack: [
33-
{
34-
name: string;
35-
},
36-
];
37-
};
38-
query?: {
39-
// It can be: undefined | string | string[] | ParsedQs | ParsedQs[] (from `qs` package), but we dont want to pull it.
40-
[key: string]: unknown;
41-
};
42-
url?: string;
43-
user?: {
44-
[key: string]: any;
45-
};
46-
}
17+
import { addRequestDataToEvent, extractRequestData, flush, isAutoSessionTrackingEnabled } from './sdk';
4718

4819
/**
4920
* Express-compatible tracing handler.
@@ -66,7 +37,7 @@ export function tracingHandler(): (
6637

6738
const transaction = startTransaction(
6839
{
69-
name: extractExpressTransactionName(req, { path: true, method: true }),
40+
name: extractPathForTransaction(req, { path: true, method: true }),
7041
op: 'http.server',
7142
...traceparentData,
7243
...(baggage && { metadata: { baggage: baggage } }),
@@ -89,7 +60,7 @@ export function tracingHandler(): (
8960
// Push `transaction.finish` to the next event loop so open spans have a chance to finish before the transaction
9061
// closes
9162
setImmediate(() => {
92-
addExpressReqToTransaction(transaction, req);
63+
addRequestDataToTransaction(transaction, req);
9364
transaction.setHttpStatus(res.statusCode);
9465
transaction.finish();
9566
});
@@ -99,263 +70,7 @@ export function tracingHandler(): (
9970
};
10071
}
10172

102-
/**
103-
* Set parameterized as transaction name e.g.: `GET /users/:id`
104-
* Also adds more context data on the transaction from the request
105-
*/
106-
function addExpressReqToTransaction(transaction: Transaction | undefined, req: ExpressRequest): void {
107-
if (!transaction) return;
108-
transaction.name = extractExpressTransactionName(req, { path: true, method: true });
109-
transaction.setData('url', req.originalUrl);
110-
transaction.setData('baseUrl', req.baseUrl);
111-
transaction.setData('query', req.query);
112-
}
113-
114-
/**
115-
* Extracts complete generalized path from the request object and uses it to construct transaction name.
116-
*
117-
* eg. GET /mountpoint/user/:id
118-
*
119-
* @param req The ExpressRequest object
120-
* @param options What to include in the transaction name (method, path, or both)
121-
*
122-
* @returns The fully constructed transaction name
123-
*/
124-
function extractExpressTransactionName(
125-
req: ExpressRequest,
126-
options: { path?: boolean; method?: boolean } = {},
127-
): string {
128-
const method = req.method?.toUpperCase();
129-
130-
let path = '';
131-
if (req.route) {
132-
path = `${req.baseUrl || ''}${req.route.path}`;
133-
} else if (req.originalUrl || req.url) {
134-
path = stripUrlQueryAndFragment(req.originalUrl || req.url || '');
135-
}
136-
137-
let info = '';
138-
if (options.method && method) {
139-
info += method;
140-
}
141-
if (options.method && options.path) {
142-
info += ' ';
143-
}
144-
if (options.path && path) {
145-
info += path;
146-
}
147-
148-
return info;
149-
}
150-
151-
type TransactionNamingScheme = 'path' | 'methodPath' | 'handler';
152-
153-
/** JSDoc */
154-
function extractTransaction(req: ExpressRequest, type: boolean | TransactionNamingScheme): string {
155-
switch (type) {
156-
case 'path': {
157-
return extractExpressTransactionName(req, { path: true });
158-
}
159-
case 'handler': {
160-
return req.route?.stack[0].name || '<anonymous>';
161-
}
162-
case 'methodPath':
163-
default: {
164-
return extractExpressTransactionName(req, { path: true, method: true });
165-
}
166-
}
167-
}
168-
169-
/** Default user keys that'll be used to extract data from the request */
170-
const DEFAULT_USER_KEYS = ['id', 'username', 'email'];
171-
172-
/** JSDoc */
173-
function extractUserData(
174-
user: {
175-
[key: string]: any;
176-
},
177-
keys: boolean | string[],
178-
): { [key: string]: any } {
179-
const extractedUser: { [key: string]: any } = {};
180-
const attributes = Array.isArray(keys) ? keys : DEFAULT_USER_KEYS;
181-
182-
attributes.forEach(key => {
183-
if (user && key in user) {
184-
extractedUser[key] = user[key];
185-
}
186-
});
187-
188-
return extractedUser;
189-
}
190-
191-
/** Default request keys that'll be used to extract data from the request */
192-
const DEFAULT_REQUEST_KEYS = ['cookies', 'data', 'headers', 'method', 'query_string', 'url'];
193-
194-
/**
195-
* Normalizes data from the request object, accounting for framework differences.
196-
*
197-
* @param req The request object from which to extract data
198-
* @param keys An optional array of keys to include in the normalized data. Defaults to DEFAULT_REQUEST_KEYS if not
199-
* provided.
200-
* @returns An object containing normalized request data
201-
*/
202-
export function extractRequestData(
203-
req: { [key: string]: any },
204-
keys: string[] = DEFAULT_REQUEST_KEYS,
205-
): ExtractedNodeRequestData {
206-
const requestData: { [key: string]: any } = {};
207-
208-
// headers:
209-
// node, express, nextjs: req.headers
210-
// koa: req.header
211-
const headers = (req.headers || req.header || {}) as {
212-
host?: string;
213-
cookie?: string;
214-
};
215-
// method:
216-
// node, express, koa, nextjs: req.method
217-
const method = req.method;
218-
// host:
219-
// express: req.hostname in > 4 and req.host in < 4
220-
// koa: req.host
221-
// node, nextjs: req.headers.host
222-
const host = req.hostname || req.host || headers.host || '<no host>';
223-
// protocol:
224-
// node, nextjs: <n/a>
225-
// express, koa: req.protocol
226-
const protocol =
227-
req.protocol === 'https' || req.secure || ((req.socket || {}) as { encrypted?: boolean }).encrypted
228-
? 'https'
229-
: 'http';
230-
// url (including path and query string):
231-
// node, express: req.originalUrl
232-
// koa, nextjs: req.url
233-
const originalUrl = (req.originalUrl || req.url || '') as string;
234-
// absolute url
235-
const absoluteUrl = `${protocol}://${host}${originalUrl}`;
236-
237-
keys.forEach(key => {
238-
switch (key) {
239-
case 'headers':
240-
requestData.headers = headers;
241-
break;
242-
case 'method':
243-
requestData.method = method;
244-
break;
245-
case 'url':
246-
requestData.url = absoluteUrl;
247-
break;
248-
case 'cookies':
249-
// cookies:
250-
// node, express, koa: req.headers.cookie
251-
// vercel, sails.js, express (w/ cookie middleware), nextjs: req.cookies
252-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
253-
requestData.cookies = req.cookies || cookie.parse(headers.cookie || '');
254-
break;
255-
case 'query_string':
256-
// query string:
257-
// node: req.url (raw)
258-
// express, koa, nextjs: req.query
259-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
260-
requestData.query_string = req.query || url.parse(originalUrl || '', false).query;
261-
break;
262-
case 'data':
263-
if (method === 'GET' || method === 'HEAD') {
264-
break;
265-
}
266-
// body data:
267-
// express, koa, nextjs: req.body
268-
//
269-
// when using node by itself, you have to read the incoming stream(see
270-
// https://nodejs.dev/learn/get-http-request-body-data-using-nodejs); if a user is doing that, we can't know
271-
// where they're going to store the final result, so they'll have to capture this data themselves
272-
if (req.body !== undefined) {
273-
requestData.data = isString(req.body) ? req.body : JSON.stringify(normalize(req.body));
274-
}
275-
break;
276-
default:
277-
if ({}.hasOwnProperty.call(req, key)) {
278-
requestData[key] = (req as { [key: string]: any })[key];
279-
}
280-
}
281-
});
282-
283-
return requestData;
284-
}
285-
286-
/**
287-
* Options deciding what parts of the request to use when enhancing an event
288-
*/
289-
export interface ParseRequestOptions {
290-
ip?: boolean;
291-
request?: boolean | string[];
292-
transaction?: boolean | TransactionNamingScheme;
293-
user?: boolean | string[];
294-
}
295-
296-
/**
297-
* Enriches passed event with request data.
298-
*
299-
* @param event Will be mutated and enriched with req data
300-
* @param req Request object
301-
* @param options object containing flags to enable functionality
302-
* @hidden
303-
*/
304-
export function parseRequest(event: Event, req: ExpressRequest, options?: ParseRequestOptions): Event {
305-
// eslint-disable-next-line no-param-reassign
306-
options = {
307-
ip: false,
308-
request: true,
309-
transaction: true,
310-
user: true,
311-
...options,
312-
};
313-
314-
if (options.request) {
315-
// if the option value is `true`, use the default set of keys by not passing anything to `extractRequestData()`
316-
const extractedRequestData = Array.isArray(options.request)
317-
? extractRequestData(req, options.request)
318-
: extractRequestData(req);
319-
event.request = {
320-
...event.request,
321-
...extractedRequestData,
322-
};
323-
}
324-
325-
if (options.user) {
326-
const extractedUser = req.user && isPlainObject(req.user) ? extractUserData(req.user, options.user) : {};
327-
328-
if (Object.keys(extractedUser)) {
329-
event.user = {
330-
...event.user,
331-
...extractedUser,
332-
};
333-
}
334-
}
335-
336-
// client ip:
337-
// node, nextjs: req.connection.remoteAddress
338-
// express, koa: req.ip
339-
if (options.ip) {
340-
const ip = req.ip || (req.connection && req.connection.remoteAddress);
341-
if (ip) {
342-
event.user = {
343-
...event.user,
344-
ip_address: ip,
345-
};
346-
}
347-
}
348-
349-
if (options.transaction && !event.transaction) {
350-
// TODO do we even need this anymore?
351-
// TODO make this work for nextjs
352-
event.transaction = extractTransaction(req, options.transaction);
353-
}
354-
355-
return event;
356-
}
357-
358-
export type RequestHandlerOptions = ParseRequestOptions & {
73+
export type RequestHandlerOptions = AddRequestDataToEventOptions & {
35974
flushTimeout?: number;
36075
};
36176

@@ -407,7 +122,7 @@ export function requestHandler(
407122
const currentHub = getCurrentHub();
408123

409124
currentHub.configureScope(scope => {
410-
scope.addEventProcessor((event: Event) => parseRequest(event, req, options));
125+
scope.addEventProcessor((event: Event) => addRequestDataToEvent(event, req, options));
411126
const client = currentHub.getClient<NodeClient>();
412127
if (isAutoSessionTrackingEnabled(client)) {
413128
const scope = currentHub.getScope();

packages/utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export * from './normalize';
1212
export * from './object';
1313
export * from './path';
1414
export * from './promisebuffer';
15+
export * from './requestdata';
1516
export * from './severity';
1617
export * from './stacktrace';
1718
export * from './string';

0 commit comments

Comments
 (0)