import { startSpan, captureException } from "@sentry/nextjs"; import { redactSensitiveData } from "./redactSensitiveData"; /* WHEN TO USE We ran a script that performs a simple mathematical calculation within a loop of 1000000 iterations. Our results were: Plain execution time: 441, Monitored execution time: 8094. This suggests that using these wrappers within large loops can incur significant overhead and is thus not recommended. For smaller loops, the cost incurred may not be very significant on an absolute scale considering that a million monitored iterations only took roughly 8 seconds when monitored. */ /** * @deprecated Use withReporting instead. This function will be removed in a future version. * Example: withReporting(myFunction, "myFunction")(...args) */ const monitorCallbackAsync = async any>( cb: T, ...args: Parameters ): Promise> => { // Check if Sentry set if (!process.env.NEXT_PUBLIC_SENTRY_DSN || !process.env.SENTRY_TRACES_SAMPLE_RATE) { return (await cb(...args)) as ReturnType; } return await startSpan({ name: cb.name }, async () => { try { const result = await cb(...args); return result as ReturnType; } catch (error) { captureException(error); throw error; } }); }; /** * @deprecated Use withReporting instead. This function will be removed in a future version. * Example: withReporting(myFunction, "myFunction")(...args) */ const monitorCallbackSync = any>( cb: T, ...args: Parameters ): ReturnType => { // Check if Sentry set if (!process.env.NEXT_PUBLIC_SENTRY_DSN || !process.env.SENTRY_TRACES_SAMPLE_RATE) return cb(...args) as ReturnType; return startSpan({ name: cb.name }, () => { try { const result = cb(...args); return result as ReturnType; } catch (error) { captureException(error); throw error; } }); }; /** * Type guard to check if a value is a Promise */ function isPromise(value: unknown): value is Promise { return value instanceof Promise; } /** * Creates an error handler that captures the error in Sentry with context */ function createErrorHandler(name: string, args: unknown[]) { return (error: unknown) => { const context = { functionName: name, args: redactSensitiveData(args), }; if (error instanceof Error) { (error as any).context = context; } captureException(error, { extra: context, tags: { functionName: name }, }); throw error; }; } /** * Recommended way to wrap functions for Sentry reporting. * Properly handles both sync and async functions with proper error context capture. * * @example * // Async function * const myAsyncFunction = withReporting(async (arg1: string, arg2: number) => { * // async implementation * }, "myAsyncFunction"); * * // Sync function * const mySyncFunction = withReporting((arg1: string, arg2: number) => { * // sync implementation * }, "mySyncFunction"); */ export function withReporting(func: (...args: T) => R, name: string): (...args: T) => R { if (!name?.trim()) { throw new Error("withReporting requires a non-empty name parameter"); } // Early return if Sentry is not configured if (!process.env.NEXT_PUBLIC_SENTRY_DSN || !process.env.SENTRY_TRACES_SAMPLE_RATE) { return func; } return (...args: T): R => { return startSpan({ name }, () => { const onError = createErrorHandler(name, args); try { const result = func(...args); // Handle Promise case with proper type guard if (isPromise(result)) { return result.catch(onError) as R; } // Handle sync case return result; } catch (error) { onError(error); // This line is technically unreachable due to onError throwing, // but TypeScript needs it for type safety throw error; } }); }; } export default monitorCallbackAsync; export { monitorCallbackSync };