22
33namespace Sentry \Laravel \Features ;
44
5+ use Illuminate \Console \Application as ConsoleApplication ;
56use Illuminate \Console \Scheduling \Event as SchedulingEvent ;
67use Illuminate \Contracts \Cache \Factory as Cache ;
78use Illuminate \Contracts \Foundation \Application ;
9+ use Illuminate \Support \Str ;
10+ use RuntimeException ;
811use Sentry \CheckIn ;
912use Sentry \CheckInStatus ;
1013use Sentry \Event as SentryEvent ;
14+ use Sentry \MonitorConfig ;
15+ use Sentry \MonitorSchedule ;
1116use Sentry \SentrySdk ;
1217
1318class ConsoleIntegration extends Feature
@@ -31,65 +36,94 @@ public function setup(Cache $cache): void
3136 {
3237 $ this ->cache = $ cache ;
3338
34- $ startCheckIn = function (string $ mutex , string $ slug , bool $ useCache , int $ useCacheTtlInMinutes ) {
35- $ this ->startCheckIn ($ mutex , $ slug , $ useCache , $ useCacheTtlInMinutes );
39+ $ startCheckIn = function (? string $ slug , SchedulingEvent $ scheduled , ? int $ checkInMargin , ? int $ maxRuntime , bool $ updateMonitorConfig ) {
40+ $ this ->startCheckIn ($ slug , $ scheduled , $ checkInMargin , $ maxRuntime , $ updateMonitorConfig );
3641 };
37- $ finishCheckIn = function (string $ mutex , string $ slug , CheckInStatus $ status, bool $ useCache ) {
38- $ this ->finishCheckIn ($ mutex , $ slug , $ status , $ useCache );
42+ $ finishCheckIn = function (? string $ slug , SchedulingEvent $ scheduled , CheckInStatus $ status ) {
43+ $ this ->finishCheckIn ($ slug , $ scheduled , $ status );
3944 };
4045
41- SchedulingEvent::macro ('sentryMonitor ' , function (string $ monitorSlug ) use ($ startCheckIn , $ finishCheckIn ) {
46+ SchedulingEvent::macro ('sentryMonitor ' , function (
47+ ?string $ monitorSlug = null ,
48+ ?int $ checkInMargin = null ,
49+ ?int $ maxRuntime = null ,
50+ bool $ updateMonitorConfig = true
51+ ) use ($ startCheckIn , $ finishCheckIn ) {
4252 /** @var SchedulingEvent $this */
53+ if ($ monitorSlug === null && $ this ->command === null ) {
54+ throw new RuntimeException ('The command string is null, please set a slug manually for this scheduled command using the `sentryMonitor( \'your-monitor-slug \')` macro. ' );
55+ }
56+
4357 return $ this
44- ->before (function () use ($ startCheckIn , $ monitorSlug ) {
58+ ->before (function () use ($ startCheckIn , $ monitorSlug, $ checkInMargin , $ maxRuntime , $ updateMonitorConfig ) {
4559 /** @var SchedulingEvent $this */
46- $ startCheckIn ($ this -> mutexName () , $ monitorSlug , $ this -> runInBackground , $ this -> expiresAt );
60+ $ startCheckIn ($ monitorSlug , $ this , $ checkInMargin , $ maxRuntime , $ updateMonitorConfig );
4761 })
4862 ->onSuccess (function () use ($ finishCheckIn , $ monitorSlug ) {
4963 /** @var SchedulingEvent $this */
50- $ finishCheckIn ($ this -> mutexName () , $ monitorSlug , CheckInStatus::ok (), $ this -> runInBackground );
64+ $ finishCheckIn ($ monitorSlug , $ this , CheckInStatus::ok ());
5165 })
5266 ->onFailure (function () use ($ finishCheckIn , $ monitorSlug ) {
5367 /** @var SchedulingEvent $this */
54- $ finishCheckIn ($ this -> mutexName () , $ monitorSlug , CheckInStatus::error (), $ this -> runInBackground );
68+ $ finishCheckIn ($ monitorSlug , $ this , CheckInStatus::error ());
5569 });
5670 });
5771 }
5872
5973 public function setupInactive (): void
6074 {
61- SchedulingEvent::macro ('sentryMonitor ' , function (string $ monitorSlug ) {
62- // When there is no Sentry DSN set there is nothing for us to do, but we still want to allow the user to setup the macro
75+ // This is an exact copy of the macro above, but without doing anything so that even when no DSN is configured the user can still use the macro
76+ SchedulingEvent::macro ('sentryMonitor ' , function (
77+ ?string $ monitorSlug = null ,
78+ ?int $ checkInMargin = null ,
79+ ?int $ maxRuntime = null ,
80+ bool $ updateMonitorConfig = true
81+ ) {
6382 return $ this ;
6483 });
6584 }
6685
67- private function startCheckIn (string $ mutex , string $ slug , bool $ useCache , int $ useCacheTtlInMinutes ): void
86+ private function startCheckIn (? string $ slug , SchedulingEvent $ scheduled , ? int $ checkInMargin , ? int $ maxRuntime , bool $ updateMonitorConfig ): void
6887 {
69- $ checkIn = $ this ->createCheckIn ($ slug , CheckInStatus::inProgress ());
88+ $ checkInSlug = $ slug ?? $ this ->makeSlugForScheduled ($ scheduled );
89+
90+ $ checkIn = $ this ->createCheckIn ($ checkInSlug , CheckInStatus::inProgress ());
7091
71- $ cacheKey = $ this ->buildCacheKey ($ mutex , $ slug );
92+ if ($ updateMonitorConfig || $ slug === null ) {
93+ $ checkIn ->setMonitorConfig (new MonitorConfig (
94+ MonitorSchedule::crontab ($ scheduled ->getExpression ()),
95+ $ checkInMargin ,
96+ $ maxRuntime ,
97+ $ scheduled ->timezone
98+ ));
99+ }
100+
101+ $ cacheKey = $ this ->buildCacheKey ($ scheduled ->mutexName (), $ checkInSlug );
72102
73103 $ this ->checkInStore [$ cacheKey ] = $ checkIn ;
74104
75- if ($ useCache ) {
76- $ this ->cache ->store ()->put ($ cacheKey , $ checkIn ->getId (), $ useCacheTtlInMinutes * 60 );
105+ if ($ scheduled -> runInBackground ) {
106+ $ this ->cache ->store ()->put ($ cacheKey , $ checkIn ->getId (), $ scheduled -> expiresAt * 60 );
77107 }
78108
79109 $ this ->sendCheckIn ($ checkIn );
80110 }
81111
82- private function finishCheckIn (string $ mutex , string $ slug , CheckInStatus $ status, bool $ useCache ): void
112+ private function finishCheckIn (? string $ slug , SchedulingEvent $ scheduled , CheckInStatus $ status ): void
83113 {
84- $ cacheKey = $ this ->buildCacheKey ($ mutex , $ slug );
114+ $ mutex = $ scheduled ->mutexName ();
115+
116+ $ checkInSlug = $ slug ?? $ this ->makeSlugForScheduled ($ scheduled );
117+
118+ $ cacheKey = $ this ->buildCacheKey ($ mutex , $ checkInSlug );
85119
86120 $ checkIn = $ this ->checkInStore [$ cacheKey ] ?? null ;
87121
88- if ($ checkIn === null && $ useCache ) {
122+ if ($ checkIn === null && $ scheduled -> runInBackground ) {
89123 $ checkInId = $ this ->cache ->store ()->get ($ cacheKey );
90124
91125 if ($ checkInId !== null ) {
92- $ checkIn = $ this ->createCheckIn ($ slug , $ status , $ checkInId );
126+ $ checkIn = $ this ->createCheckIn ($ checkInSlug , $ status , $ checkInId );
93127 }
94128 }
95129
@@ -101,7 +135,7 @@ private function finishCheckIn(string $mutex, string $slug, CheckInStatus $statu
101135 // We don't need to keep the checkIn ID stored since we finished executing the command
102136 unset($ this ->checkInStore [$ mutex ]);
103137
104- if ($ useCache ) {
138+ if ($ scheduled -> runInBackground ) {
105139 $ this ->cache ->store ()->forget ($ cacheKey );
106140 }
107141
@@ -136,4 +170,21 @@ private function buildCacheKey(string $mutex, string $slug): string
136170 // We use the mutex name as part of the cache key to avoid collisions between the same commands with the same schedule but with different slugs
137171 return 'sentry:checkIn: ' . sha1 ("{$ mutex }: {$ slug }" );
138172 }
173+
174+ private function makeSlugForScheduled (SchedulingEvent $ scheduled ): string
175+ {
176+ $ generatedSlug = Str::slug (
177+ Str::replace (
178+ // `:` is commonly used in the command name, so we replace it with `-` to avoid it being stripped out by the slug function
179+ ': ' ,
180+ '- ' ,
181+ trim (
182+ // The command string always starts with the PHP binary, so we remove it since it's not relevant to the slug
183+ Str::after ($ scheduled ->command , ConsoleApplication::phpBinary ())
184+ )
185+ )
186+ );
187+
188+ return "scheduled_ {$ generatedSlug }" ;
189+ }
139190}
0 commit comments