UNPKG

9.88 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 * @param event Will be mutated and enriched with req data
160 * @param req Request object
161 * @param options object containing flags to enable functionality
162 * @hidden
163 */
164function parseRequest(event, req, options) {
165 // tslint:disable-next-line:no-parameter-reassignment
166 options = tslib_1.__assign({ request: true, serverName: true, transaction: true, user: true, version: true }, options);
167 if (options.version) {
168 event.extra = tslib_1.__assign({}, event.extra, { node: global.process.version });
169 }
170 if (options.request) {
171 event.request = tslib_1.__assign({}, event.request, extractRequestData(req, options.request));
172 }
173 if (options.serverName && !event.server_name) {
174 event.server_name = global.process.env.SENTRY_NAME || os.hostname();
175 }
176 if (options.user && req.user) {
177 event.user = tslib_1.__assign({}, event.user, extractUserData(req, options.user));
178 }
179 if (options.transaction && !event.transaction) {
180 var transaction = extractTransaction(req, options.transaction);
181 if (transaction) {
182 event.transaction = transaction;
183 }
184 }
185 return event;
186}
187exports.parseRequest = parseRequest;
188/**
189 * Express compatible request handler.
190 * @see Exposed as `Handlers.requestHandler`
191 */
192function requestHandler(options) {
193 return function sentryRequestMiddleware(req, res, next) {
194 if (options && options.flushTimeout && options.flushTimeout > 0) {
195 // tslint:disable-next-line: no-unbound-method
196 var _end_1 = res.end;
197 res.end = function (chunk, encoding, cb) {
198 var _this = this;
199 sdk_1.flush(options.flushTimeout)
200 .then(function () {
201 _end_1.call(_this, chunk, encoding, cb);
202 })
203 .then(null, function (e) {
204 utils_1.logger.error(e);
205 });
206 };
207 }
208 var local = domain.create();
209 local.add(req);
210 local.add(res);
211 local.on('error', next);
212 local.run(function () {
213 core_1.getCurrentHub().configureScope(function (scope) {
214 return scope.addEventProcessor(function (event) { return parseRequest(event, req, options); });
215 });
216 next();
217 });
218 };
219}
220exports.requestHandler = requestHandler;
221/** JSDoc */
222function getStatusCodeFromResponse(error) {
223 var statusCode = error.status || error.statusCode || error.status_code || (error.output && error.output.statusCode);
224 return statusCode ? parseInt(statusCode, 10) : 500;
225}
226/** Returns true if response code is internal server error */
227function defaultShouldHandleError(error) {
228 var status = getStatusCodeFromResponse(error);
229 return status >= 500;
230}
231/**
232 * Express compatible error handler.
233 * @see Exposed as `Handlers.errorHandler`
234 */
235function errorHandler(options) {
236 return function sentryErrorMiddleware(error, req, res, next) {
237 var shouldHandleError = (options && options.shouldHandleError) || defaultShouldHandleError;
238 if (shouldHandleError(error)) {
239 core_1.withScope(function (scope) {
240 if (req.headers && utils_1.isString(req.headers['sentry-trace'])) {
241 var span = apm_1.Span.fromTraceparent(req.headers['sentry-trace']);
242 scope.setSpan(span);
243 }
244 var eventId = core_1.captureException(error);
245 res.sentry = eventId;
246 next(error);
247 });
248 return;
249 }
250 next(error);
251 };
252}
253exports.errorHandler = errorHandler;
254/**
255 * @hidden
256 */
257function logAndExitProcess(error) {
258 console.error(error && error.stack ? error.stack : error);
259 var client = core_1.getCurrentHub().getClient();
260 if (client === undefined) {
261 utils_1.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 utils_1.forget(client.close(timeout).then(function (result) {
269 if (!result) {
270 utils_1.logger.warn('We reached the timeout for emptying the request buffer, still exiting now!');
271 }
272 global.process.exit(1);
273 }));
274}
275exports.logAndExitProcess = logAndExitProcess;
276//# sourceMappingURL=handlers.js.map
\No newline at end of file