123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- 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, normalize, isThenable, dropUndefinedKeys } from '@sentry/utils';
- import { DEBUG_BUILD } from './debug-build.js';
- import { isAutoSessionTrackingEnabled } from './sdk.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(() => {
- 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.
- */
- function trpcMiddleware(options = {}) {
- return function ({ path, type, next, rawInput }) {
- const clientOptions = _optionalChain([getClient, 'call', _12 => _12(), 'optionalAccess', _13 => _13.getOptions, 'call', _14 => _14()]);
- // eslint-disable-next-line deprecation/deprecation
- const sentryTransaction = getCurrentScope().getTransaction();
- if (sentryTransaction) {
- sentryTransaction.updateName(`trpc/${path}`);
- sentryTransaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
- sentryTransaction.op = 'rpc.server';
- const trpcContext = {
- procedure_type: type,
- };
- if (options.attachRpcInput !== undefined ? options.attachRpcInput : _optionalChain([clientOptions, 'optionalAccess', _15 => _15.sendDefaultPii])) {
- trpcContext.input = normalize(rawInput);
- }
- // TODO: Can we rewrite this to an attribute? Or set this on the scope?
- // eslint-disable-next-line deprecation/deprecation
- sentryTransaction.setContext('trpc', trpcContext);
- }
- function captureIfError(nextResult) {
- if (!nextResult.ok) {
- captureException(nextResult.error, { mechanism: { handled: false, data: { function: 'trpcMiddleware' } } });
- }
- }
- let maybePromiseResult;
- try {
- maybePromiseResult = next();
- } catch (e) {
- captureException(e, { mechanism: { handled: false, data: { function: 'trpcMiddleware' } } });
- throw e;
- }
- if (isThenable(maybePromiseResult)) {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- Promise.resolve(maybePromiseResult).then(
- nextResult => {
- captureIfError(nextResult );
- },
- e => {
- captureException(e, { mechanism: { handled: false, data: { function: 'trpcMiddleware' } } });
- },
- );
- } else {
- captureIfError(maybePromiseResult );
- }
- // We return the original promise just to be safe.
- return maybePromiseResult;
- };
- }
- export { errorHandler, requestHandler, tracingHandler, trpcMiddleware };
- //# sourceMappingURL=handlers.js.map
|