Watchdogs Check-System ist vollständig plugin-basiert. Jede PHP-Klasse, die CheckInterface implementiert und das Attribut #[AutoconfigureTag('watchdog.check')] trägt, wird automatisch erkannt und steht sofort in der UI zur Verfügung — keine Registrierung, kein YAML nötig.
Jeder Check implementiert diese Methoden:
interface CheckInterface
{
// Eindeutige Typ-ID fĂĽr Datenbank und API, z.B. "my_check"
public function getType(): string;
// Lesbares Label — wird überall in der UI verwendet (Liste, Dashboard, E-Mails)
public function getLabel(): string;
// Standard-Konfiguration, die vor jedem Run mit der gespeicherten Config zusammengefĂĽhrt wird
public function getDefaultConfig(): array;
// Formularfeld-Definitionen fĂĽr die UI (siehe Schritt 3)
public function getConfigSchema(): array;
// Wo dieser Check laufen kann: AgentOnly, DashboardOnly oder Both
public function runnerMode(): RunnerMode;
// Check ausfĂĽhren und ein (noch nicht persistiertes) CheckResult zurĂĽckgeben
public function run(SiteCheck $check): CheckResult;
// Spaltenbezeichnung fĂĽr das ĂĽberwachte Ziel in Alert-E-Mails (oder null)
public function getEmailTargetLabel(): ?string;
// Den lesbaren Zielwert aus der Config für Alert-E-Mails auflösen
public function resolveEmailTarget(array $config): ?string;
}
Check in src/Check/ ablegen:
<?php
declare(strict_types=1);
namespace App\Check;
use App\Entity\CheckResult;
use App\Entity\SiteCheck;
use App\Enum\CheckStatus;
use App\Enum\RunnerMode;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
#[AutoconfigureTag('watchdog.check')]
final class MeinEigenerCheck implements CheckInterface
{
public function getType(): string
{
return 'mein_check'; // Muss eindeutig sein; wird in der Datenbank gespeichert
}
public function getLabel(): string
{
return 'Mein eigener Check';
}
public function runnerMode(): RunnerMode
{
return RunnerMode::Both; // AgentOnly, DashboardOnly oder Both
}
public function getDefaultConfig(): array
{
return [
'schwellwert' => 100,
];
}
public function getConfigSchema(): array
{
return [
[
'name' => 'schwellwert',
'label' => 'Schwellwert',
'type' => 'number',
'required' => false,
'default' => 100,
'placeholder' => '100',
'help' => 'Fehler, wenn der gemessene Wert diesen Schwellwert ĂĽberschreitet.',
],
];
}
public function getEmailTargetLabel(): ?string
{
return 'Schwellwert';
}
public function resolveEmailTarget(array $config): ?string
{
return isset($config['schwellwert']) ? (string) $config['schwellwert'] : null;
}
public function run(SiteCheck $check): CheckResult
{
$config = array_merge($this->getDefaultConfig(), $check->getConfig());
$result = new CheckResult();
$result->setCheck($check);
$schwellwert = (int) ($config['schwellwert'] ?? 100);
$wert = $this->messen();
if ($wert > $schwellwert) {
$result->setStatus(CheckStatus::Fail);
$result->setMessage(sprintf('Wert %d ĂĽberschreitet Schwellwert %d', $wert, $schwellwert));
} else {
$result->setStatus(CheckStatus::Ok);
$result->setMessage(sprintf('Wert %d (Schwellwert: %d)', $wert, $schwellwert));
}
return $result;
}
private function messen(): int
{
// Eigene Mess-Logik
return 42;
}
}
Das war's — keine Service-Registrierung nötig. Das #[AutoconfigureTag]-Attribut zusammen mit #[AutowireIterator] im CheckRegistry erledigt das Wiring automatisch.
âś…
getLabel()ist die einzige Quelle der WahrheitDas Label aus
CheckInterface::getLabel()wird überall in der UI verwendet: im Typ-Auswahlfeld, in der Check-Liste, auf dem Dashboard und in Alert-E-Mails. Der Twig-Filtercheck_labelleitet den Aufruf intern überCheckRegistryweiter — kein weiteres Mapping nötig.Auf korrekte Schreibweise achten — der Filter macht keine Nachbearbeitung:
getLabel()UI-Ausgabe Richtig? return 'DNS';DNS ✅ return 'Dns';Dns ❌ return 'TCP Port';TCP Port ✅ return 'Tcp port';Tcp port ❌ return 'Load Average';Load Average ✅
| Modus | Wann verwenden |
|---|---|
RunnerMode::DashboardOnly |
Benötigt eine URL aus dem Client-Datensatz (HTTP, SSL). Kann nicht auf Agenten laufen. |
RunnerMode::AgentOnly |
Benötigt direkten Host-Zugriff — Dateisystem, /proc, Prozessliste. Kann nicht auf dem Dashboard-Worker laufen. |
RunnerMode::Both |
Netzwerk-Level-Check, der von jedem Host aus funktioniert: TCP, Redis, DNS, Datenbankverbindungen. |
Jeder Eintrag definiert ein Formularfeld in der UI:
[
'name' => 'mein_feld', // Config-SchlĂĽssel (wird als JSON gespeichert)
'label' => 'Mein Feld', // Bezeichnung im Formular
'type' => 'text', // Feldtyp (siehe Tabelle unten)
'required' => true, // Client- und serverseitig validiert
'default' => '', // VorausgefĂĽllter Standardwert
'placeholder' => 'z.B. /var/log', // Platzhaltertext im Input
'help' => 'Erklärungstext.', // Hilfetext unterhalb des Feldes
]
VerfĂĽgbare Feldtypen:
| Typ | Rendert als |
|---|---|
text |
Text-Input |
number |
Numerischer Input (Integer) |
float |
Numerischer Input (Dezimal) |
password |
Passwort-Input (maskiert) |
checkbox |
Boolean-Schalter |
duration |
Dauer-Picker (in Minuten gespeichert, als Stunden/Tage angezeigt) |
client_url_select |
Dropdown der fĂĽr diesen Client konfigurierten URLs |
client_url_multiselect |
Mehrfachauswahl der Client-URLs |
Leeres Array [] zurückgeben, wenn der Check keine Konfiguration benötigt.
public function run(SiteCheck $check): CheckResult
{
// Immer zuerst Defaults zusammenführen — gespeicherte Config kann unvollständig sein
$config = array_merge($this->getDefaultConfig(), $check->getConfig());
$result = new CheckResult();
$result->setCheck($check);
// Einen von: Ok, Warn, Fail, Unknown setzen
$result->setStatus(CheckStatus::Ok);
// Lesbarer Text fĂĽr Dashboard und Alert-E-Mails
$result->setMessage('Alles in Ordnung');
// Optional: explizite Antwortzeit in ms setzen
// Wenn nicht gesetzt, misst das Framework die run()-Dauer automatisch
$result->setResponseTimeMs(42);
return $result;
}
Status-Semantik:
| Status | Bedeutung | Löst Alerts aus? |
|---|---|---|
CheckStatus::Ok |
Check erfolgreich | Ja — bei fail → ok-Übergang (Wiederherstellung) |
CheckStatus::Warn |
Nähert sich einem Schwellwert | Nein (standardmäßig) |
CheckStatus::Fail |
Check fehlgeschlagen | Ja — bei ok → fail-Übergang |
CheckStatus::Unknown |
Check konnte nicht ausgefĂĽhrt werden (Fehlkonfiguration, nicht verfĂĽgbare Ressource) | Nein |
Unknown verwenden, wenn der Check kein sinnvolles Ergebnis liefern kann — fehlende Konfiguration, nicht vorhandene Datei, nicht unterstützte Plattform. Löst keine Fail-Alerts aus.
Eigene Checks sind normale Symfony-Services mit vollem Autowiring:
#[AutoconfigureTag('watchdog.check')]
final class MeinEigenerCheck implements CheckInterface
{
public function __construct(
private readonly HttpClientInterface $httpClient,
private readonly LoggerInterface $logger,
) {}
}
Test in tests/Unit/Check/ anlegen:
final class MeinEigenerCheckTest extends TestCase
{
private MeinEigenerCheck $check;
protected function setUp(): void
{
$this->check = new MeinEigenerCheck();
}
public function testGibtOkZurueckWennUnterSchwellwert(): void
{
$siteCheck = new SiteCheck();
$siteCheck->setType('mein_check');
$siteCheck->setConfig(['schwellwert' => 100]);
$result = $this->check->run($siteCheck);
$this->assertSame(CheckStatus::Ok, $result->getStatus());
}
public function testGibtFailZurueckWennSchwellwertUeberschritten(): void
{
$siteCheck = new SiteCheck();
$siteCheck->setType('mein_check');
$siteCheck->setConfig(['schwellwert' => 0]);
$result = $this->check->run($siteCheck);
$this->assertSame(CheckStatus::Fail, $result->getStatus());
}
}
Infrastruktur-Abhängigkeiten (HTTP-Clients, Dateisystem-Reader) über Interfaces mocken — alle eingebauten Checks folgen diesem Muster mit *Interface-Contracts für Testbarkeit.
Wenn ein eigener Check-Typ allgemein nĂĽtzlich ist, kann er als Pull Request eingereicht werden. Siehe CONTRIBUTING.md fĂĽr den Entwicklungs-Workflow und Code-Style-Anforderungen.