Skip to content

Commit c3808bd

Browse files
stayallivecleptric
andauthored
Add Scheduled Task Tracing (#968)
Co-authored-by: Michael Hoffmann <[email protected]>
1 parent 596bfa1 commit c3808bd

File tree

2 files changed

+114
-1
lines changed

2 files changed

+114
-1
lines changed

src/Sentry/Laravel/Features/ConsoleSchedulingIntegration.php

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,30 @@
44

55
use DateTimeZone;
66
use Illuminate\Console\Application as ConsoleApplication;
7+
use Illuminate\Console\Events\ScheduledTaskFailed;
8+
use Illuminate\Console\Events\ScheduledTaskFinished;
9+
use Illuminate\Console\Events\ScheduledTaskStarting;
710
use Illuminate\Console\Scheduling\Event as SchedulingEvent;
811
use Illuminate\Contracts\Cache\Factory as Cache;
912
use Illuminate\Contracts\Cache\Repository;
13+
use Illuminate\Contracts\Events\Dispatcher;
1014
use Illuminate\Support\Str;
1115
use RuntimeException;
1216
use Sentry\CheckIn;
1317
use Sentry\CheckInStatus;
1418
use Sentry\Event as SentryEvent;
19+
use Sentry\Laravel\Features\Concerns\TracksPushedScopesAndSpans;
1520
use Sentry\MonitorConfig;
1621
use Sentry\MonitorSchedule;
1722
use Sentry\SentrySdk;
23+
use Sentry\Tracing\SpanStatus;
24+
use Sentry\Tracing\TransactionContext;
25+
use Sentry\Tracing\TransactionSource;
1826

1927
class 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);

test/Sentry/Features/ConsoleSchedulingIntegrationTest.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
namespace Sentry\Laravel\Tests\Features;
44

55
use DateTimeZone;
6+
use Illuminate\Bus\Queueable;
67
use Illuminate\Console\Scheduling\Schedule;
8+
use Illuminate\Contracts\Queue\ShouldQueue;
79
use RuntimeException;
810
use Sentry\Laravel\Tests\TestCase;
911
use Illuminate\Console\Scheduling\Event;
@@ -121,8 +123,59 @@ public function testScheduleMacroIsRegisteredWithoutDsnSet(): void
121123
$this->assertTrue(Event::hasMacro('sentryMonitor'));
122124
}
123125

126+
/** @define-env envSamplingAllTransactions */
127+
public function testScheduledCommandCreatesTransaction(): void
128+
{
129+
$this->getScheduler()->command('inspire')->everyMinute();
130+
131+
$this->artisan('schedule:run');
132+
133+
$this->assertSentryTransactionCount(1);
134+
135+
$transaction = $this->getLastSentryEvent();
136+
137+
$this->assertEquals('inspire', $transaction->getTransaction());
138+
}
139+
140+
/** @define-env envSamplingAllTransactions */
141+
public function testScheduledClosureCreatesTransaction(): void
142+
{
143+
$this->getScheduler()->call(function () {})->everyMinute();
144+
145+
$this->artisan('schedule:run');
146+
147+
$this->assertSentryTransactionCount(1);
148+
149+
$transaction = $this->getLastSentryEvent();
150+
151+
$this->assertEquals('Closure', $transaction->getTransaction());
152+
}
153+
154+
/** @define-env envSamplingAllTransactions */
155+
public function testScheduledJobCreatesTransaction(): void
156+
{
157+
$this->getScheduler()->job(ScheduledQueuedJob::class)->everyMinute();
158+
159+
$this->artisan('schedule:run');
160+
161+
$this->assertSentryTransactionCount(1);
162+
163+
$transaction = $this->getLastSentryEvent();
164+
165+
$this->assertEquals(ScheduledQueuedJob::class, $transaction->getTransaction());
166+
}
167+
124168
private function getScheduler(): Schedule
125169
{
126170
return $this->app->make(Schedule::class);
127171
}
128172
}
173+
174+
class ScheduledQueuedJob implements ShouldQueue
175+
{
176+
use Queueable;
177+
178+
public function handle(): void
179+
{
180+
}
181+
}

0 commit comments

Comments
 (0)