Skip to content

ref(tracing): Set default sampling context data where startTransaction is called #3210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 106 additions & 17 deletions packages/node/src/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { captureException, getCurrentHub, startTransaction, withScope } from '@sentry/core';
import { extractTraceparentData, Span } from '@sentry/tracing';
import { Event, Transaction } from '@sentry/types';
import {
extractNodeRequestData,
forget,
isPlainObject,
isString,
logger,
stripUrlQueryAndFragment,
} from '@sentry/utils';
import { Event, ExtractedNodeRequestData, Transaction } from '@sentry/types';
import { forget, isPlainObject, isString, logger, normalize, stripUrlQueryAndFragment } from '@sentry/utils';
import * as cookie from 'cookie';
import * as domain from 'domain';
import * as http from 'http';
import * as os from 'os';
import * as url from 'url';

import { NodeClient } from './client';
import { flush } from './sdk';
Expand Down Expand Up @@ -58,11 +53,14 @@ export function tracingHandler(): (
traceparentData = extractTraceparentData(req.headers['sentry-trace'] as string);
}

const transaction = startTransaction({
name: extractExpressTransactionName(req, { path: true, method: true }),
op: 'http.server',
...traceparentData,
});
const transaction = startTransaction(
{
name: extractExpressTransactionName(req, { path: true, method: true }),
op: 'http.server',
...traceparentData,
},
{ request: extractRequestData(req) },
);

// We put the transaction on the scope so users can attach children to it
getCurrentHub().configureScope(scope => {
Expand Down Expand Up @@ -179,6 +177,97 @@ function extractUserData(
return extractedUser;
}

/** Default request keys that'll be used to extract data from the request */
const DEFAULT_REQUEST_KEYS = ['cookies', 'data', 'headers', 'method', 'query_string', 'url'];

/**
* Normalizes data from the request object, accounting for framework differences.
*
* @param req The request object from which to extract data
* @param keys An optional array of keys to include in the normalized data. Defaults to DEFAULT_REQUEST_KEYS if not
* provided.
* @returns An object containing normalized request data
*/
export function extractRequestData(
req: { [key: string]: any },
keys: string[] = DEFAULT_REQUEST_KEYS,
): ExtractedNodeRequestData {
const requestData: { [key: string]: any } = {};

// headers:
// node, express: req.headers
// koa: req.header
const headers = (req.headers || req.header || {}) as {
host?: string;
cookie?: string;
};
// method:
// node, express, koa: req.method
const method = req.method;
// host:
// express: req.hostname in > 4 and req.host in < 4
// koa: req.host
// node: req.headers.host
const host = req.hostname || req.host || headers.host || '<no host>';
// protocol:
// node: <n/a>
// express, koa: req.protocol
const protocol =
req.protocol === 'https' || req.secure || ((req.socket || {}) as { encrypted?: boolean }).encrypted
? 'https'
: 'http';
// url (including path and query string):
// node, express: req.originalUrl
// koa: req.url
const originalUrl = (req.originalUrl || req.url || '') as string;
// absolute url
const absoluteUrl = `${protocol}://${host}${originalUrl}`;

keys.forEach(key => {
switch (key) {
case 'headers':
requestData.headers = headers;
break;
case 'method':
requestData.method = method;
break;
case 'url':
requestData.url = absoluteUrl;
break;
case 'cookies':
// cookies:
// node, express, koa: req.headers.cookie
// vercel, sails.js, express (w/ cookie middleware): req.cookies
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
requestData.cookies = req.cookies || cookie.parse(headers.cookie || '');
break;
case 'query_string':
// query string:
// node: req.url (raw)
// express, koa: req.query
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
requestData.query_string = url.parse(originalUrl || '', false).query;
break;
case 'data':
if (method === 'GET' || method === 'HEAD') {
break;
}
// body data:
// node, express, koa: req.body
if (req.body !== undefined) {
requestData.data = isString(req.body) ? req.body : JSON.stringify(normalize(req.body));
}
break;
default:
if ({}.hasOwnProperty.call(req, key)) {
requestData[key] = (req as { [key: string]: any })[key];
}
}
});

return requestData;
}

/**
* Options deciding what parts of the request to use when enhancing an event
*/
Expand Down Expand Up @@ -222,10 +311,10 @@ export function parseRequest(event: Event, req: ExpressRequest, options?: ParseR
}

if (options.request) {
// if the option value is `true`, use the default set of keys by not passing anything to `extractNodeRequestData()`
// if the option value is `true`, use the default set of keys by not passing anything to `extractRequestData()`
const extractedRequestData = Array.isArray(options.request)
? extractNodeRequestData(req, options.request)
: extractNodeRequestData(req);
? extractRequestData(req, options.request)
: extractRequestData(req);
event.request = {
...event.request,
...extractedRequestData,
Expand Down
Loading