instance_id === null) { return; } $metric = InstanceMetric::query()->firstOrCreate( ['instance_id' => $target->instance_id, 'day' => now()->toDateString()], ); $metric->increment('checks_total'); if ($healthy) { $metric->increment('checks_ok'); } } public function handle(MonitoringClient $monitoring): void { MonitoringTarget::query() ->whereHas('instance', fn ($instance) => $instance->where('status', 'active')) ->chunkById(100, function ($targets) use ($monitoring) { foreach ($targets as $target) { $this->refresh($target, $monitoring); } }); } private function refresh(MonitoringTarget $target, MonitoringClient $monitoring): void { try { // health(), not isHealthy(): the boolean answer folds "no monitoring // configured" into true and "monitor unreachable" into false, and // recording either as a measurement publishes a fleet-wide lie. $healthy = $monitoring->health($target->external_id); } catch (Throwable $e) { // The monitoring service being unreachable says nothing about the // instance. Recording 'down' here would raise an alarm about the // wrong system; recording 'up' would hide a real one. Leave the // previous verdict and let it go stale. Log::warning('Monitoring did not answer for a target', [ 'external_id' => $target->external_id, 'exception' => $e->getMessage(), ]); return; } // Counted before the early return below, so a day's denominator grows // only when a real answer arrived. An unanswered check is not a failed // one — folding the two together turns a monitoring outage into the // customer's outage, on the one figure they are asked to trust. $this->countCheck($target, $healthy); if ($healthy === null) { // No answer. The previous verdict stays and is allowed to go stale, // which readers show as "not monitored" rather than as health. return; } $target->update([ 'status' => $healthy ? 'up' : 'down', 'checked_at' => now(), ]); } }