Zum Hauptinhalt springen

Health & Monitoring Commands

Commands für Health-Checks, Queue-Metriken, API-Quota-Tracking, Cron-Heartbeat-Monitoring und WebSocket-Server.


health:check-daily-scrapes

Prüft ob die täglichen Scrape-Jobs für Instagram und YouTube erfolgreich erstellt wurden. Sendet Alert-E-Mails an Admins wenn Jobs fehlen.

health:check-daily-scrapes
{--dry-run : Ergebnisse anzeigen ohne E-Mails zu senden}

Beispiele

# Standard Health-Check
php artisan health:check-daily-scrapes

# Vorschau ohne E-Mail-Versand
php artisan health:check-daily-scrapes --dry-run

Optionen

OptionBeschreibung
--dry-runZeigt Ergebnisse und Alert-Inhalte ohne E-Mail-Versand

Checks

PlattformPrüfungAlert-BedingungQuelle
InstagramCache-Marker + Job-CountNur wenn Queue-Command gar nicht gelaufen ist (0 Jobs UND kein Cache-Marker)collector_jobs + Cache health:instagram_queue_ran:{date}
YouTubedaily_sync_runs Record für heuteexpected_profiles = 0 (kein Sync-Run erstellt)daily_sync_runs Tabelle
YouTube Video SyncCache-Marker (ab 15:00 UTC)Kein Cache-Marker health:youtube_video_sync_ran:{date} nach 15:00 UTCCache (gesetzt von youtube:sync-video-stats, TTL: 36h UTC)
Stuck LeasesExpired Leases > 10 MinMehr als 100 gelockte Jobs mit abgelaufenem Leasecollector_jobs (status=leased, lease_expires_at < now-10min)
Progress StagnationCompletion Rate nach 2hWeniger als 10% Instagram-Jobs nach >2h abgeschlossencollector_jobs (nur wenn Cache-Marker existiert)

Instagram-Check: Cache-Marker vs. Job-Count

Der Instagram-Check nutzt einen zweistufigen Ansatz:

  • Job-Count: Alle collector_jobs mit source=instagram für heute zählen
  • Cache-Marker: health:instagram_queue_ran:{date} wird von social:queue-daily-instagram gesetzt und enthält Statistiken (queued, skipped_rotation, skipped_today, skipped_open)

Wichtig: 0 Jobs ist kein Alert wenn der Cache-Marker existiert. Das ist normal bei Rotation (alle Profile außerhalb des aktuellen Buckets), bereits geschrapten Profilen oder offenen Jobs vom Vortag. Nur wenn weder Jobs noch Cache-Marker existieren, wird ein Alert ausgelöst.

Cache-Marker TTL

Die Health-Marker werden mit now('UTC')->addHours(36) TTL gesetzt (nicht endOfDay()). Grund: Bei App-Timezone Europe/Berlin laeuft endOfDay() bereits um ~22:59 UTC ab, aber der Health-Check laeuft bis 23:30 UTC — das verursachte False-Positive-Alerts.

MarkerGesetzt vonTTL
health:instagram_queue_ran:{date}social:queue-daily-instagram36h (UTC)
health:youtube_video_sync_ran:{date}youtube:sync-video-stats36h (UTC)
health:daily-scrape-alert:{date}health:check-daily-scrapesbis endOfDay()

Interne Schritte

  1. Instagram-Jobs fuer heute zaehlen + Cache-Marker pruefen
  2. YouTube-Status laden (DailySyncRun fuer heute)
  3. YouTube Video Sync Cache-Marker pruefen (erst ab 15:00 UTC, da der Command um 14:00 laeuft)
  4. Stuck Leases pruefen (>100 abgelaufene Leases aelter als 10 Minuten)
  5. Progress Stagnation pruefen (<10% abgeschlossen nach >2h, nur wenn Cache-Marker existiert)
  6. Alert nur generieren wenn eine Plattform echtes Problem hat (s.o.)
  7. Daily Cooldown: Pro Tag wird maximal eine Alert-E-Mail gesendet (Cache-Key: health:daily-scrape-alert:{date})
  8. E-Mail an alle config('postbox.admin_emails') senden
  9. E-Mail-Versand wird ueber LogSentMail Listener protokolliert

Alert-E-Mail Inhalt

Die Alert-E-Mail enthält:

  • Betroffene Plattform und das Problem
  • Den fehlgeschlagenen Artisan Command
  • Empfohlene Maßnahmen (Cron-Heartbeats, Schedule-Cache, manueller Lauf, Logs)

Schedule

Alle 2 Stunden bei :30 (ungerade: 01:30, 03:30, 05:30, ...).

Location: app/Console/Commands/CheckDailyScrapeHealth.php


queue:metrics-snapshot

Speichert 15-Minuten Queue-Metriken für das Admin Queue-Chart. Erfasst sowohl Collector-Jobs als auch Laravel Queue-Jobs.

queue:metrics-snapshot
{--from= : Backfill-Start (UTC)}
{--to= : Backfill-Ende (UTC)}

Beispiele

# Aktuelles 15-Minuten-Bucket erfassen
php artisan queue:metrics-snapshot

# Historische Buckets nachfüllen
php artisan queue:metrics-snapshot --from="2026-02-01 00:00:00" --to="2026-02-02 00:00:00"

Optionen

OptionBeschreibung
--from=Backfill Start-Zeitpunkt (UTC)
--to=Backfill End-Zeitpunkt (UTC)

Metriken-Serien

Jeder 15-Minuten-Snapshot enthält pro Plattform:

SerieBeschreibung
totalGesamtzahl aller Jobs
todoNoch nicht begonnene Jobs
queuedIn der Queue wartend
leasedAktuell in Bearbeitung
completedErfolgreich abgeschlossen
errorFehlgeschlagen

Interne Schritte

  1. Aktuelles 15-Minuten-Bucket bestimmen (UTC)
  2. Collector-Jobs (Instagram): Status-Counts aus collector_jobs aggregieren
  3. Laravel Queue-Jobs (YouTube): Counts aus jobs Tabelle nach Queue-Name mappen
  4. YouTube Daily Scrape: Counts aus social_profile_daily_metrics und Failures aus daily_sync_runs
  5. Totals inkludieren offenen Backlog (Jobs von Vortagen), damit Charts nicht bei UTC-Mitternacht auf 0 fallen
  6. "All"-Platform Chart enthält extra YouTube-Total-Linie für Vergleich

Schedule

Alle 15 Minuten mit .withoutOverlapping(5).

Location: app/Console/Commands/SnapshotQueueMetrics.php


queue:retry-failed-range

Setzt fehlgeschlagene Laravel Queue-Jobs in einem Zeitraum zurück in die Queue. Optional filterbar nach Queue-Name.

queue:retry-failed-range
{--from= : Start-Zeitpunkt}
{--to= : End-Zeitpunkt}
{--queue= : Queue-Name Filter}
{--dry-run : Vorschau ohne Retry}

Beispiele

# Alle Failed Jobs in einem Zeitraum retrien
php artisan queue:retry-failed-range --from="2026-02-01 00:00:00" --to="2026-02-02 23:59:59"

# Nur bestimmte Queue
php artisan queue:retry-failed-range --from="2026-02-01 00:00:00" --to="2026-02-02 23:59:59" --queue=imports-youtube-video

# Vorschau
php artisan queue:retry-failed-range --from="2026-02-01 00:00:00" --to="2026-02-02 23:59:59" --dry-run

Optionen

OptionBeschreibung
--from=Start-Zeitpunkt (Pflicht)
--to=End-Zeitpunkt (Pflicht)
--queue=Queue-Name (z.B. imports-youtube, imports-youtube-video, ai-detection)
--dry-runZeigt was retried würde, ohne Ausführung

Hinweise

  • Arbeitet mit Jobs aus der failed_jobs Tabelle (Laravel Standard)
  • Für Instagram-Jobs stattdessen collector:retry-failed verwenden

Schedule

Manuell.

Location: app/Console/Commands/RetryFailedQueueJobs.php


google:sync-api-usage

Synchronisiert Google API Quota-Nutzung und Limits für alle konfigurierten Projekte. Basis für die Quota-Anzeige im Admin-Dashboard und die Quota-Prüfung in ResearchQuotaService.

google:sync-api-usage

Beispiele

# Quota-Nutzung synchronisieren
php artisan google:sync-api-usage

Interne Schritte

  1. Quota Limits: Abruf über serviceusage.googleapis.com/v1beta1 API
  2. Quota Usage: Abruf über monitoring.googleapis.com/v3 API (timeSeries mit rate/net_usage)
  3. Daten in google_api_quota_limits und google_api_quota_usages Tabellen speichern
  4. Sync-Timestamps und Fehler für Admin-Dashboard cachen

Konfiguration

# Kommaseparierte Google Cloud Projekt-IDs
GOOGLE_API_USAGE_PROJECTS=project-1,project-2

# Service-Account Credentials
GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json

Schedule

Stündlich mit .withoutOverlapping(10).

Location: app/Console/Commands/SyncGoogleApiUsage.php


reverb:start

Startet den Laravel Reverb WebSocket-Server. In Production als Forge Daemon konfiguriert. Stellt WebSocket-Verbindungen für Echtzeit-Events bereit (Notifications, Import-Progress, Daily Scrape Status).

reverb:start
{--host= : Bind-Adresse}
{--port= : Port}
{--debug : Debug-Logging aktivieren}

Beispiele

# Lokale Entwicklung
php artisan reverb:start

# Production (alle Interfaces, Port 8081)
php artisan reverb:start --host=0.0.0.0 --port=8081

Optionen

OptionBeschreibung
--host=Bind-Adresse (Default: 127.0.0.1, Production: 0.0.0.0)
--port=Port (Default: 8081, Nginx leitet dahin weiter)
--debugDebug-Logging für Verbindungen/Nachrichten aktivieren

Registrierte Event-Channels

ChannelEventsBeschreibung
user.{id}Daily Scrape, Sync, Sanitize, ReactivatePer-User Notifications
profile.{id}Related Profiles CalculatedPer-Profile Updates
workspace.{id}Import ProgressWorkspace-weite Events
adminUser RegisteredAdmin-only Events

Production-Setup (Forge)

# Daemon-Konfiguration in Forge:
php8.4 /home/forge/app.postbox.so/current/artisan reverb:start --host=0.0.0.0 --port=8081

Nginx Proxy-Config leitet WebSocket-Traffic von Port 443 auf den internen Port 8081 weiter.

Schedule

Dauerhaft als Daemon (kein Cron-Job).

Location: Laravel Reverb Package


server:check-alerts

Liest aktuelle Server-Metriken aus Laravel Pulse und prüft sie gegen konfigurierte Alert-Schwellwerte. Sendet Benachrichtigungen wenn Metriken überschritten werden.

server:check-alerts

Beispiele

# Standard-Check (alle Metriken prüfen)
php artisan server:check-alerts

Geprüfte Metriken

MetrikQuelleBeschreibung
cpu_percentPulse systemCPU-Auslastung in Prozent
ram_percentPulse systemRAM-Auslastung (berechnet aus memory_used / memory_total)
disk_percentPulse systemFestplatten-Auslastung (erstes Storage-Volume)
swap_percentPulse swap_metricsSwap-Auslastung in Prozent
pg_connectionsPulse pg_metricsAktive PostgreSQL-Verbindungen
pg_cache_hitPulse pg_metricsPostgreSQL Cache-Hit-Ratio

Interne Logik

  1. Pulse Storage-Werte auslesen (system, swap_metrics, pg_metrics)
  2. JSON-Werte parsen und in Metriken-Array überführen
  3. ServerAlertService::check($metrics) aufrufen — prüft gegen Schwellwerte
  4. Alert-E-Mails oder Benachrichtigungen bei Überschreitung

Schedule

Alle 5 Minuten mit .withoutOverlapping().

Location: app/Console/Commands/CheckServerAlerts.php


mail:resume

Hebt die Flood-Guard-Pause für den Mailversand auf. Wird manuell ausgeführt nachdem der automatische Flood-Schutz den Versand pausiert hat.

mail:resume

Beispiele

# Mailversand fortsetzen
php artisan mail:resume

Interne Logik

  1. Prüft ob Mail aktuell pausiert ist (MailFloodGuard::isPaused())
  2. Falls nicht pausiert: Info-Meldung, kein Fehler
  3. Falls pausiert: MailFloodGuard::resume() — löscht Redis-Keys für Pause, Counter und Notification-Flag

Hinweise

  • Alternativ über die Admin-UI: /admin/statistics → "Mailversand fortsetzen" Button
  • Nach Resume werden pausierte Queue-Jobs automatisch weiterverarbeitet

Schedule

Manuell (bei Flood-Guard-Auslösung).

Location: app/Console/Commands/MailResume.php


mail:status

Zeigt den aktuellen Mail-System-Status auf der Konsole an. Nützlich für Quick-Checks ohne Browser-Zugriff.

mail:status

Beispiele

# Vollständigen Status anzeigen
php artisan mail:status

Angezeigte Informationen

BereichMetriken
Flood GuardStatus (Aktiv/Pausiert), Mails/Minute, Mails/Stunde, Limits
Mail-Log (24h)Gesendet gesamt, Gesendet letzte Stunde, Fehlgeschlagen
Top Mail-TypenDie 10 häufigsten Mail-Typen der letzten 24h mit Anzahl

Interne Logik

  1. MailFloodGuard::getStatus() für Redis-Counter und Pause-Status
  2. MailLog Queries für 24h- und 1h-Statistiken
  3. Gruppierung nach mail_type für Top-10 Tabelle

Schedule

Manuell.

Location: app/Console/Commands/MailStatus.php


notifications:rollup-stats

Aggregiert Mail-Log Einträge in tägliche notification_stats für Admin-Charts. Konsolidiert die einzelnen mail_log Rows zu täglichen Zählern pro Mail-Typ.

notifications:rollup-stats
{--date= : Bestimmtes Datum aggregieren (Y-m-d)}

Optionen

OptionTypDefaultBeschreibung
--date=stringGesternDatum für die Aggregation (Format: Y-m-d)

Beispiele

# Standard: gestrige Mails aggregieren
php artisan notifications:rollup-stats

# Bestimmtes Datum nachholend aggregieren
php artisan notifications:rollup-stats --date=2026-02-10

Interne Logik

  1. mail_log Einträge für das Datum gruppiert nach mail_type zählen
  2. Erfolgreiche Mails → Channel email in notification_stats upserten
  3. Fehlgeschlagene Mails → Channel email_failed in notification_stats upserten
  4. Upsert-Pattern: Bei existierendem Record wird count überschrieben

Schedule

Täglich um 00:30 UTC.

Location: app/Console/Commands/RollupNotificationStats.php


api-tokens:check-alerts

Prueft API-Tokens auf Inaktivitaet und bevorstehenden Ablauf. Benachrichtigt Admins via Nachrichtenzentrale.

api-tokens:check-alerts

Beispiele

# Standard-Check (Inaktivitaet + Ablauf)
php artisan api-tokens:check-alerts

Checks

CheckSchwellwertConfigBeschreibung
Inaktivitaet7 Tagepostbox.api_tokens.inactivity_alert_daysToken seit X Tagen nicht benutzt
Ablauf14 Tagepostbox.api_tokens.expiry_warning_daysToken laeuft in weniger als X Tagen ab

Interne Logik

  1. ApiTokenService::getInactiveTokens() — findet nicht-revozierte Tokens mit last_used_at < now - X days
  2. ApiTokenService::getExpiringTokens() — findet nicht-revozierte Tokens mit expires_at innerhalb X Tagen
  3. Pro Token wird ein Cache-Key gesetzt (24h TTL), um doppelte Alerts zu unterdruecken
  4. Benachrichtigung an alle Admin-User via NotificationService::announceToUser()

Notification-Typen

TypIconBeschreibung
api_token_inactiveexclamation-triangleToken seit X Tagen unbenutzt
api_token_expiringclockToken laeuft in X Tagen ab

Schedule

Taeglich um 08:00 UTC mit .withoutOverlapping().

Location: app/Console/Commands/CheckApiTokenAlerts.php


cron:check-heartbeats

Prüft alle konfigurierten Cron-Heartbeats gegen ihre max_minutes-Schwellwerte und sendet Alerts bei Ausfällen.

cron:check-heartbeats

Beispiele

# Standard: Alle Heartbeats prüfen
php artisan cron:check-heartbeats

Output

12/22 heartbeats OK, 0 FAILED, 10 NOT_REQUIRED, 0 MUTED

Bei Ausfällen werden Details angezeigt:

12/22 heartbeats OK, 1 FAILED, 9 NOT_REQUIRED, 0 MUTED
FAILED: Pipeline Run (2h) (180 min ago, max: 120 min)
→ Abhängigkeit: Kein vorgelagerter Fehler erkannt

Funktionsweise

  1. Liest alle Heartbeats aus config('postbox.health.cron_heartbeats')
  2. Prüft Alter gegen max_minutes — überfällig = FAILED
  3. Deploy-Grace-Period: Nach Deployment NOT_REQUIRED statt FAILED
  4. Muted Heartbeats werden übersprungen (MUTED)
  5. Bei Statuswechsel okfailed: sofortige Admin-Notification + Event-Log
  6. Bei anhaltendem Ausfall: Cooldown (Default: 60 Min) verhindert Spam
  7. Eskalation (E3): Nach 60 Min ohne Recovery zusätzliche Eskalations-Notification
  8. Bei Recovery (failedok): Recovery-Notification + Cache-Cleanup
  9. Dependency-Check (E4): Bei Ausfall werden vorgelagerte Abhängigkeiten geprüft

Notification-Typen

TypBeschreibungEvent-Log
cron_heartbeat_failedHeartbeat überfälligfailed Event
cron_heartbeat_recoveredHeartbeat wiederhergestelltrecovered Event
cron_heartbeat_escalationSeit >60 Min ausgefallenescalated Event
cron_heartbeat_mute_reminderWöchentliche Erinnerung an stumm geschaltete Heartbeats

Konfiguration

// config/postbox.php → cron_monitoring
'cron_monitoring' => [
'alert_cooldown_minutes' => 60, // Cooldown zwischen Alerts
'escalation_after_minutes' => 60, // Eskalation nach X Minuten
'event_retention_days' => 30, // Event-Historie-Retention
'dependencies' => [ // Abhängigkeits-Map
'scores_calculate' => ['pipeline_run'],
'sitemap_generate' => ['public_explorer_refresh'],
// ...
],
],

Schedule

Alle 5 Minuten mit .withoutOverlapping(10).

Zusätzlich:

  • Mute-Reminder: Wöchentlich Montag 09:00 UTC — erinnert Admins an stumm geschaltete Heartbeats
  • Event-Pruning: Täglich 03:20 UTC — löscht Events älter als event_retention_days

Location: app/Console/Commands/CheckCronHeartbeats.php, app/Services/CronMonitoring/CronHeartbeatMonitorService.php


Übersicht: Monitoring-Infrastruktur

┌─────────────────────┐
│ cron:check- │──── Heartbeat-Alerting (In-App Cron Monitoring)
│ heartbeats │
├─────────────────────┤
│ health:check- │──── Alert E-Mail bei fehlenden Scrape-/Video-Sync-Jobs
│ daily-scrapes │
├─────────────────────┤
│ queue:metrics- │──── 15-Min Snapshots für Admin Queue Charts
│ snapshot │
├─────────────────────┤
│ google:sync- │──── Stündliche API-Quota Synchronisierung
│ api-usage │
├─────────────────────┤
│ server:check- │──── CPU/RAM/Disk/DB-Alerts aus Pulse-Daten
│ alerts │
├─────────────────────┤
│ api-tokens: │──── Token-Inaktivitaet + Ablauf-Warnungen
│ check-alerts │
├─────────────────────┤
└─────────────────────┘

server:snapshot

Erfasst Server-Metriken (CPU, RAM, Swap, Disk, PG-Connections, PG-Cache-Hit) aus Pulse-Recordern in die eigene server_metrics Tabelle. Ersetzt direkte Pulse-Zeitreihen-Abfragen für langfristiges Monitoring.

# Standard: Snapshot erstellen
php artisan server:snapshot

# Alte Snapshots prunen (> 90 Tage)
php artisan server:snapshot --prune
OptionBeschreibung
--pruneSnapshots älter als Retention (90 Tage) löschen

Interne Logik:

  1. Liest aktuelle Werte aus Pulse-Recordern (ServerMetrics, PublicStorageMetrics, IsolatedBeat)
  2. Erstellt einen ServerMetric Record mit Timestamp
  3. Bei --prune: löscht Records älter als server-monitoring.server_metrics.retention_days

Schedule:

  • server:snapshot — Alle 5 Minuten
  • server:snapshot --prune — Täglich 02:20 UTC

Location: app/Console/Commands/ServerSnapshot.php


server:rollup-hourly

Aggregiert 5-Minuten-Snapshots aus server_metrics zu stündlichen Durchschnittswerten in server_metrics_hourly. Ermöglicht langfristige Trend-Analyse (365 Tage Retention) ohne hohen Speicherverbrauch.

# Standard: gestrige Stunden aggregieren
php artisan server:rollup-hourly

# Bestimmten Tag aggregieren
php artisan server:rollup-hourly --date=2026-02-20

# Mehrere Tage rückwirkend
php artisan server:rollup-hourly --days=7

# Mit Pruning alter Rollups
php artisan server:rollup-hourly --prune
OptionTypDefaultBeschreibung
--datestringgesternBestimmtes Datum (YYYY-MM-DD)
--daysint1Anzahl Tage rückwirkend
--pruneflagfalseRollups älter als Retention (365 Tage) löschen

Schedule: Täglich 03:00 UTC (nach Snapshot-Prune) Location: app/Console/Commands/ServerRollupHourly.php


pulse:check-size

Prüft wöchentlich die Größe der Pulse-Tabellen (pulse_entries, pulse_aggregates, pulse_values). Sendet Admin-Notification wenn die Gesamtgröße den Schwellwert überschreitet.

php artisan pulse:check-size

Schwellwert: 2 GB (konfigurierbar via server-monitoring.pulse_size_threshold_gb).

Schedule: Wöchentlich Sonntag 03:30 UTC Location: app/Console/Commands/PulseCheckSize.php


/up_system — System Health Endpoint

Externer Health-Check-Endpoint für UptimeRobot und andere Monitoring-Tools. Gibt immer HTTP 200 zurück — der tatsächliche Status wird über den Response-Body kommuniziert (Keyword-Monitoring).

URL & Authentifizierung

GET /up_system
Header: X-Health-Token: <HEALTH_TOKEN>

Geschützt durch VerifyHealthToken-Middleware. Token wird in .env konfiguriert:

# Token für den /up_system Health-Endpoint
HEALTH_TOKEN=your-secret-token

Response-Format

=== TAGES-PIPELINE (2026-03-09) ===

Datensammlung:
[OK] Instagram Daily Scrape 3.200 -> 3.150 Abgeschl. 14:32
[>>] YouTube Daily Sync 1.800 -> 920 Läuft [##########..........] 51%
...

Verarbeitung:
[OK] Pipeline Run - -> - Abgeschl. 03:12
...

Job-Durchsatz (Collector): 1min=12 5min=58 15min=165
Job-Durchsatz (Queue): 1min=3 5min=14 15min=42

============================================================

[2026-03-09 14:35:00 UTC]

DATABASE RUNNING
Database connection OK

CACHE RUNNING
Cache connection OK

REVERB RUNNING
Reverb listening on 127.0.0.1:8081

QUEUE_WORKERS RUNNING
42 pending, 3 processing

FAILED_JOBS RUNNING
12 failed jobs

INSTAGRAM_COLLECTOR RUNNING
3150/3200 processed, 5 failed, 45 pending | 120 queued total, 340 low-prio profiles

YOUTUBE_DAILY_SYNC RUNNING
920/1800 (51.1%), 3 failed - in progress | 45 queued total, 180 low-prio profiles

YOUTUBE_VIDEO_SYNC RUNNING
85 video sync jobs queued at 14:02

COLLECTOR_HEARTBEAT RUNNING
Last activity: 2 min ago

API_TOKENS RUNNING
3 active tokens, 0 expiring

GOOGLE_API_QUOTA RUNNING
4500/30000 (15.0%) across 3 keys

EXPLORE_METRICS RUNNING
Explore metrics fresh: last calculated 8h ago

PIPELINE_STATUS RUNNING
12/14 steps completed (85%)

PULSE_TABLE_SIZE RUNNING
Pulse tables: 245.3 MB

CRON_INSTAGRAM_SCRAPE RUNNING
Instagram Scrape (hourly) — last run 45 min ago

CRON_YOUTUBE_SCRAPE RUNNING
YouTube Daily Sync (2h, 09-23 UTC) — last run 90 min ago

...

STATUS: ALL_SYSTEMS_OPERATIONAL

Health-Checks

Der Endpoint führt 14 statische Checks + dynamische CRON_*-Checks durch:

CheckService-MethodeWas wird geprüftFAILED wenn
DATABASEcheckDatabase()SELECT 1DB nicht erreichbar
CACHEcheckCache()Cache put/get/forgetRedis/Cache nicht erreichbar
REVERBcheckReverb()TCP-Socket auf Reverb-PortWebSocket-Server down
QUEUE_WORKERScheckQueueWorkers()Pending Jobs + Worker-Aktivität— (nur WARNING)
FAILED_JOBScheckFailedJobs()Anzahl failed_jobs— (nur WARNING ab 100)
INSTAGRAM_COLLECTORcheckInstagramDailyScrape()Collector-Jobs für heute0 Jobs nach 01:00 UTC
YOUTUBE_DAILY_SYNCcheckYouTubeDailySync()DailySyncRun für heuteKein Run nach 09:00 UTC
YOUTUBE_VIDEO_SYNCcheckYouTubeVideoSync()Cache-MarkerKein Marker nach 15:00 UTC
COLLECTOR_HEARTBEATcheckCollectorHeartbeat()Letzte Collector-Aktivität— (nur WARNING)
API_TOKENScheckApiTokenHealth()Token-Status via ApiTokenServiceCheck-Exception
GOOGLE_API_QUOTAcheckGoogleApiQuota()YouTube API Quota-Nutzung≥95% verbraucht
EXPLORE_METRICScheckExploreMetricsFreshness()daily_calculated_at Alter>72h stale
PIPELINE_STATUScheckPipelineStatus()PipelineStatusService Progress— (nur WARNING bei failed/overdue)
PULSE_TABLE_SIZEcheckPulseTableSize()pg_total_relation_size()— (nur WARNING ab 2 GB)
CRON_* (dynamisch)checkCronHeartbeats()Cache-Key cron:heartbeat:{key}Heartbeat älter als max_minutes

Status-Logik

StatusBedeutungUptimeRobot
RUNNINGCheck bestanden
WARNINGAuffällig, aber nicht kritischKein Alert (zählt als operational)
WAITINGNoch nicht gestartet (Zeitfenster, Deploy-Grace)Kein Alert
FAILEDCheck fehlgeschlagenAlert (SYSTEM_DEGRADED)

Gesamtstatus:

  • ALL_SYSTEMS_OPERATIONAL — kein Check hat FAILED (WARNING/WAITING sind OK)
  • SYSTEM_DEGRADED — mindestens ein Check hat FAILED

UptimeRobot prüft auf das Keyword ALL_SYSTEMS_OPERATIONAL. Fehlt es → Alert.

Deploy-Grace-Period

Nach einem Deployment oder Cache-Clear werden CRON_*-Checks für die Dauer ihres max_minutes-Werts als WAITING statt FAILED gemeldet. Der Deploy-Zeitpunkt wird automatisch im Cache gespeichert (deploy:timestamp, 48h TTL).

Quiet Hours

Queue-Worker-Check gibt WAITING statt WARNING zurück wenn die aktuelle UTC-Stunde im konfigurierten Quiet-Hours-Fenster liegt (Default: 04:00–06:00 UTC).

// config/postbox.php → health
'queue_quiet_start' => 4, // UTC-Stunde Start
'queue_quiet_end' => 6, // UTC-Stunde Ende

Unterstützt Wrap-Around (z.B. start=23, end=5 für 23:00–04:59 UTC). Deaktiviert wenn start === end.

Konfiguration

// config/postbox.php → health
'health' => [
'failed_jobs_threshold' => 100, // WARNING ab X failed_jobs
'collector_heartbeat_minutes' => 30, // Collector-Inaktivitäts-Schwelle
'queue_pending_threshold' => 5000, // Queue-Backlog WARNING-Schwelle
'queue_activity_window_minutes' => 30, // Zeitfenster für Worker-Aktivität
'queue_quiet_start' => 4, // Quiet Hours Start (UTC)
'queue_quiet_end' => 6, // Quiet Hours Ende (UTC)
'cron_heartbeats' => [ // Dynamische CRON_*-Checks
'key' => ['max_minutes' => 120, 'label' => 'Description'],
// ... alle heartbeat-Einträge
],
],

Dateien

DateiBeschreibung
app/Http/Controllers/SystemHealthController.phpController, formatiert Plain-Text-Output
app/Services/Health/SystemHealthService.phpService mit allen Health-Checks
app/Http/Middleware/VerifyHealthToken.phpToken-Authentifizierung
routes/web.phpRoute-Definition (/up_system)
config/postbox.phpSchwellwerte und Heartbeat-Konfiguration

health:log-snapshot

Loggt alle Health-Check-Ergebnisse in die health_check_logs-Tabelle für Kalibrierungs-Analyse.

health:log-snapshot
{--diff-only : Nur Status-Änderungen loggen}

Beispiele

# Alle Checks loggen
php artisan health:log-snapshot

# Nur geänderte Status loggen (spart Speicher)
php artisan health:log-snapshot --diff-only

Optionen

OptionBeschreibung
--diff-onlyVergleicht mit dem vorherigen Snapshot und loggt nur Checks mit geändertem Status

Funktionsweise

  1. Ruft SystemHealthService::runAllChecks() auf
  2. Speichert jeden Check als eigene Row in health_check_logs
  3. Bei --diff-only: Vergleicht mit Cache-Key health:last_snapshot_statuses und loggt nur Änderungen
  4. Auto-Prune: Model nutzt MassPrunable (14 Tage Retention)

Schedule

Alle 5 Minuten mit --diff-only und .withoutOverlapping(10).

Location: app/Console/Commands/LogHealthSnapshot.php


health:analyze-logs

Analysiert Health-Check-Logs für Schwellwert-Kalibrierung. Zeigt Status-Verteilung pro Check, stündliche Degradation-Raten und Percentile-basierte Schwellwert-Empfehlungen.

health:analyze-logs
{--days=7 : Anzahl der Tage für die Analyse}
{--check= : Nur einen bestimmten Check analysieren}

Beispiele

# Letzte 7 Tage analysieren
php artisan health:analyze-logs

# Letzter Tag, nur YouTube Scrape
php artisan health:analyze-logs --days=1 --check=CRON_YOUTUBE_SCRAPE

Optionen

OptionBeschreibung
--days=Analysezeitraum in Tagen (Default: 7)
--check=Nur einen bestimmten Check analysieren

Output

  • Per-Check Status-Verteilung: Visueller Balkendiagramm mit Prozentangaben
  • Stündliche SYSTEM_DEGRADED Rate: Zeigt zu welchen Uhrzeiten Probleme auftreten
  • Schwellwert-Empfehlungen (E3): P50/P90/P95/P99 Percentile + empfohlene max_minutes (P99 × 1.2)
  • Quiet-Hours-Empfehlungen: Wenn Failures in zusammenhängenden Stunden auftreten

Hinweise

  • Benötigt PostgreSQL (verwendet EXTRACT(HOUR FROM ...) Syntax)
  • Mindestens 5 Datenpunkte pro Check für Schwellwert-Empfehlungen
  • Empfohlene Schwellwerte basieren auf P99 + 20% Puffer

Location: app/Console/Commands/AnalyzeHealthLogs.php