Skip to content

Commit 230a6bd

Browse files
authored
ref(tracing): Set default sampling context data where startTransaction is called (#3210)
1 parent 5d7f350 commit 230a6bd

File tree

8 files changed

+404
-445
lines changed

8 files changed

+404
-445
lines changed

packages/node/src/handlers.ts

+106-17
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,13 @@
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, Transaction } from '@sentry/types';
6-
import {
7-
extractNodeRequestData,
8-
forget,
9-
isPlainObject,
10-
isString,
11-
logger,
12-
stripUrlQueryAndFragment,
13-
} from '@sentry/utils';
5+
import { Event, ExtractedNodeRequestData, Transaction } from '@sentry/types';
6+
import { forget, isPlainObject, isString, logger, normalize, stripUrlQueryAndFragment } from '@sentry/utils';
7+
import * as cookie from 'cookie';
148
import * as domain from 'domain';
159
import * as http from 'http';
1610
import * as os from 'os';
11+
import * as url from 'url';
1712

1813
import { NodeClient } from './client';
1914
import { flush } from './sdk';
@@ -66,11 +61,14 @@ export function tracingHandler(): (
6661
traceparentData = extractTraceparentData(req.headers['sentry-trace'] as string);
6762
}
6863

69-
const transaction = startTransaction({
70-
name: extractExpressTransactionName(req, { path: true, method: true }),
71-
op: 'http.server',
72-
...traceparentData,
73-
});
64+
const transaction = startTransaction(
65+
{
66+
name: extractExpressTransactionName(req, { path: true, method: true }),
67+
op: 'http.server',
68+
...traceparentData,
69+
},
70+
{ request: extractRequestData(req) },
71+
);
7472

7573
// We put the transaction on the scope so users can attach children to it
7674
getCurrentHub().configureScope(scope => {
@@ -187,6 +185,97 @@ function extractUserData(
187185
return extractedUser;
188186
}
189187

188+
/** Default request keys that'll be used to extract data from the request */
189+
const DEFAULT_REQUEST_KEYS = ['cookies', 'data', 'headers', 'method', 'query_string', 'url'];
190+
191+
/**
192+
* Normalizes data from the request object, accounting for framework differences.
193+
*
194+
* @param req The request object from which to extract data
195+
* @param keys An optional array of keys to include in the normalized data. Defaults to DEFAULT_REQUEST_KEYS if not
196+
* provided.
197+
* @returns An object containing normalized request data
198+
*/
199+
export function extractRequestData(
200+
req: { [key: string]: any },
201+
keys: string[] = DEFAULT_REQUEST_KEYS,
202+
): ExtractedNodeRequestData {
203+
const requestData: { [key: string]: any } = {};
204+
205+
// headers:
206+
// node, express: req.headers
207+
// koa: req.header
208+
const headers = (req.headers || req.header || {}) as {
209+
host?: string;
210+
cookie?: string;
211+
};
212+
// method:
213+
// node, express, koa: req.method
214+
const method = req.method;
215+
// host:
216+
// express: req.hostname in > 4 and req.host in < 4
217+
// koa: req.host
218+
// node: req.headers.host
219+
const host = req.hostname || req.host || headers.host || '<no host>';
220+
// protocol:
221+
// node: <n/a>
222+
// express, koa: req.protocol
223+
const protocol =
224+
req.protocol === 'https' || req.secure || ((req.socket || {}) as { encrypted?: boolean }).encrypted
225+
? 'https'
226+
: 'http';
227+
// url (including path and query string):
228+
// node, express: req.originalUrl
229+
// koa: req.url
230+
const originalUrl = (req.originalUrl || req.url || '') as string;
231+
// absolute url
232+
const absoluteUrl = `${protocol}://${host}${originalUrl}`;
233+
234+
keys.forEach(key => {
235+
switch (key) {
236+
case 'headers':
237+
requestData.headers = headers;
238+
break;
239+
case 'method':
240+
requestData.method = method;
241+
break;
242+
case 'url':
243+
requestData.url = absoluteUrl;
244+
break;
245+
case 'cookies':
246+
// cookies:
247+
// node, express, koa: req.headers.cookie
248+
// vercel, sails.js, express (w/ cookie middleware): req.cookies
249+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
250+
requestData.cookies = req.cookies || cookie.parse(headers.cookie || '');
251+
break;
252+
case 'query_string':
253+
// query string:
254+
// node: req.url (raw)
255+
// express, koa: req.query
256+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
257+
requestData.query_string = url.parse(originalUrl || '', false).query;
258+
break;
259+
case 'data':
260+
if (method === 'GET' || method === 'HEAD') {
261+
break;
262+
}
263+
// body data:
264+
// node, express, koa: req.body
265+
if (req.body !== undefined) {
266+
requestData.data = isString(req.body) ? req.body : JSON.stringify(normalize(req.body));
267+
}
268+
break;
269+
default:
270+
if ({}.hasOwnProperty.call(req, key)) {
271+
requestData[key] = (req as { [key: string]: any })[key];
272+
}
273+
}
274+
});
275+
276+
return requestData;
277+
}
278+
190279
/**
191280
* Options deciding what parts of the request to use when enhancing an event
192281
*/
@@ -230,10 +319,10 @@ export function parseRequest(event: Event, req: ExpressRequest, options?: ParseR
230319
}
231320

232321
if (options.request) {
233-
// if the option value is `true`, use the default set of keys by not passing anything to `extractNodeRequestData()`
322+
// if the option value is `true`, use the default set of keys by not passing anything to `extractRequestData()`
234323
const extractedRequestData = Array.isArray(options.request)
235-
? extractNodeRequestData(req, options.request)
236-
: extractNodeRequestData(req);
324+
? extractRequestData(req, options.request)
325+
: extractRequestData(req);
237326
event.request = {
238327
...event.request,
239328
...extractedRequestData,

0 commit comments

Comments
 (0)