import { _optionalChain } from '@sentry/utils'; import { hasTracingEnabled, continueTrace, startTransaction, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getCurrentScope, setHttpStatus, getClient, flush, runWithAsyncContext, withScope, getActiveSpan, captureException } from '@sentry/core'; import { isString, extractPathForTransaction, extractRequestData, addRequestDataToTransaction, logger, dropUndefinedKeys } from '@sentry/utils'; import { DEBUG_BUILD } from './debug-build.js'; import { isAutoSessionTrackingEnabled } from './sdk.js'; import { trpcMiddleware as trpcMiddleware$1 } from './trpc.js'; export { extractRequestData, parseRequest } from './requestDataDeprecated.js'; /** * Express-compatible tracing handler. * @see Exposed as `Handlers.tracingHandler` */ function tracingHandler() { return function sentryTracingMiddleware( req, res, next, ) { const options = _optionalChain([getClient, 'call', _ => _(), 'optionalAccess', _2 => _2.getOptions, 'call', _3 => _3()]); if ( !options || options.instrumenter !== 'sentry' || _optionalChain([req, 'access', _4 => _4.method, 'optionalAccess', _5 => _5.toUpperCase, 'call', _6 => _6()]) === 'OPTIONS' || _optionalChain([req, 'access', _7 => _7.method, 'optionalAccess', _8 => _8.toUpperCase, 'call', _9 => _9()]) === 'HEAD' ) { return next(); } const sentryTrace = req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined; const baggage = _optionalChain([req, 'access', _10 => _10.headers, 'optionalAccess', _11 => _11.baggage]); if (!hasTracingEnabled(options)) { return next(); } const [name, source] = extractPathForTransaction(req, { path: true, method: true }); const transaction = continueTrace({ sentryTrace, baggage }, ctx => // TODO: Refactor this to use `startSpan()` // eslint-disable-next-line deprecation/deprecation startTransaction( { name, op: 'http.server', origin: 'auto.http.node.tracingHandler', ...ctx, data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, }, metadata: { // eslint-disable-next-line deprecation/deprecation ...ctx.metadata, // The request should already have been stored in `scope.sdkProcessingMetadata` (which will become // `event.sdkProcessingMetadata` the same way the metadata here will) by `sentryRequestMiddleware`, but on the // off chance someone is using `sentryTracingMiddleware` without `sentryRequestMiddleware`, it doesn't hurt to // be sure request: req, }, }, // extra context passed to the tracesSampler { request: extractRequestData(req) }, ), ); // We put the transaction on the scope so users can attach children to it // eslint-disable-next-line deprecation/deprecation getCurrentScope().setSpan(transaction); // We also set __sentry_transaction on the response so people can grab the transaction there to add // spans to it later. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (res ).__sentry_transaction = transaction; res.once('finish', () => { // Push `transaction.finish` to the next event loop so open spans have a chance to finish before the transaction // closes setImmediate(() => { // eslint-disable-next-line deprecation/deprecation addRequestDataToTransaction(transaction, req); setHttpStatus(transaction, res.statusCode); transaction.end(); }); }); next(); }; } /** * Backwards compatibility shim which can be removed in v8. Forces the given options to follow the * `AddRequestDataToEventOptions` interface. * * TODO (v8): Get rid of this, and stop passing `requestDataOptionsFromExpressHandler` to `setSDKProcessingMetadata`. */ function convertReqHandlerOptsToAddReqDataOpts( reqHandlerOptions = {}, ) { let addRequestDataOptions; if ('include' in reqHandlerOptions) { addRequestDataOptions = { include: reqHandlerOptions.include }; } else { // eslint-disable-next-line deprecation/deprecation const { ip, request, transaction, user } = reqHandlerOptions ; if (ip || request || transaction || user) { addRequestDataOptions = { include: dropUndefinedKeys({ ip, request, transaction, user }) }; } } return addRequestDataOptions; } /** * Express compatible request handler. * @see Exposed as `Handlers.requestHandler` */ function requestHandler( options, ) { // TODO (v8): Get rid of this const requestDataOptions = convertReqHandlerOptsToAddReqDataOpts(options); const client = getClient(); // Initialise an instance of SessionFlusher on the client when `autoSessionTracking` is enabled and the // `requestHandler` middleware is used indicating that we are running in SessionAggregates mode if (client && isAutoSessionTrackingEnabled(client)) { client.initSessionFlusher(); // If Scope contains a Single mode Session, it is removed in favor of using Session Aggregates mode const scope = getCurrentScope(); if (scope.getSession()) { scope.setSession(); } } return function sentryRequestMiddleware( req, res, next, ) { if (options && options.flushTimeout && options.flushTimeout > 0) { // eslint-disable-next-line @typescript-eslint/unbound-method const _end = res.end; res.end = function (chunk, encoding, cb) { void flush(options.flushTimeout) .then(() => { _end.call(this, chunk, encoding, cb); }) .then(null, e => { DEBUG_BUILD && logger.error(e); _end.call(this, chunk, encoding, cb); }); }; } runWithAsyncContext(() => { const scope = getCurrentScope(); scope.setSDKProcessingMetadata({ request: req, // TODO (v8): Stop passing this requestDataOptionsFromExpressHandler: requestDataOptions, }); const client = getClient(); if (isAutoSessionTrackingEnabled(client)) { // Set `status` of `RequestSession` to Ok, at the beginning of the request scope.setRequestSession({ status: 'ok' }); } res.once('finish', () => { const client = getClient(); if (isAutoSessionTrackingEnabled(client)) { setImmediate(() => { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (client && (client )._captureRequestSession) { // Calling _captureRequestSession to capture request session at the end of the request by incrementing // the correct SessionAggregates bucket i.e. crashed, errored or exited // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (client )._captureRequestSession(); } }); } }); next(); }); }; } /** JSDoc */ /** JSDoc */ function getStatusCodeFromResponse(error) { const 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) { const 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, ) { const shouldHandleError = (options && options.shouldHandleError) || defaultShouldHandleError; if (shouldHandleError(error)) { withScope(_scope => { // The request should already have been stored in `scope.sdkProcessingMetadata` by `sentryRequestMiddleware`, // but on the off chance someone is using `sentryErrorMiddleware` without `sentryRequestMiddleware`, it doesn't // hurt to be sure _scope.setSDKProcessingMetadata({ request: _req }); // For some reason we need to set the transaction on the scope again // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const transaction = (res ).__sentry_transaction ; if (transaction && !getActiveSpan()) { // eslint-disable-next-line deprecation/deprecation _scope.setSpan(transaction); } const client = getClient(); if (client && isAutoSessionTrackingEnabled(client)) { // Check if the `SessionFlusher` is instantiated on the client to go into this branch that marks the // `requestSession.status` as `Crashed`, and this check is necessary because the `SessionFlusher` is only // instantiated when the the`requestHandler` middleware is initialised, which indicates that we should be // running in SessionAggregates mode // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const isSessionAggregatesMode = (client )._sessionFlusher !== undefined; if (isSessionAggregatesMode) { const requestSession = _scope.getRequestSession(); // If an error bubbles to the `errorHandler`, then this is an unhandled error, and should be reported as a // Crashed session. The `_requestSession.status` is checked to ensure that this error is happening within // the bounds of a request, and if so the status is updated if (requestSession && requestSession.status !== undefined) { requestSession.status = 'crashed'; } } } const eventId = captureException(error, { mechanism: { type: 'middleware', handled: false } }); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (res ).sentry = eventId; next(error); }); return; } next(error); }; } /** * Sentry tRPC middleware that names the handling transaction after the called procedure. * * Use the Sentry tRPC middleware in combination with the Sentry server integration, * e.g. Express Request Handlers or Next.js SDK. * * @deprecated Please use the top level export instead: * ``` * // OLD * import * as Sentry from '@sentry/node'; * Sentry.Handlers.trpcMiddleware(); * * // NEW * import * as Sentry from '@sentry/node'; * Sentry.trpcMiddleware(); * ``` */ // eslint-disable-next-line deprecation/deprecation const trpcMiddleware = trpcMiddleware$1; export { errorHandler, requestHandler, tracingHandler, trpcMiddleware }; //# sourceMappingURL=handlers.js.map