wrapApiHandlerWithSentryVercelCrons.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import { _optionalChain } from '@sentry/utils';
  2. import { runWithAsyncContext, addTracingExtensions, captureCheckIn } from '@sentry/core';
  3. /**
  4. * Wraps a function with Sentry crons instrumentation by automaticaly sending check-ins for the given Vercel crons config.
  5. */
  6. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  7. function wrapApiHandlerWithSentryVercelCrons(
  8. handler,
  9. vercelCronsConfig,
  10. ) {
  11. return new Proxy(handler, {
  12. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  13. apply: (originalFunction, thisArg, args) => {
  14. return runWithAsyncContext(() => {
  15. if (!args || !args[0]) {
  16. return originalFunction.apply(thisArg, args);
  17. }
  18. addTracingExtensions();
  19. const [req] = args ;
  20. let maybePromiseResult;
  21. const cronsKey = 'nextUrl' in req ? req.nextUrl.pathname : req.url;
  22. const userAgentHeader = 'nextUrl' in req ? req.headers.get('user-agent') : req.headers['user-agent'];
  23. if (
  24. !vercelCronsConfig || // do nothing if vercel crons config is missing
  25. !_optionalChain([userAgentHeader, 'optionalAccess', _ => _.includes, 'call', _2 => _2('vercel-cron')]) // do nothing if endpoint is not called from vercel crons
  26. ) {
  27. return originalFunction.apply(thisArg, args);
  28. }
  29. const vercelCron = vercelCronsConfig.find(vercelCron => vercelCron.path === cronsKey);
  30. if (!vercelCron || !vercelCron.path || !vercelCron.schedule) {
  31. return originalFunction.apply(thisArg, args);
  32. }
  33. const monitorSlug = vercelCron.path;
  34. const checkInId = captureCheckIn(
  35. {
  36. monitorSlug,
  37. status: 'in_progress',
  38. },
  39. {
  40. maxRuntime: 60 * 12, // (minutes) so 12 hours - just a very high arbitrary number since we don't know the actual duration of the users cron job
  41. schedule: {
  42. type: 'crontab',
  43. value: vercelCron.schedule,
  44. },
  45. },
  46. );
  47. const startTime = Date.now() / 1000;
  48. const handleErrorCase = () => {
  49. captureCheckIn({
  50. checkInId,
  51. monitorSlug,
  52. status: 'error',
  53. duration: Date.now() / 1000 - startTime,
  54. });
  55. };
  56. try {
  57. maybePromiseResult = originalFunction.apply(thisArg, args);
  58. } catch (e) {
  59. handleErrorCase();
  60. throw e;
  61. }
  62. if (typeof maybePromiseResult === 'object' && maybePromiseResult !== null && 'then' in maybePromiseResult) {
  63. Promise.resolve(maybePromiseResult).then(
  64. () => {
  65. captureCheckIn({
  66. checkInId,
  67. monitorSlug,
  68. status: 'ok',
  69. duration: Date.now() / 1000 - startTime,
  70. });
  71. },
  72. () => {
  73. handleErrorCase();
  74. },
  75. );
  76. // It is very important that we return the original promise here, because Next.js attaches various properties
  77. // to that promise and will throw if they are not on the returned value.
  78. return maybePromiseResult;
  79. } else {
  80. captureCheckIn({
  81. checkInId,
  82. monitorSlug,
  83. status: 'ok',
  84. duration: Date.now() / 1000 - startTime,
  85. });
  86. return maybePromiseResult;
  87. }
  88. });
  89. },
  90. });
  91. }
  92. export { wrapApiHandlerWithSentryVercelCrons };
  93. //# sourceMappingURL=wrapApiHandlerWithSentryVercelCrons.js.map