UNPKG

9.85 kBJavaScriptView Raw
1import * as tslib_1 from "tslib";
2import { Span } from '@sentry/apm';
3import { captureException, getCurrentHub, withScope } from '@sentry/core';
4import { forget, isString, logger, normalize } from '@sentry/utils';
5import * as cookie from 'cookie';
6import * as domain from 'domain';
7import * as os from 'os';
8import * as url from 'url';
9import { flush } from './sdk';
10var DEFAULT_SHUTDOWN_TIMEOUT = 2000;
11/**
12 * Express compatible tracing handler.
13 * @see Exposed as `Handlers.tracingHandler`
14 */
15export function tracingHandler() {
16 return function sentryTracingMiddleware(req, res, next) {
17 // TODO: At this point req.route.path we use in `extractTransaction` is not available
18 // but `req.path` or `req.url` should do the job as well. We could unify this here.
19 var reqMethod = (req.method || '').toUpperCase();
20 var reqUrl = req.url;
21 var hub = getCurrentHub();
22 var transaction = hub.startSpan({
23 transaction: reqMethod + "|" + reqUrl,
24 });
25 hub.configureScope(function (scope) {
26 scope.setSpan(transaction);
27 });
28 res.once('finish', function () {
29 transaction.setHttpStatus(res.statusCode);
30 transaction.finish();
31 });
32 next();
33 };
34}
35/** JSDoc */
36function extractTransaction(req, type) {
37 try {
38 // Express.js shape
39 var request = req;
40 switch (type) {
41 case 'path': {
42 return request.route.path;
43 }
44 case 'handler': {
45 return request.route.stack[0].name;
46 }
47 case 'methodPath':
48 default: {
49 var method = request.method.toUpperCase();
50 var path = request.route.path;
51 return method + "|" + path;
52 }
53 }
54 }
55 catch (_oO) {
56 return undefined;
57 }
58}
59/** Default request keys that'll be used to extract data from the request */
60var DEFAULT_REQUEST_KEYS = ['cookies', 'data', 'headers', 'method', 'query_string', 'url'];
61/** JSDoc */
62function extractRequestData(req, keys) {
63 var request = {};
64 var attributes = Array.isArray(keys) ? keys : DEFAULT_REQUEST_KEYS;
65 // headers:
66 // node, express: req.headers
67 // koa: req.header
68 var headers = (req.headers || req.header || {});
69 // method:
70 // node, express, koa: req.method
71 var method = req.method;
72 // host:
73 // express: req.hostname in > 4 and req.host in < 4
74 // koa: req.host
75 // node: req.headers.host
76 var host = req.hostname || req.host || headers.host || '<no host>';
77 // protocol:
78 // node: <n/a>
79 // express, koa: req.protocol
80 var protocol = req.protocol === 'https' || req.secure || (req.socket || {}).encrypted
81 ? 'https'
82 : 'http';
83 // url (including path and query string):
84 // node, express: req.originalUrl
85 // koa: req.url
86 var originalUrl = (req.originalUrl || req.url);
87 // absolute url
88 var absoluteUrl = protocol + "://" + host + originalUrl;
89 attributes.forEach(function (key) {
90 switch (key) {
91 case 'headers':
92 request.headers = headers;
93 break;
94 case 'method':
95 request.method = method;
96 break;
97 case 'url':
98 request.url = absoluteUrl;
99 break;
100 case 'cookies':
101 // cookies:
102 // node, express, koa: req.headers.cookie
103 request.cookies = cookie.parse(headers.cookie || '');
104 break;
105 case 'query_string':
106 // query string:
107 // node: req.url (raw)
108 // express, koa: req.query
109 request.query_string = url.parse(originalUrl || '', false).query;
110 break;
111 case 'data':
112 // body data:
113 // node, express, koa: req.body
114 var data = req.body;
115 if (method === 'GET' || method === 'HEAD') {
116 if (typeof data === 'undefined') {
117 data = '<unavailable>';
118 }
119 }
120 if (data && !isString(data)) {
121 // Make sure the request body is a string
122 data = JSON.stringify(normalize(data));
123 }
124 request.data = data;
125 break;
126 default:
127 if ({}.hasOwnProperty.call(req, key)) {
128 request[key] = req[key];
129 }
130 }
131 });
132 return request;
133}
134/** Default user keys that'll be used to extract data from the request */
135var DEFAULT_USER_KEYS = ['id', 'username', 'email'];
136/** JSDoc */
137function extractUserData(user, keys) {
138 var extractedUser = {};
139 var attributes = Array.isArray(keys) ? keys : DEFAULT_USER_KEYS;
140 attributes.forEach(function (key) {
141 if (user && key in user) {
142 extractedUser[key] = user[key];
143 }
144 });
145 return extractedUser;
146}
147/**
148 * Enriches passed event with request data.
149 *
150 * @param event Will be mutated and enriched with req data
151 * @param req Request object
152 * @param options object containing flags to enable functionality
153 * @hidden
154 */
155export function parseRequest(event, req, options) {
156 // tslint:disable-next-line:no-parameter-reassignment
157 options = tslib_1.__assign({ ip: false, request: true, serverName: true, transaction: true, user: true, version: true }, options);
158 if (options.version) {
159 event.extra = tslib_1.__assign({}, event.extra, { node: global.process.version });
160 }
161 if (options.request) {
162 event.request = tslib_1.__assign({}, event.request, extractRequestData(req, options.request));
163 }
164 if (options.serverName && !event.server_name) {
165 event.server_name = global.process.env.SENTRY_NAME || os.hostname();
166 }
167 if (options.user) {
168 var extractedUser = req.user ? extractUserData(req.user, options.user) : {};
169 if (Object.keys(extractedUser)) {
170 event.user = tslib_1.__assign({}, event.user, extractedUser);
171 }
172 }
173 // client ip:
174 // node: req.connection.remoteAddress
175 // express, koa: req.ip
176 if (options.ip) {
177 var ip = req.ip || (req.connection && req.connection.remoteAddress);
178 if (ip) {
179 event.user = tslib_1.__assign({}, event.user, { ip_address: ip });
180 }
181 }
182 if (options.transaction && !event.transaction) {
183 var transaction = extractTransaction(req, options.transaction);
184 if (transaction) {
185 event.transaction = transaction;
186 }
187 }
188 return event;
189}
190/**
191 * Express compatible request handler.
192 * @see Exposed as `Handlers.requestHandler`
193 */
194export function requestHandler(options) {
195 return function sentryRequestMiddleware(req, res, next) {
196 if (options && options.flushTimeout && options.flushTimeout > 0) {
197 // tslint:disable-next-line: no-unbound-method
198 var _end_1 = res.end;
199 res.end = function (chunk, encoding, cb) {
200 var _this = this;
201 flush(options.flushTimeout)
202 .then(function () {
203 _end_1.call(_this, chunk, encoding, cb);
204 })
205 .then(null, function (e) {
206 logger.error(e);
207 });
208 };
209 }
210 var local = domain.create();
211 local.add(req);
212 local.add(res);
213 local.on('error', next);
214 local.run(function () {
215 getCurrentHub().configureScope(function (scope) {
216 return scope.addEventProcessor(function (event) { return parseRequest(event, req, options); });
217 });
218 next();
219 });
220 };
221}
222/** JSDoc */
223function getStatusCodeFromResponse(error) {
224 var statusCode = error.status || error.statusCode || error.status_code || (error.output && error.output.statusCode);
225 return statusCode ? parseInt(statusCode, 10) : 500;
226}
227/** Returns true if response code is internal server error */
228function defaultShouldHandleError(error) {
229 var status = getStatusCodeFromResponse(error);
230 return status >= 500;
231}
232/**
233 * Express compatible error handler.
234 * @see Exposed as `Handlers.errorHandler`
235 */
236export function errorHandler(options) {
237 return function sentryErrorMiddleware(error, req, res, next) {
238 var shouldHandleError = (options && options.shouldHandleError) || defaultShouldHandleError;
239 if (shouldHandleError(error)) {
240 withScope(function (scope) {
241 if (req.headers && isString(req.headers['sentry-trace'])) {
242 var span = Span.fromTraceparent(req.headers['sentry-trace']);
243 scope.setSpan(span);
244 }
245 var eventId = captureException(error);
246 res.sentry = eventId;
247 next(error);
248 });
249 return;
250 }
251 next(error);
252 };
253}
254/**
255 * @hidden
256 */
257export function logAndExitProcess(error) {
258 console.error(error && error.stack ? error.stack : error);
259 var client = getCurrentHub().getClient();
260 if (client === undefined) {
261 logger.warn('No NodeClient was defined, we are exiting the process now.');
262 global.process.exit(1);
263 return;
264 }
265 var options = client.getOptions();
266 var timeout = (options && options.shutdownTimeout && options.shutdownTimeout > 0 && options.shutdownTimeout) ||
267 DEFAULT_SHUTDOWN_TIMEOUT;
268 forget(client.close(timeout).then(function (result) {
269 if (!result) {
270 logger.warn('We reached the timeout for emptying the request buffer, still exiting now!');
271 }
272 global.process.exit(1);
273 }));
274}
275//# sourceMappingURL=handlers.js.map
\No newline at end of file