Skip to content

Commit 3c5b835

Browse files
authored
Cron monitoring improvements (#677)
1 parent 377a93e commit 3c5b835

File tree

2 files changed

+73
-22
lines changed

2 files changed

+73
-22
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"require": {
2424
"php": "^7.2 | ^8.0",
2525
"illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0",
26-
"sentry/sentry": "^3.19",
26+
"sentry/sentry": "^3.20",
2727
"sentry/sdk": "^3.4",
2828
"symfony/psr-http-message-bridge": "^1.0 | ^2.0",
2929
"nyholm/psr7": "^1.0"

src/Sentry/Laravel/Features/ConsoleIntegration.php

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22

33
namespace Sentry\Laravel\Features;
44

5+
use Illuminate\Console\Application as ConsoleApplication;
56
use Illuminate\Console\Scheduling\Event as SchedulingEvent;
67
use Illuminate\Contracts\Cache\Factory as Cache;
78
use Illuminate\Contracts\Foundation\Application;
9+
use Illuminate\Support\Str;
10+
use RuntimeException;
811
use Sentry\CheckIn;
912
use Sentry\CheckInStatus;
1013
use Sentry\Event as SentryEvent;
14+
use Sentry\MonitorConfig;
15+
use Sentry\MonitorSchedule;
1116
use Sentry\SentrySdk;
1217

1318
class 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

Comments
 (0)