Skip to content

Commit fe3a189

Browse files
committed
move request data functions to utils package
1 parent 5cee1d9 commit fe3a189

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
parseBaggageSetMutability,
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.
@@ -68,7 +39,7 @@ export function tracingHandler(): (
6839

6940
const transaction = startTransaction(
7041
{
71-
name: extractExpressTransactionName(req, { path: true, method: true }),
42+
name: extractPathForTransaction(req, { path: true, method: true }),
7243
op: 'http.server',
7344
...traceparentData,
7445
metadata: { baggage: baggage },
@@ -91,7 +62,7 @@ export function tracingHandler(): (
9162
// Push `transaction.finish` to the next event loop so open spans have a chance to finish before the transaction
9263
// closes
9364
setImmediate(() => {
94-
addExpressReqToTransaction(transaction, req);
65+
addRequestDataToTransaction(transaction, req);
9566
transaction.setHttpStatus(res.statusCode);
9667
transaction.finish();
9768
});
@@ -101,263 +72,7 @@ export function tracingHandler(): (
10172
};
10273
}
10374

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

@@ -409,7 +124,7 @@ export function requestHandler(
409124
const currentHub = getCurrentHub();
410125

411126
currentHub.configureScope(scope => {
412-
scope.addEventProcessor((event: Event) => parseRequest(event, req, options));
127+
scope.addEventProcessor((event: Event) => addRequestDataToEvent(event, req, options));
413128
const client = currentHub.getClient<NodeClient>();
414129
if (isAutoSessionTrackingEnabled(client)) {
415130
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)