handlers.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. var {
  2. _optionalChain
  3. } = require('@sentry/utils');
  4. Object.defineProperty(exports, '__esModule', { value: true });
  5. const core = require('@sentry/core');
  6. const utils = require('@sentry/utils');
  7. const debugBuild = require('./debug-build.js');
  8. const sdk = require('./sdk.js');
  9. const requestDataDeprecated = require('./requestDataDeprecated.js');
  10. /**
  11. * Express-compatible tracing handler.
  12. * @see Exposed as `Handlers.tracingHandler`
  13. */
  14. function tracingHandler()
  15. {
  16. return function sentryTracingMiddleware(
  17. req,
  18. res,
  19. next,
  20. ) {
  21. const options = _optionalChain([core.getClient, 'call', _ => _(), 'optionalAccess', _2 => _2.getOptions, 'call', _3 => _3()]);
  22. if (
  23. !options ||
  24. options.instrumenter !== 'sentry' ||
  25. _optionalChain([req, 'access', _4 => _4.method, 'optionalAccess', _5 => _5.toUpperCase, 'call', _6 => _6()]) === 'OPTIONS' ||
  26. _optionalChain([req, 'access', _7 => _7.method, 'optionalAccess', _8 => _8.toUpperCase, 'call', _9 => _9()]) === 'HEAD'
  27. ) {
  28. return next();
  29. }
  30. const sentryTrace = req.headers && utils.isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined;
  31. const baggage = _optionalChain([req, 'access', _10 => _10.headers, 'optionalAccess', _11 => _11.baggage]);
  32. if (!core.hasTracingEnabled(options)) {
  33. return next();
  34. }
  35. const [name, source] = utils.extractPathForTransaction(req, { path: true, method: true });
  36. const transaction = core.continueTrace({ sentryTrace, baggage }, ctx =>
  37. // TODO: Refactor this to use `startSpan()`
  38. // eslint-disable-next-line deprecation/deprecation
  39. core.startTransaction(
  40. {
  41. name,
  42. op: 'http.server',
  43. origin: 'auto.http.node.tracingHandler',
  44. ...ctx,
  45. data: {
  46. [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
  47. },
  48. metadata: {
  49. // eslint-disable-next-line deprecation/deprecation
  50. ...ctx.metadata,
  51. // The request should already have been stored in `scope.sdkProcessingMetadata` (which will become
  52. // `event.sdkProcessingMetadata` the same way the metadata here will) by `sentryRequestMiddleware`, but on the
  53. // off chance someone is using `sentryTracingMiddleware` without `sentryRequestMiddleware`, it doesn't hurt to
  54. // be sure
  55. request: req,
  56. },
  57. },
  58. // extra context passed to the tracesSampler
  59. { request: utils.extractRequestData(req) },
  60. ),
  61. );
  62. // We put the transaction on the scope so users can attach children to it
  63. // eslint-disable-next-line deprecation/deprecation
  64. core.getCurrentScope().setSpan(transaction);
  65. // We also set __sentry_transaction on the response so people can grab the transaction there to add
  66. // spans to it later.
  67. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  68. (res ).__sentry_transaction = transaction;
  69. res.once('finish', () => {
  70. // Push `transaction.finish` to the next event loop so open spans have a chance to finish before the transaction
  71. // closes
  72. setImmediate(() => {
  73. utils.addRequestDataToTransaction(transaction, req);
  74. core.setHttpStatus(transaction, res.statusCode);
  75. transaction.end();
  76. });
  77. });
  78. next();
  79. };
  80. }
  81. /**
  82. * Backwards compatibility shim which can be removed in v8. Forces the given options to follow the
  83. * `AddRequestDataToEventOptions` interface.
  84. *
  85. * TODO (v8): Get rid of this, and stop passing `requestDataOptionsFromExpressHandler` to `setSDKProcessingMetadata`.
  86. */
  87. function convertReqHandlerOptsToAddReqDataOpts(
  88. reqHandlerOptions = {},
  89. ) {
  90. let addRequestDataOptions;
  91. if ('include' in reqHandlerOptions) {
  92. addRequestDataOptions = { include: reqHandlerOptions.include };
  93. } else {
  94. // eslint-disable-next-line deprecation/deprecation
  95. const { ip, request, transaction, user } = reqHandlerOptions ;
  96. if (ip || request || transaction || user) {
  97. addRequestDataOptions = { include: utils.dropUndefinedKeys({ ip, request, transaction, user }) };
  98. }
  99. }
  100. return addRequestDataOptions;
  101. }
  102. /**
  103. * Express compatible request handler.
  104. * @see Exposed as `Handlers.requestHandler`
  105. */
  106. function requestHandler(
  107. options,
  108. ) {
  109. // TODO (v8): Get rid of this
  110. const requestDataOptions = convertReqHandlerOptsToAddReqDataOpts(options);
  111. const client = core.getClient();
  112. // Initialise an instance of SessionFlusher on the client when `autoSessionTracking` is enabled and the
  113. // `requestHandler` middleware is used indicating that we are running in SessionAggregates mode
  114. if (client && sdk.isAutoSessionTrackingEnabled(client)) {
  115. client.initSessionFlusher();
  116. // If Scope contains a Single mode Session, it is removed in favor of using Session Aggregates mode
  117. const scope = core.getCurrentScope();
  118. if (scope.getSession()) {
  119. scope.setSession();
  120. }
  121. }
  122. return function sentryRequestMiddleware(
  123. req,
  124. res,
  125. next,
  126. ) {
  127. if (options && options.flushTimeout && options.flushTimeout > 0) {
  128. // eslint-disable-next-line @typescript-eslint/unbound-method
  129. const _end = res.end;
  130. res.end = function (chunk, encoding, cb) {
  131. void core.flush(options.flushTimeout)
  132. .then(() => {
  133. _end.call(this, chunk, encoding, cb);
  134. })
  135. .then(null, e => {
  136. debugBuild.DEBUG_BUILD && utils.logger.error(e);
  137. _end.call(this, chunk, encoding, cb);
  138. });
  139. };
  140. }
  141. core.runWithAsyncContext(() => {
  142. const scope = core.getCurrentScope();
  143. scope.setSDKProcessingMetadata({
  144. request: req,
  145. // TODO (v8): Stop passing this
  146. requestDataOptionsFromExpressHandler: requestDataOptions,
  147. });
  148. const client = core.getClient();
  149. if (sdk.isAutoSessionTrackingEnabled(client)) {
  150. // Set `status` of `RequestSession` to Ok, at the beginning of the request
  151. scope.setRequestSession({ status: 'ok' });
  152. }
  153. res.once('finish', () => {
  154. const client = core.getClient();
  155. if (sdk.isAutoSessionTrackingEnabled(client)) {
  156. setImmediate(() => {
  157. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  158. if (client && (client )._captureRequestSession) {
  159. // Calling _captureRequestSession to capture request session at the end of the request by incrementing
  160. // the correct SessionAggregates bucket i.e. crashed, errored or exited
  161. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  162. (client )._captureRequestSession();
  163. }
  164. });
  165. }
  166. });
  167. next();
  168. });
  169. };
  170. }
  171. /** JSDoc */
  172. /** JSDoc */
  173. function getStatusCodeFromResponse(error) {
  174. const statusCode = error.status || error.statusCode || error.status_code || (error.output && error.output.statusCode);
  175. return statusCode ? parseInt(statusCode , 10) : 500;
  176. }
  177. /** Returns true if response code is internal server error */
  178. function defaultShouldHandleError(error) {
  179. const status = getStatusCodeFromResponse(error);
  180. return status >= 500;
  181. }
  182. /**
  183. * Express compatible error handler.
  184. * @see Exposed as `Handlers.errorHandler`
  185. */
  186. function errorHandler(options
  187. )
  188. {
  189. return function sentryErrorMiddleware(
  190. error,
  191. _req,
  192. res,
  193. next,
  194. ) {
  195. const shouldHandleError = (options && options.shouldHandleError) || defaultShouldHandleError;
  196. if (shouldHandleError(error)) {
  197. core.withScope(_scope => {
  198. // The request should already have been stored in `scope.sdkProcessingMetadata` by `sentryRequestMiddleware`,
  199. // but on the off chance someone is using `sentryErrorMiddleware` without `sentryRequestMiddleware`, it doesn't
  200. // hurt to be sure
  201. _scope.setSDKProcessingMetadata({ request: _req });
  202. // For some reason we need to set the transaction on the scope again
  203. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  204. const transaction = (res ).__sentry_transaction ;
  205. if (transaction && !core.getActiveSpan()) {
  206. // eslint-disable-next-line deprecation/deprecation
  207. _scope.setSpan(transaction);
  208. }
  209. const client = core.getClient();
  210. if (client && sdk.isAutoSessionTrackingEnabled(client)) {
  211. // Check if the `SessionFlusher` is instantiated on the client to go into this branch that marks the
  212. // `requestSession.status` as `Crashed`, and this check is necessary because the `SessionFlusher` is only
  213. // instantiated when the the`requestHandler` middleware is initialised, which indicates that we should be
  214. // running in SessionAggregates mode
  215. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  216. const isSessionAggregatesMode = (client )._sessionFlusher !== undefined;
  217. if (isSessionAggregatesMode) {
  218. const requestSession = _scope.getRequestSession();
  219. // If an error bubbles to the `errorHandler`, then this is an unhandled error, and should be reported as a
  220. // Crashed session. The `_requestSession.status` is checked to ensure that this error is happening within
  221. // the bounds of a request, and if so the status is updated
  222. if (requestSession && requestSession.status !== undefined) {
  223. requestSession.status = 'crashed';
  224. }
  225. }
  226. }
  227. const eventId = core.captureException(error, { mechanism: { type: 'middleware', handled: false } });
  228. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  229. (res ).sentry = eventId;
  230. next(error);
  231. });
  232. return;
  233. }
  234. next(error);
  235. };
  236. }
  237. /**
  238. * Sentry tRPC middleware that names the handling transaction after the called procedure.
  239. *
  240. * Use the Sentry tRPC middleware in combination with the Sentry server integration,
  241. * e.g. Express Request Handlers or Next.js SDK.
  242. */
  243. function trpcMiddleware(options = {}) {
  244. return function ({ path, type, next, rawInput }) {
  245. const clientOptions = _optionalChain([core.getClient, 'call', _12 => _12(), 'optionalAccess', _13 => _13.getOptions, 'call', _14 => _14()]);
  246. // eslint-disable-next-line deprecation/deprecation
  247. const sentryTransaction = core.getCurrentScope().getTransaction();
  248. if (sentryTransaction) {
  249. sentryTransaction.updateName(`trpc/${path}`);
  250. sentryTransaction.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
  251. sentryTransaction.op = 'rpc.server';
  252. const trpcContext = {
  253. procedure_type: type,
  254. };
  255. if (options.attachRpcInput !== undefined ? options.attachRpcInput : _optionalChain([clientOptions, 'optionalAccess', _15 => _15.sendDefaultPii])) {
  256. trpcContext.input = utils.normalize(rawInput);
  257. }
  258. // TODO: Can we rewrite this to an attribute? Or set this on the scope?
  259. // eslint-disable-next-line deprecation/deprecation
  260. sentryTransaction.setContext('trpc', trpcContext);
  261. }
  262. function captureIfError(nextResult) {
  263. if (!nextResult.ok) {
  264. core.captureException(nextResult.error, { mechanism: { handled: false, data: { function: 'trpcMiddleware' } } });
  265. }
  266. }
  267. let maybePromiseResult;
  268. try {
  269. maybePromiseResult = next();
  270. } catch (e) {
  271. core.captureException(e, { mechanism: { handled: false, data: { function: 'trpcMiddleware' } } });
  272. throw e;
  273. }
  274. if (utils.isThenable(maybePromiseResult)) {
  275. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  276. Promise.resolve(maybePromiseResult).then(
  277. nextResult => {
  278. captureIfError(nextResult );
  279. },
  280. e => {
  281. core.captureException(e, { mechanism: { handled: false, data: { function: 'trpcMiddleware' } } });
  282. },
  283. );
  284. } else {
  285. captureIfError(maybePromiseResult );
  286. }
  287. // We return the original promise just to be safe.
  288. return maybePromiseResult;
  289. };
  290. }
  291. exports.extractRequestData = requestDataDeprecated.extractRequestData;
  292. exports.parseRequest = requestDataDeprecated.parseRequest;
  293. exports.errorHandler = errorHandler;
  294. exports.requestHandler = requestHandler;
  295. exports.tracingHandler = tracingHandler;
  296. exports.trpcMiddleware = trpcMiddleware;
  297. //# sourceMappingURL=handlers.js.map