@sentry/node
Version:
Offical Sentry SDK for Node.js
306 lines • 11.2 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var apm_1 = require("@sentry/apm");
var core_1 = require("@sentry/core");
var utils_1 = require("@sentry/utils");
var cookie = require("cookie");
var domain = require("domain");
var os = require("os");
var url = require("url");
var sdk_1 = require("./sdk");
var DEFAULT_SHUTDOWN_TIMEOUT = 2000;
/**
* Express compatible tracing handler.
* @see Exposed as `Handlers.tracingHandler`
*/
function tracingHandler() {
return function sentryTracingMiddleware(req, res, next) {
// TODO: At this point req.route.path we use in `extractTransaction` is not available
// but `req.path` or `req.url` should do the job as well. We could unify this here.
var reqMethod = (req.method || '').toUpperCase();
var reqUrl = req.url;
var traceId;
var parentSpanId;
var sampled;
// If there is a trace header set, we extract the data from it and set the span on the scope
// to be the origin an created transaction set the parent_span_id / trace_id
if (req.headers && utils_1.isString(req.headers['sentry-trace'])) {
var span = apm_1.Span.fromTraceparent(req.headers['sentry-trace']);
if (span) {
traceId = span.traceId;
parentSpanId = span.parentSpanId;
sampled = span.sampled;
}
}
var transaction = core_1.startTransaction({
name: reqMethod + " " + reqUrl,
op: 'http.server',
parentSpanId: parentSpanId,
sampled: sampled,
traceId: traceId,
});
// We put the transaction on the scope so users can attach children to it
core_1.getCurrentHub().configureScope(function (scope) {
scope.setSpan(transaction);
});
// We also set __sentry_transaction on the response so people can grab the transaction there to add
// spans to it later.
res.__sentry_transaction = transaction;
res.once('finish', function () {
transaction.setHttpStatus(res.statusCode);
transaction.finish();
});
next();
};
}
exports.tracingHandler = tracingHandler;
/** JSDoc */
function extractTransaction(req, type) {
try {
// Express.js shape
var request = req;
var routePath = void 0;
try {
routePath = url.parse(request.originalUrl || request.url).pathname;
}
catch (_oO) {
routePath = request.route.path;
}
switch (type) {
case 'path': {
return routePath;
}
case 'handler': {
return request.route.stack[0].name;
}
case 'methodPath':
default: {
var method = request.method.toUpperCase();
return method + "|" + routePath;
}
}
}
catch (_oO) {
return undefined;
}
}
/** Default request keys that'll be used to extract data from the request */
var DEFAULT_REQUEST_KEYS = ['cookies', 'data', 'headers', 'method', 'query_string', 'url'];
/** JSDoc */
function extractRequestData(req, keys) {
var request = {};
var attributes = Array.isArray(keys) ? keys : DEFAULT_REQUEST_KEYS;
// headers:
// node, express: req.headers
// koa: req.header
var headers = (req.headers || req.header || {});
// method:
// node, express, koa: req.method
var method = req.method;
// host:
// express: req.hostname in > 4 and req.host in < 4
// koa: req.host
// node: req.headers.host
var host = req.hostname || req.host || headers.host || '<no host>';
// protocol:
// node: <n/a>
// express, koa: req.protocol
var protocol = req.protocol === 'https' || req.secure || (req.socket || {}).encrypted
? 'https'
: 'http';
// url (including path and query string):
// node, express: req.originalUrl
// koa: req.url
var originalUrl = (req.originalUrl || req.url);
// absolute url
var absoluteUrl = protocol + "://" + host + originalUrl;
attributes.forEach(function (key) {
switch (key) {
case 'headers':
request.headers = headers;
break;
case 'method':
request.method = method;
break;
case 'url':
request.url = absoluteUrl;
break;
case 'cookies':
// cookies:
// node, express, koa: req.headers.cookie
request.cookies = cookie.parse(headers.cookie || '');
break;
case 'query_string':
// query string:
// node: req.url (raw)
// express, koa: req.query
request.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) {
request.data = utils_1.isString(req.body) ? req.body : JSON.stringify(utils_1.normalize(req.body));
}
break;
default:
if ({}.hasOwnProperty.call(req, key)) {
request[key] = req[key];
}
}
});
return request;
}
/** Default user keys that'll be used to extract data from the request */
var DEFAULT_USER_KEYS = ['id', 'username', 'email'];
/** JSDoc */
function extractUserData(user, keys) {
var extractedUser = {};
var attributes = Array.isArray(keys) ? keys : DEFAULT_USER_KEYS;
attributes.forEach(function (key) {
if (user && key in user) {
extractedUser[key] = user[key];
}
});
return extractedUser;
}
/**
* Enriches passed event with request data.
*
* @param event Will be mutated and enriched with req data
* @param req Request object
* @param options object containing flags to enable functionality
* @hidden
*/
function parseRequest(event, req, options) {
// tslint:disable-next-line:no-parameter-reassignment
options = tslib_1.__assign({ ip: false, request: true, serverName: true, transaction: true, user: true, version: true }, options);
if (options.version) {
event.contexts = tslib_1.__assign({}, event.contexts, { runtime: {
name: 'node',
version: global.process.version,
} });
}
if (options.request) {
event.request = tslib_1.__assign({}, event.request, extractRequestData(req, options.request));
}
if (options.serverName && !event.server_name) {
event.server_name = global.process.env.SENTRY_NAME || os.hostname();
}
if (options.user) {
var extractedUser = req.user && utils_1.isPlainObject(req.user) ? extractUserData(req.user, options.user) : {};
if (Object.keys(extractedUser)) {
event.user = tslib_1.__assign({}, event.user, extractedUser);
}
}
// client ip:
// node: req.connection.remoteAddress
// express, koa: req.ip
if (options.ip) {
var ip = req.ip || (req.connection && req.connection.remoteAddress);
if (ip) {
event.user = tslib_1.__assign({}, event.user, { ip_address: ip });
}
}
if (options.transaction && !event.transaction) {
var transaction = extractTransaction(req, options.transaction);
if (transaction) {
event.transaction = transaction;
}
}
return event;
}
exports.parseRequest = parseRequest;
/**
* Express compatible request handler.
* @see Exposed as `Handlers.requestHandler`
*/
function requestHandler(options) {
return function sentryRequestMiddleware(req, res, next) {
if (options && options.flushTimeout && options.flushTimeout > 0) {
// tslint:disable-next-line: no-unbound-method
var _end_1 = res.end;
res.end = function (chunk, encoding, cb) {
var _this = this;
sdk_1.flush(options.flushTimeout)
.then(function () {
_end_1.call(_this, chunk, encoding, cb);
})
.then(null, function (e) {
utils_1.logger.error(e);
});
};
}
var local = domain.create();
local.add(req);
local.add(res);
local.on('error', next);
local.run(function () {
core_1.getCurrentHub().configureScope(function (scope) {
return scope.addEventProcessor(function (event) { return parseRequest(event, req, options); });
});
next();
});
};
}
exports.requestHandler = requestHandler;
/** JSDoc */
function getStatusCodeFromResponse(error) {
var statusCode = error.status || error.statusCode || error.status_code || (error.output && error.output.statusCode);
return statusCode ? parseInt(statusCode, 10) : 500;
}
/** Returns true if response code is internal server error */
function defaultShouldHandleError(error) {
var status = getStatusCodeFromResponse(error);
return status >= 500;
}
/**
* Express compatible error handler.
* @see Exposed as `Handlers.errorHandler`
*/
function errorHandler(options) {
return function sentryErrorMiddleware(error, _req, res, next) {
var shouldHandleError = (options && options.shouldHandleError) || defaultShouldHandleError;
if (shouldHandleError(error)) {
core_1.withScope(function (_scope) {
// For some reason we need to set the transaction on the scope again
var transaction = res.__sentry_transaction;
if (transaction && _scope.getSpan() === undefined) {
_scope.setSpan(transaction);
}
var eventId = core_1.captureException(error);
res.sentry = eventId;
next(error);
});
return;
}
next(error);
};
}
exports.errorHandler = errorHandler;
/**
* @hidden
*/
function logAndExitProcess(error) {
console.error(error && error.stack ? error.stack : error);
var client = core_1.getCurrentHub().getClient();
if (client === undefined) {
utils_1.logger.warn('No NodeClient was defined, we are exiting the process now.');
global.process.exit(1);
return;
}
var options = client.getOptions();
var timeout = (options && options.shutdownTimeout && options.shutdownTimeout > 0 && options.shutdownTimeout) ||
DEFAULT_SHUTDOWN_TIMEOUT;
utils_1.forget(client.close(timeout).then(function (result) {
if (!result) {
utils_1.logger.warn('We reached the timeout for emptying the request buffer, still exiting now!');
}
global.process.exit(1);
}));
}
exports.logAndExitProcess = logAndExitProcess;
//# sourceMappingURL=handlers.js.map