UNPKG

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