|
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