|
2 | 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ |
3 | 3 | import { captureException, getCurrentHub, startTransaction, withScope } from '@sentry/core'; |
4 | 4 | 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'; |
14 | 8 | import * as domain from 'domain'; |
15 | 9 | import * as http from 'http'; |
16 | 10 | import * as os from 'os'; |
| 11 | +import * as url from 'url'; |
17 | 12 |
|
18 | 13 | import { NodeClient } from './client'; |
19 | 14 | import { flush } from './sdk'; |
@@ -66,11 +61,14 @@ export function tracingHandler(): ( |
66 | 61 | traceparentData = extractTraceparentData(req.headers['sentry-trace'] as string); |
67 | 62 | } |
68 | 63 |
|
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 | + ); |
74 | 72 |
|
75 | 73 | // We put the transaction on the scope so users can attach children to it |
76 | 74 | getCurrentHub().configureScope(scope => { |
@@ -187,6 +185,97 @@ function extractUserData( |
187 | 185 | return extractedUser; |
188 | 186 | } |
189 | 187 |
|
| 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 | + |
190 | 279 | /** |
191 | 280 | * Options deciding what parts of the request to use when enhancing an event |
192 | 281 | */ |
@@ -230,10 +319,10 @@ export function parseRequest(event: Event, req: ExpressRequest, options?: ParseR |
230 | 319 | } |
231 | 320 |
|
232 | 321 | 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()` |
234 | 323 | const extractedRequestData = Array.isArray(options.request) |
235 | | - ? extractNodeRequestData(req, options.request) |
236 | | - : extractNodeRequestData(req); |
| 324 | + ? extractRequestData(req, options.request) |
| 325 | + : extractRequestData(req); |
237 | 326 | event.request = { |
238 | 327 | ...event.request, |
239 | 328 | ...extractedRequestData, |
|
0 commit comments