|  | 
|  | 1 | +import { withMonitor } from '@sentry/core'; | 
|  | 2 | +import { replaceCronNames } from './common'; | 
|  | 3 | + | 
|  | 4 | +export type CronJobParams = { | 
|  | 5 | +  cronTime: string | Date; | 
|  | 6 | +  onTick: (context: unknown, onComplete?: unknown) => void | Promise<void>; | 
|  | 7 | +  onComplete?: () => void | Promise<void>; | 
|  | 8 | +  start?: boolean | null; | 
|  | 9 | +  context?: unknown; | 
|  | 10 | +  runOnInit?: boolean | null; | 
|  | 11 | +  unrefTimeout?: boolean | null; | 
|  | 12 | +} & ( | 
|  | 13 | +  | { | 
|  | 14 | +      timeZone?: string | null; | 
|  | 15 | +      utcOffset?: never; | 
|  | 16 | +    } | 
|  | 17 | +  | { | 
|  | 18 | +      timeZone?: never; | 
|  | 19 | +      utcOffset?: number | null; | 
|  | 20 | +    } | 
|  | 21 | +); | 
|  | 22 | + | 
|  | 23 | +export type CronJob = { | 
|  | 24 | +  // | 
|  | 25 | +}; | 
|  | 26 | + | 
|  | 27 | +export type CronJobConstructor = { | 
|  | 28 | +  from: (param: CronJobParams) => CronJob; | 
|  | 29 | + | 
|  | 30 | +  new ( | 
|  | 31 | +    cronTime: CronJobParams['cronTime'], | 
|  | 32 | +    onTick: CronJobParams['onTick'], | 
|  | 33 | +    onComplete?: CronJobParams['onComplete'], | 
|  | 34 | +    start?: CronJobParams['start'], | 
|  | 35 | +    timeZone?: CronJobParams['timeZone'], | 
|  | 36 | +    context?: CronJobParams['context'], | 
|  | 37 | +    runOnInit?: CronJobParams['runOnInit'], | 
|  | 38 | +    utcOffset?: null, | 
|  | 39 | +    unrefTimeout?: CronJobParams['unrefTimeout'], | 
|  | 40 | +  ): CronJob; | 
|  | 41 | +  new ( | 
|  | 42 | +    cronTime: CronJobParams['cronTime'], | 
|  | 43 | +    onTick: CronJobParams['onTick'], | 
|  | 44 | +    onComplete?: CronJobParams['onComplete'], | 
|  | 45 | +    start?: CronJobParams['start'], | 
|  | 46 | +    timeZone?: null, | 
|  | 47 | +    context?: CronJobParams['context'], | 
|  | 48 | +    runOnInit?: CronJobParams['runOnInit'], | 
|  | 49 | +    utcOffset?: CronJobParams['utcOffset'], | 
|  | 50 | +    unrefTimeout?: CronJobParams['unrefTimeout'], | 
|  | 51 | +  ): CronJob; | 
|  | 52 | +}; | 
|  | 53 | + | 
|  | 54 | +const ERROR_TEXT = 'Automatic instrumentation of CronJob only supports crontab string'; | 
|  | 55 | + | 
|  | 56 | +/** | 
|  | 57 | + * Instruments the `cron` library to send a check-in event to Sentry for each job execution. | 
|  | 58 | + * | 
|  | 59 | + * ```ts | 
|  | 60 | + * import * as Sentry from '@sentry/node'; | 
|  | 61 | + * import { CronJob } from 'cron'; | 
|  | 62 | + * | 
|  | 63 | + * const CronJobWithCheckIn = Sentry.cron.instrumentCron(CronJob, 'my-cron-job'); | 
|  | 64 | + * | 
|  | 65 | + * // use the constructor | 
|  | 66 | + * const job = new CronJobWithCheckIn('* * * * *', () => { | 
|  | 67 | + *  console.log('You will see this message every minute'); | 
|  | 68 | + * }); | 
|  | 69 | + * | 
|  | 70 | + * // or from | 
|  | 71 | + * const job = CronJobWithCheckIn.from({ cronTime: '* * * * *', onTick: () => { | 
|  | 72 | + *   console.log('You will see this message every minute'); | 
|  | 73 | + * }); | 
|  | 74 | + * ``` | 
|  | 75 | + */ | 
|  | 76 | +export function instrumentCron<T>(lib: T & CronJobConstructor, monitorSlug: string): T { | 
|  | 77 | +  let jobScheduled = false; | 
|  | 78 | + | 
|  | 79 | +  return new Proxy(lib, { | 
|  | 80 | +    construct(target, args: ConstructorParameters<CronJobConstructor>) { | 
|  | 81 | +      const [cronTime, onTick, onComplete, start, timeZone, ...rest] = args; | 
|  | 82 | + | 
|  | 83 | +      if (typeof cronTime !== 'string') { | 
|  | 84 | +        throw new Error(ERROR_TEXT); | 
|  | 85 | +      } | 
|  | 86 | + | 
|  | 87 | +      if (jobScheduled) { | 
|  | 88 | +        throw new Error(`A job named '${monitorSlug}' has already been scheduled`); | 
|  | 89 | +      } | 
|  | 90 | + | 
|  | 91 | +      jobScheduled = true; | 
|  | 92 | + | 
|  | 93 | +      const cronString = replaceCronNames(cronTime); | 
|  | 94 | + | 
|  | 95 | +      function monitoredTick(context: unknown, onComplete?: unknown): void | Promise<void> { | 
|  | 96 | +        return withMonitor( | 
|  | 97 | +          monitorSlug, | 
|  | 98 | +          () => { | 
|  | 99 | +            return onTick(context, onComplete); | 
|  | 100 | +          }, | 
|  | 101 | +          { | 
|  | 102 | +            schedule: { type: 'crontab', value: cronString }, | 
|  | 103 | +            ...(timeZone ? { timeZone } : {}), | 
|  | 104 | +          }, | 
|  | 105 | +        ); | 
|  | 106 | +      } | 
|  | 107 | + | 
|  | 108 | +      return new target(cronTime, monitoredTick, onComplete, start, timeZone, ...rest); | 
|  | 109 | +    }, | 
|  | 110 | +    get(target, prop: keyof CronJobConstructor) { | 
|  | 111 | +      if (prop === 'from') { | 
|  | 112 | +        return (param: CronJobParams) => { | 
|  | 113 | +          const { cronTime, onTick, timeZone } = param; | 
|  | 114 | + | 
|  | 115 | +          if (typeof cronTime !== 'string') { | 
|  | 116 | +            throw new Error(ERROR_TEXT); | 
|  | 117 | +          } | 
|  | 118 | + | 
|  | 119 | +          if (jobScheduled) { | 
|  | 120 | +            throw new Error(`A job named '${monitorSlug}' has already been scheduled`); | 
|  | 121 | +          } | 
|  | 122 | + | 
|  | 123 | +          jobScheduled = true; | 
|  | 124 | + | 
|  | 125 | +          const cronString = replaceCronNames(cronTime); | 
|  | 126 | + | 
|  | 127 | +          param.onTick = (context: unknown, onComplete?: unknown) => { | 
|  | 128 | +            return withMonitor( | 
|  | 129 | +              monitorSlug, | 
|  | 130 | +              () => { | 
|  | 131 | +                return onTick(context, onComplete); | 
|  | 132 | +              }, | 
|  | 133 | +              { | 
|  | 134 | +                schedule: { type: 'crontab', value: cronString }, | 
|  | 135 | +                ...(timeZone ? { timeZone } : {}), | 
|  | 136 | +              }, | 
|  | 137 | +            ); | 
|  | 138 | +          }; | 
|  | 139 | + | 
|  | 140 | +          return target.from(param); | 
|  | 141 | +        }; | 
|  | 142 | +      } else { | 
|  | 143 | +        return target[prop]; | 
|  | 144 | +      } | 
|  | 145 | +    }, | 
|  | 146 | +  }); | 
|  | 147 | +} | 
0 commit comments