Skip to content

Commit 92ec416

Browse files
Implement phase 2 monitor check logging ingestion
1 parent e015c2c commit 92ec416

9 files changed

Lines changed: 337 additions & 16 deletions

app/Console/Commands/CheckDomainExpiration.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ public function handle(DomainService $domainService): void
3030
$hasNotifications = false;
3131

3232
foreach ($monitors as $monitor) {
33-
$notificationMessageConfirmation = $domainService->verifyDomainExpiration($monitor);
33+
$result = $domainService->verifyDomainExpiration($monitor);
3434

35-
if ($notificationMessageConfirmation) {
35+
if ($result['notified']) {
3636
$hasNotifications = true;
3737
}
3838
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace App\Listeners;
4+
5+
use App\Services\MonitorCheckLogService;
6+
use Carbon\Carbon;
7+
use Spatie\UptimeMonitor\Events\CertificateCheckFailed;
8+
9+
class LogCertificateCheckFailed
10+
{
11+
public function __construct(protected MonitorCheckLogService $monitorCheckLogService)
12+
{
13+
}
14+
15+
public function handle(CertificateCheckFailed $event): void
16+
{
17+
$monitor = $event->monitor;
18+
if (! $monitor->certificate_check_enabled) {
19+
return;
20+
}
21+
22+
$this->monitorCheckLogService->logCheck(
23+
monitor: $monitor,
24+
checkType: MonitorCheckLogService::CHECK_TYPE_CERTIFICATE,
25+
status: MonitorCheckLogService::STATUS_FAILED,
26+
checkedAt: Carbon::now()->utc(),
27+
message: 'Certificate check failed.',
28+
failureReason: $event->reason,
29+
metadata: [
30+
'certificate_expiration_date' => optional($monitor->certificate_expiration_date)?->toDateString(),
31+
],
32+
);
33+
}
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace App\Listeners;
4+
5+
use App\Services\MonitorCheckLogService;
6+
use Carbon\Carbon;
7+
use Spatie\UptimeMonitor\Events\CertificateCheckSucceeded;
8+
9+
class LogCertificateCheckSucceeded
10+
{
11+
public function __construct(protected MonitorCheckLogService $monitorCheckLogService)
12+
{
13+
}
14+
15+
public function handle(CertificateCheckSucceeded $event): void
16+
{
17+
$monitor = $event->monitor;
18+
if (! $monitor->certificate_check_enabled) {
19+
return;
20+
}
21+
22+
$this->monitorCheckLogService->logCheck(
23+
monitor: $monitor,
24+
checkType: MonitorCheckLogService::CHECK_TYPE_CERTIFICATE,
25+
status: MonitorCheckLogService::STATUS_SUCCESS,
26+
checkedAt: Carbon::now()->utc(),
27+
message: 'Certificate check succeeded.',
28+
metadata: [
29+
'issuer' => $monitor->certificate_issuer,
30+
'certificate_expiration_date' => optional($monitor->certificate_expiration_date)?->toDateString(),
31+
],
32+
);
33+
}
34+
}

app/Models/Monitor.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
namespace App\Models;
44

5+
use App\Services\MonitorCheckLogService;
56
use Illuminate\Database\Eloquent\Builder;
67
use Illuminate\Database\Eloquent\Collection;
78
use Illuminate\Database\Eloquent\Relations\HasMany;
9+
use Psr\Http\Message\ResponseInterface;
10+
use Spatie\UptimeMonitor\Models\Enums\UptimeStatus;
811
use Spatie\UptimeMonitor\Models\Monitor as SpatieMonitor;
912

1013
class Monitor extends SpatieMonitor
@@ -24,6 +27,57 @@ public function scopeDomainCheckEnabled(Builder $query): Collection
2427
->get();
2528
}
2629

30+
public function uptimeRequestSucceeded(ResponseInterface $response): void
31+
{
32+
parent::uptimeRequestSucceeded($response);
33+
34+
if (! $this->uptime_check_enabled) {
35+
return;
36+
}
37+
38+
$updatedMonitor = $this->fresh();
39+
if (! $updatedMonitor) {
40+
return;
41+
}
42+
43+
$status = $this->mapUptimeStatusToCheckStatus($updatedMonitor->uptime_status);
44+
$failureReason = $status === MonitorCheckLogService::STATUS_FAILED
45+
? $updatedMonitor->uptime_check_failure_reason
46+
: null;
47+
48+
app(MonitorCheckLogService::class)->logCheck(
49+
monitor: $updatedMonitor,
50+
checkType: MonitorCheckLogService::CHECK_TYPE_UPTIME,
51+
status: $status,
52+
checkedAt: $updatedMonitor->uptime_last_check_date,
53+
message: $status === MonitorCheckLogService::STATUS_SUCCESS ? 'Uptime check succeeded.' : 'Uptime check failed.',
54+
failureReason: $failureReason,
55+
);
56+
}
57+
58+
public function uptimeRequestFailed(string $reason): void
59+
{
60+
parent::uptimeRequestFailed($reason);
61+
62+
if (! $this->uptime_check_enabled) {
63+
return;
64+
}
65+
66+
$updatedMonitor = $this->fresh();
67+
if (! $updatedMonitor) {
68+
return;
69+
}
70+
71+
app(MonitorCheckLogService::class)->logCheck(
72+
monitor: $updatedMonitor,
73+
checkType: MonitorCheckLogService::CHECK_TYPE_UPTIME,
74+
status: MonitorCheckLogService::STATUS_FAILED,
75+
checkedAt: $updatedMonitor->uptime_last_check_date,
76+
message: 'Uptime check failed.',
77+
failureReason: $reason,
78+
);
79+
}
80+
2781
public function group()
2882
{
2983
return $this->belongsTo(Group::class);
@@ -38,4 +92,13 @@ public function dailyCheckMetrics(): HasMany
3892
{
3993
return $this->hasMany(MonitorDailyCheckMetric::class);
4094
}
95+
96+
protected function mapUptimeStatusToCheckStatus(string $uptimeStatus): string
97+
{
98+
return match ($uptimeStatus) {
99+
UptimeStatus::UP => MonitorCheckLogService::STATUS_SUCCESS,
100+
UptimeStatus::DOWN => MonitorCheckLogService::STATUS_FAILED,
101+
default => MonitorCheckLogService::STATUS_UNKNOWN,
102+
};
103+
}
41104
}

app/Models/MonitorCheckLog.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class MonitorCheckLog extends Model
1313

1414
protected $fillable = [
1515
'monitor_id',
16+
'idempotency_key',
1617
'check_type',
1718
'status',
1819
'checked_at',

app/Providers/AppServiceProvider.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22

33
namespace App\Providers;
44

5+
use App\Listeners\LogCertificateCheckFailed;
6+
use App\Listeners\LogCertificateCheckSucceeded;
7+
use Illuminate\Auth\Events\Registered;
8+
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
9+
use Illuminate\Support\Facades\Event;
510
use Illuminate\Support\ServiceProvider;
11+
use Spatie\UptimeMonitor\Events\CertificateCheckFailed;
12+
use Spatie\UptimeMonitor\Events\CertificateCheckSucceeded;
613

714
class AppServiceProvider extends ServiceProvider
815
{
@@ -23,9 +30,8 @@ public function register()
2330
*/
2431
public function boot()
2532
{
26-
\Illuminate\Support\Facades\Event::listen(
27-
\Illuminate\Auth\Events\Registered::class,
28-
\Illuminate\Auth\Listeners\SendEmailVerificationNotification::class
29-
);
33+
Event::listen(Registered::class, SendEmailVerificationNotification::class);
34+
Event::listen(CertificateCheckSucceeded::class, LogCertificateCheckSucceeded::class);
35+
Event::listen(CertificateCheckFailed::class, LogCertificateCheckFailed::class);
3036
}
3137
}

app/Services/DomainService.php

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,40 +34,98 @@ public static function addDomainExpiration(Monitor $monitor): bool
3434
return false;
3535
}
3636

37-
public function verifyDomainExpiration(Monitor $monitor): bool
37+
public function verifyDomainExpiration(Monitor $monitor): array
3838
{
39+
if (! $monitor->domain_check_enabled) {
40+
return [
41+
'status' => MonitorCheckLogService::STATUS_UNKNOWN,
42+
'notified' => false,
43+
'reason' => 'Domain check is disabled for this monitor.',
44+
'days_until_expiration' => null,
45+
'expiration_date' => null,
46+
];
47+
}
48+
49+
$checkedAt = Carbon::now()->utc();
3950
$domainInfo = $this->getDomainExpirationDate($monitor->url);
4051

4152
if (empty($domainInfo)) {
42-
return false;
53+
app(MonitorCheckLogService::class)->logCheck(
54+
monitor: $monitor,
55+
checkType: MonitorCheckLogService::CHECK_TYPE_DOMAIN,
56+
status: MonitorCheckLogService::STATUS_FAILED,
57+
checkedAt: $checkedAt,
58+
message: 'Domain expiration lookup failed.',
59+
failureReason: 'Unable to determine domain expiration date.',
60+
);
61+
62+
return [
63+
'status' => MonitorCheckLogService::STATUS_FAILED,
64+
'notified' => false,
65+
'reason' => 'Unable to determine domain expiration date.',
66+
'days_until_expiration' => null,
67+
'expiration_date' => null,
68+
];
4369
}
4470

4571
$expirationDate = Carbon::parse($domainInfo['expirationDate']);
4672
if (! $monitor->domain_expires_at || ! $monitor->domain_expires_at->equalTo($expirationDate)) {
4773
$this->updateDomainExpiration($monitor, $domainInfo['expirationDate']);
4874
}
4975

50-
return $this->checkAndNotifyExpiration($monitor);
76+
$daysUntilExpiration = Carbon::now()->startOfDay()->diffInDays($expirationDate->copy()->startOfDay(), false);
77+
$notifications = $this->checkAndNotifyExpiration($monitor, $daysUntilExpiration);
78+
79+
$status = match (true) {
80+
$daysUntilExpiration < 0 => MonitorCheckLogService::STATUS_FAILED,
81+
$daysUntilExpiration <= 30 => MonitorCheckLogService::STATUS_WARNING,
82+
default => MonitorCheckLogService::STATUS_SUCCESS,
83+
};
84+
85+
$message = match (true) {
86+
$daysUntilExpiration < 0 => 'Domain has expired.',
87+
$daysUntilExpiration === 0 => 'Domain expires today.',
88+
default => "Domain expires in {$daysUntilExpiration} day(s).",
89+
};
90+
91+
app(MonitorCheckLogService::class)->logCheck(
92+
monitor: $monitor,
93+
checkType: MonitorCheckLogService::CHECK_TYPE_DOMAIN,
94+
status: $status,
95+
checkedAt: $checkedAt,
96+
message: $message,
97+
metadata: [
98+
'days_until_expiration' => $daysUntilExpiration,
99+
'expiration_date' => optional($monitor->domain_expires_at)->toDateString(),
100+
'notifications_sent' => count($notifications),
101+
],
102+
);
103+
104+
return [
105+
'status' => $status,
106+
'notified' => ! empty($notifications),
107+
'reason' => null,
108+
'days_until_expiration' => $daysUntilExpiration,
109+
'expiration_date' => optional($monitor->domain_expires_at)->toDateString(),
110+
];
51111
}
52112

53-
protected function checkAndNotifyExpiration(Monitor $monitor): bool
113+
protected function checkAndNotifyExpiration(Monitor $monitor, int $daysUntilExpiration): array
54114
{
55115
$expirationDate = $monitor->domain_expires_at;
56116

57117
if (! $expirationDate) {
58-
return false;
118+
return [];
59119
}
60120

61-
$daysUntilExpiration = Carbon::now()->diffInDays($expirationDate);
62-
63121
$domainCheckTimePeriods = config('domain-expiration.domain_check_time_period');
64122

65123
$notifications = [];
66124

67125
foreach ($domainCheckTimePeriods as $warningType => $details) {
68126
$daysThreshold = $details['days'];
69127

70-
if ($daysUntilExpiration === $daysThreshold) {
128+
if ($daysUntilExpiration >= 0 && $daysUntilExpiration === $daysThreshold) {
71129
$notifications[] = [
72130
'days' => $daysThreshold,
73131
'message' => "Domain expires in $daysThreshold ".($daysThreshold === 1 ? 'day' : 'days').'!',
@@ -77,7 +135,7 @@ protected function checkAndNotifyExpiration(Monitor $monitor): bool
77135
}
78136

79137
if (empty($notifications)) {
80-
return false;
138+
return [];
81139
}
82140

83141
$notifiable = new Notifiable;
@@ -87,7 +145,7 @@ protected function checkAndNotifyExpiration(Monitor $monitor): bool
87145
$notifiable->notify($notificationInstance);
88146
}
89147

90-
return true;
148+
return $notifications;
91149
}
92150

93151
protected function getDomainExpirationDate(string $url): array

0 commit comments

Comments
 (0)