4
4
5
5
use DateTimeZone ;
6
6
use Illuminate \Console \Application as ConsoleApplication ;
7
+ use Illuminate \Console \Events \ScheduledTaskFailed ;
8
+ use Illuminate \Console \Events \ScheduledTaskFinished ;
9
+ use Illuminate \Console \Events \ScheduledTaskStarting ;
7
10
use Illuminate \Console \Scheduling \Event as SchedulingEvent ;
8
11
use Illuminate \Contracts \Cache \Factory as Cache ;
9
12
use Illuminate \Contracts \Cache \Repository ;
13
+ use Illuminate \Contracts \Events \Dispatcher ;
10
14
use Illuminate \Support \Str ;
11
15
use RuntimeException ;
12
16
use Sentry \CheckIn ;
13
17
use Sentry \CheckInStatus ;
14
18
use Sentry \Event as SentryEvent ;
19
+ use Sentry \Laravel \Features \Concerns \TracksPushedScopesAndSpans ;
15
20
use Sentry \MonitorConfig ;
16
21
use Sentry \MonitorSchedule ;
17
22
use Sentry \SentrySdk ;
23
+ use Sentry \Tracing \SpanStatus ;
24
+ use Sentry \Tracing \TransactionContext ;
25
+ use Sentry \Tracing \TransactionSource ;
18
26
19
27
class ConsoleSchedulingIntegration extends Feature
20
28
{
29
+ use TracksPushedScopesAndSpans;
30
+
21
31
/**
22
32
* @var string|null
23
33
*/
@@ -105,9 +115,13 @@ public function isApplicable(): bool
105
115
return true ;
106
116
}
107
117
108
- public function onBoot (): void
118
+ public function onBoot (Dispatcher $ events ): void
109
119
{
110
120
$ 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 ' ]);
111
125
}
112
126
113
127
public function onBootInactive (): void
@@ -120,6 +134,40 @@ public function useCacheStore(?string $name): void
120
134
$ this ->cacheStore = $ name ;
121
135
}
122
136
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
+
123
171
private function startCheckIn (
124
172
?string $ slug ,
125
173
SchedulingEvent $ scheduled ,
@@ -248,6 +296,18 @@ private function makeSlugForScheduled(SchedulingEvent $scheduled): string
248
296
return "scheduled_ {$ generatedSlug }" ;
249
297
}
250
298
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
+
251
311
private function resolveCache (): Repository
252
312
{
253
313
return $ this ->container ()->make (Cache::class)->store ($ this ->cacheStore );
0 commit comments