diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index 4e54406704e6..5dfcb7bf0a13 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -10,6 +10,7 @@ import { extractTraceparentData, isString, logger, + normalize, } from '@sentry/utils'; import * as domain from 'domain'; import type * as http from 'http'; @@ -315,6 +316,49 @@ export function errorHandler(options?: { }; } +interface SentryTrpcMiddlewareOptions { + /** Whether to include procedure inputs in reported events. Defaults to `false`. */ + attachRpcInput?: boolean; +} + +interface TrpcMiddlewareArguments { + path: string; + type: 'query' | 'mutation' | 'subscription'; + next: () => T; + rawInput: unknown; +} + +/** + * 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. + */ +export async function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { + return function ({ path, type, next, rawInput }: TrpcMiddlewareArguments): T { + const hub = getCurrentHub(); + const clientOptions = hub.getClient()?.getOptions(); + const sentryTransaction = hub.getScope()?.getTransaction(); + + if (sentryTransaction) { + sentryTransaction.setName(`trcp/${path}`, 'route'); + sentryTransaction.op = 'rpc.server'; + + const trpcContext: Record = { + procedure_type: type, + }; + + if (options.attachRpcInput !== undefined ? options.attachRpcInput : clientOptions?.sendDefaultPii) { + trpcContext.input = normalize(rawInput); + } + + sentryTransaction.setContext('trpc', trpcContext); + } + + return next(); + }; +} + // TODO (v8 / #5257): Remove this // eslint-disable-next-line deprecation/deprecation export type { ParseRequestOptions, ExpressRequest } from './requestDataDeprecated';