44
55use DateTimeZone ;
66use Illuminate \Console \Application as ConsoleApplication ;
7+ use Illuminate \Console \Events \ScheduledTaskFailed ;
8+ use Illuminate \Console \Events \ScheduledTaskFinished ;
9+ use Illuminate \Console \Events \ScheduledTaskStarting ;
710use Illuminate \Console \Scheduling \Event as SchedulingEvent ;
811use Illuminate \Contracts \Cache \Factory as Cache ;
912use Illuminate \Contracts \Cache \Repository ;
13+ use Illuminate \Contracts \Events \Dispatcher ;
1014use Illuminate \Support \Str ;
1115use RuntimeException ;
1216use Sentry \CheckIn ;
1317use Sentry \CheckInStatus ;
1418use Sentry \Event as SentryEvent ;
19+ use Sentry \Laravel \Features \Concerns \TracksPushedScopesAndSpans ;
1520use Sentry \MonitorConfig ;
1621use Sentry \MonitorSchedule ;
1722use Sentry \SentrySdk ;
23+ use Sentry \Tracing \SpanStatus ;
24+ use Sentry \Tracing \TransactionContext ;
25+ use Sentry \Tracing \TransactionSource ;
1826
1927class ConsoleSchedulingIntegration extends Feature
2028{
29+ use TracksPushedScopesAndSpans;
30+
2131 /**
2232 * @var string|null
2333 */
@@ -105,9 +115,13 @@ public function isApplicable(): bool
105115 return true ;
106116 }
107117
108- public function onBoot (): void
118+ public function onBoot (Dispatcher $ events ): void
109119 {
110120 $ this ->shouldHandleCheckIn = true ;
121+
122+ $ events ->listen (ScheduledTaskStarting::class, [$ this , 'handleScheduledTaskStarting ' ]);
123+ $ events ->listen (ScheduledTaskFinished::class, [$ this , 'handleScheduledTaskFinished ' ]);
124+ $ events ->listen (ScheduledTaskFailed::class, [$ this , 'handleScheduledTaskFailed ' ]);
111125 }
112126
113127 public function onBootInactive (): void
@@ -120,6 +134,40 @@ public function useCacheStore(?string $name): void
120134 $ this ->cacheStore = $ name ;
121135 }
122136
137+ public function handleScheduledTaskStarting (ScheduledTaskStarting $ event ): void
138+ {
139+ if (!$ event ->task ) {
140+ return ;
141+ }
142+
143+ // When scheduling a command class the command name will be the most descriptive
144+ // When a job is scheduled the command name is `null` and the job class name (or display name) is set as the description
145+ // When a closure is scheduled both the command name and description are `null`
146+ $ name = $ this ->getCommandNameForScheduled ($ event ->task ) ?? $ event ->task ->description ?? 'Closure ' ;
147+
148+ $ context = TransactionContext::make ()
149+ ->setName ($ name )
150+ ->setSource (TransactionSource::task ())
151+ ->setOp ('console.command.scheduled ' )
152+ ->setStartTimestamp (microtime (true ));
153+
154+ $ transaction = SentrySdk::getCurrentHub ()->startTransaction ($ context );
155+
156+ $ this ->pushSpan ($ transaction );
157+ }
158+
159+ public function handleScheduledTaskFinished (): void
160+ {
161+ $ this ->maybeFinishSpan (SpanStatus::ok ());
162+ $ this ->maybePopScope ();
163+ }
164+
165+ public function handleScheduledTaskFailed (): void
166+ {
167+ $ this ->maybeFinishSpan (SpanStatus::internalError ());
168+ $ this ->maybePopScope ();
169+ }
170+
123171 private function startCheckIn (
124172 ?string $ slug ,
125173 SchedulingEvent $ scheduled ,
@@ -248,6 +296,18 @@ private function makeSlugForScheduled(SchedulingEvent $scheduled): string
248296 return "scheduled_ {$ generatedSlug }" ;
249297 }
250298
299+ private function getCommandNameForScheduled (SchedulingEvent $ scheduled ): ?string
300+ {
301+ if (!$ scheduled ->command ) {
302+ return null ;
303+ }
304+
305+ // The command string always starts with the PHP binary and artisan binary, so we remove it since it's not relevant to the name
306+ return trim (
307+ Str::after ($ scheduled ->command , ConsoleApplication::phpBinary () . ' ' . ConsoleApplication::artisanBinary ())
308+ );
309+ }
310+
251311 private function resolveCache (): Repository
252312 {
253313 return $ this ->container ()->make (Cache::class)->store ($ this ->cacheStore );
0 commit comments