morz-infoboard/docs/MONITORING-KONZEPT.md
Jesko Anschütz dd3ec070f7 Security-Review + Phase 6: CSRF, Rate-Limiting, Tenant-Isolation, Screenshot, Ansible
### Security-Fixes (K1–K6, W1–W4, W7, N1, N5–N6, V1, V5–V7)
- K1: CSRF-Schutz via Double-Submit-Cookie (httpapi/csrf.go + csrf_helpers.go)
- K2: requireScreenAccess() in allen manage-Handlern (Tenant-Isolation)
- K3: Tenant-Check bei DELETE /api/v1/media/{id}
- K4: requirePlaylistAccess() + GetByItemID() für JSON-API Playlist-Routen
- K5: Admin-Passwort nur noch als [gesetzt] geloggt
- K6: POST /api/v1/screens/register mit Pre-Shared-Secret (MORZ_INFOBOARD_REGISTER_SECRET)
- W1: Race Condition bei order_index behoben (atomare Subquery in AddItem)
- W2: Graceful Shutdown mit 15s Timeout auf SIGTERM/SIGINT
- W3: http.MaxBytesReader (512 MB) in allen Upload-Handlern
- W4: err.Error() nicht mehr an den Client
- W7: Template-Execution via bytes.Buffer (kein partial write bei Fehler)
- N1: Rate-Limiting auf /login (5 Versuche/Minute pro IP, httpapi/ratelimit.go)
- N5: Directory-Listing auf /uploads/ deaktiviert (neuteredFileSystem)
- N6: Uploads nach Tenant getrennt (uploads/{tenantSlug}/)
- V1: Upload-Logik konsolidiert in internal/fileutil/fileutil.go
- V5: Cookie-Name als Konstante reqcontext.SessionCookieName
- V6: Strukturiertes Logging mit log/slog + JSON-Handler
- V7: DB-Pool wird im Graceful-Shutdown geschlossen

### Phase 6: Screenshot-Erzeugung
- player/agent/internal/screenshot/screenshot.go erstellt
- Integration in app.go mit MORZ_INFOBOARD_SCREENSHOT_EVERY Config

### UX: PDF.js Integration
- pdf.min.js + pdf.worker.min.js als lokale Assets eingebettet
- Automatisches Seitendurchblättern im Player

### Ansible: Neue Rollen
- signage_base, signage_server, signage_provision erstellt
- inventory.yml und site.yml erweitert

### Konzept-Docs
- GRUPPEN-KONZEPT.md, KAMPAGNEN-AKTIVIERUNG.md, MONITORING-KONZEPT.md
- PROVISION-KONZEPT.md, TEMPLATE-EDITOR.md, WATCHDOG-KONZEPT.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 21:06:35 +01:00

10 KiB

Info-Board Neu - Logging- und Monitoring-Konzept

Ziel

Logging und Monitoring geben dem Betriebsteam vollstaendige Transparenz ueber:

  • Verhalten und Fehler auf dem Player
  • Verhalten und Fehler auf dem Server
  • Health-Status aller Screens
  • Netzwerk- und Synchronisierungsprobleme
  • Kapazitaetsauslastung und Trends

Das Konzept muss robust gegen Speicherplatz-Engpaesse auf dem Raspberry Pi arbeiten und zentralisiert auf dem Server auswertbar sein.

Logging-Architektur

Allgemeine Prinzipien

  • strukturiertes JSON-Logging — nicht Freitextloggen, sondern strukturierte Felder
  • Log-Levels: debug, info, warn, error, fatal
  • Zentrale Auswertung — Player loggen lokal und senden auch an Server
  • Rotation und Bereinigung — lokale Logs werden rotiert und komprimiert
  • Datenschutz — keine sensiblen Inhalte (Passwoerter, API-Keys) ins Log

Komponenten und ihre Logs

1. Player-Logs

Player-Agent

Der Agent protokolliert:

  • Startup/Shutdown

    {
      "ts": "2025-03-23T14:22:00Z",
      "level": "info",
      "component": "agent",
      "event": "startup",
      "config_file": "/etc/signage/config.yml",
      "screen_id": "info01"
    }
    
  • Server-Sync

    {
      "ts": "2025-03-23T14:22:05Z",
      "level": "info",
      "component": "agent.sync",
      "event": "sync_complete",
      "duration_ms": 342,
      "items_synced": 15,
      "bytes_downloaded": 4521000
    }
    
  • MQTT-Ereignisse

    {
      "ts": "2025-03-23T14:22:10Z",
      "level": "info",
      "component": "agent.mqtt",
      "event": "playlist_changed",
      "source": "mqtt",
      "cause": "playlist-changed-event"
    }
    
  • Fehler

    {
      "ts": "2025-03-23T14:22:15Z",
      "level": "error",
      "component": "agent.cache",
      "event": "download_failed",
      "media_id": "abc123",
      "url": "https://cdn.example.com/video.mp4",
      "error": "connection_timeout",
      "retry_count": 2
    }
    
  • Watchdog-Ereignisse (siehe WATCHDOG-KONZEPT.md)

Player-UI

Die lokale Web-App protokolliert:

  • Item-Wechsel

    {
      "ts": "2025-03-23T14:23:00Z",
      "level": "info",
      "component": "ui",
      "event": "item_change",
      "previous_item": "img-001",
      "current_item": "video-002",
      "source": "campaign"
    }
    
  • Rendering-Fehler

    {
      "ts": "2025-03-23T14:23:05Z",
      "level": "warn",
      "component": "ui.renderer",
      "event": "render_failed",
      "item_id": "url-003",
      "media_type": "webpage",
      "error": "load_timeout",
      "timeout_ms": 10000
    }
    
  • Overlay-Status-Aenderungen

    {
      "ts": "2025-03-23T14:23:10Z",
      "level": "info",
      "component": "ui.overlay",
      "event": "status_change",
      "old_status": "online",
      "new_status": "offline",
      "reason": "broker_connection_lost"
    }
    

Chromium

Der Browser ist schwer zu loggable, aber systemd journal erfasst:

  • Startup und Argumente
  • Crash-Meldungen
  • Fehlerrückmeldungen bei Seitenladefehler

2. Server-Logs

Backend-API

Der Server protokolliert:

  • HTTP-Requests (strukturiert, nicht kompletter Request-Body)

    {
      "ts": "2025-03-23T14:22:20Z",
      "level": "info",
      "component": "server.http",
      "method": "POST",
      "path": "/api/v1/screens/info01/playlist",
      "status": 200,
      "duration_ms": 34,
      "user_id": "admin123",
      "tenant_id": "tenant01"
    }
    
  • Datenbank-Operationen (nur bei Debug-Level)

    {
      "ts": "2025-03-23T14:22:25Z",
      "level": "debug",
      "component": "server.db",
      "query": "UPDATE playlists SET updated_at = NOW() WHERE screen_id = $1",
      "duration_ms": 5,
      "rows_affected": 1
    }
    
  • Fehler und Exceptions

    {
      "ts": "2025-03-23T14:22:30Z",
      "level": "error",
      "component": "server.api",
      "event": "media_download_failed",
      "media_id": "abc123",
      "reason": "storage_quota_exceeded",
      "available_bytes": 1024000,
      "required_bytes": 50000000
    }
    
  • Admin-Kommandos

    {
      "ts": "2025-03-23T14:22:35Z",
      "level": "info",
      "component": "server.command",
      "event": "command_sent",
      "command_type": "restart_player",
      "target_screen": "info01",
      "triggered_by_user": "admin123"
    }
    

Provisionierungs-Worker

{
  "ts": "2025-03-23T14:22:40Z",
  "level": "info",
  "component": "server.provision",
  "event": "provision_started",
  "screen_id": "new_display_01",
  "target_ip": "192.168.1.50",
  "ansible_playbook": "site.yml"
}

Log-Format und Ausgabe

Struktur

Alle Logs folgen diesem Schema:

{
  "ts": "2025-03-23T14:22:00Z",       // ISO 8601, UTC
  "level": "info|warn|error|debug",
  "component": "agent|ui|server.api|server.db|server.mqtt",
  "event": "descriptive_name",
  "screen_id": "info01",              // nur auf Player relevant
  "tenant_id": "tenant01",            // nur auf Server relevant
  "user_id": "user123",               // nur auf Server bei Auth-Events
  "duration_ms": 342,                 // bei Performance-Events

  // Fehler-spezifische Felder
  "error": "error_code",
  "error_message": "readable error",

  // Domain-spezifische Felder
  "item_id": "...",
  "media_type": "image|video|pdf|webpage",
  "source": "campaign|tenant_playlist|fallback",

  // Sonstige beliebige Felder
  "details": { ... }
}

Ausgabeziele

Auf dem Player

  1. stdout/stderr mit log/slog JSON-Formatter

    • erfasst von systemd journal
    • abrufbar via journalctl
  2. Lokale Datei /var/log/signage/player.log

    • JSON, eine Zeile pro Event
    • Rotation auf 100 MB, 10 Archive
  3. Schnelle Fehler an Server via HTTP-POST

    • POST /api/v1/screens/{screenSlug}/log-event
    • asynchron, Fehler bei Offline ignoriert
    • nur error und fatal Events

Auf dem Server

  1. stdout/stderr mit strukturiertem Logging

    • erfasst von Docker/systemd
    • abrufbar via docker logs oder journalctl
  2. PostgreSQL (Phase 2+)

    • wichtige Fehler und Status-Events in Tabelle logs
    • Abfrage-UI im Admin-Dashboard
  3. Dateispeicher (Docker Volume)

    • /var/log/signage/server.log
    • Rotation und Verdichtung durch Container-Orchester

Log-Level-Strategie

Debug (development)

  • SQL-Queries
  • HTTP-Request-Details
  • interner State-Uebergaenge

Bei Production: --log-level warn oder --log-level info

Info (standard)

  • Startup/Shutdown
  • erfolgreiche Operationen
  • Status-Wechsel
  • Synchronisierungsereignisse

Warn (aufmerksamkeit)

  • Timeouts
  • Retry-Versuche
  • deprecierte APIs
  • suboptimale Performance

Error (problematisch)

  • gescheiterte HTTP-Requests
  • Datenbankfehler
  • fehlende Ressourcen
  • Auth-Fehler

Fatal (kritisch)

  • nicht-wiederherstellbare Fehler
  • Prozess beendet sich danach

Monitoring-Metriken

Player-seitig

Metriken, die der Agent periodisch dem Server meldet:

{
  "screen_id": "info01",
  "ts": "2025-03-23T14:25:00Z",
  "heartbeat": {
    "uptime_seconds": 86400,
    "last_sync_at": "2025-03-23T14:24:55Z",
    "seconds_since_last_sync": 5,
    "sync_status": "ok|failed|pending",
    "sync_fail_count_24h": 0
  },
  "resources": {
    "cpu_percent": 25,
    "memory_percent": 45,
    "disk_free_mb": 2048,
    "disk_used_percent": 35
  },
  "network": {
    "broker_connected": true,
    "server_reachable": true,
    "ip_addresses": ["192.168.1.10"],
    "signal_strength_dbm": -55
  },
  "playback": {
    "current_item_id": "img-001",
    "source": "campaign",
    "rendering_status": "ok",
    "seconds_on_current_item": 23
  },
  "errors_last_hour": [
    {
      "event": "download_failed",
      "media_id": "video-999",
      "count": 2
    }
  ]
}

Uebertragung: HTTP POST /api/v1/screens/{screenSlug}/heartbeat alle 60 Sekunden

Server-seitig

Der Server sammelt und ueberwacht:

{
  "screen_id": "info01",
  "status": "online|offline|degraded|error",
  "last_heartbeat_at": "2025-03-23T14:25:00Z",
  "seconds_since_last_heartbeat": 0,
  "heartbeat_interval_sec": 60,
  "offline_since_sec": null,

  "screenshot": {
    "latest_at": "2025-03-23T14:25:00Z",
    "seconds_since_latest": 0
  },

  "sync": {
    "latest_at": "2025-03-23T14:24:55Z",
    "latest_duration_ms": 342,
    "fail_count_24h": 1,
    "last_error": null
  },

  "content": {
    "current_item": "img-001",
    "source": "campaign",
    "campaign_id": "xmas-2025"
  },

  "performance": {
    "cpu_avg_percent_1h": 22,
    "memory_avg_percent_1h": 44,
    "disk_free_mb": 2048
  }
}

Diese Metriken werden in PostgreSQL gespeichert und bilden Basis fuer:

  • Status-Dashboard
  • Alerts
  • Trend-Analysen
  • Kapazitaetsplanung

Log-Rotation auf dem Player

Der Raspberry Pi hat begrenzte Speicherkapazitaet. Log-Rotation muss aggressiv sein:

# /etc/logrotate.d/signage

/var/log/signage/player.log
{
  size 50M
  rotate 5
  compress
  delaycompress
  missingok
  notifempty
  create 0644 root root
  postrotate
    systemctl reload signage-agent.service || true
  endscript
}

/var/log/signage/watchdog.log
{
  size 20M
  rotate 3
  compress
  delaycompress
  missingok
  notifempty
  create 0644 root root
}

Resultat:

  • player.log: max 50 MB * 5 = 250 MB
  • watchdog.log: max 20 MB * 3 = 60 MB
  • Komprimierung von alten Logs auf ~10% der urspruenglichen Groesse

Alerting-Strategie

Kriterien fuer Alerts

Bedingung Severity Aktion
Screen offline > 15 min High Email + Dashboard-Alert
Screen offline > 2h Critical Email + SMS
Sync-Fehlerquote > 50% in 1h Medium Email
Disk Full auf Player Critical Email + Stop-Recording
CPU > 90% fuer 5 min Medium Warnung + Analysis
Provisioning fehlgeschlagen High Email an Provisioner

Alert-Kanal (Phase 2)

  1. Dashboard-Benachrichtigungen (im Admin-UI sichtbar)
  2. Email an konfigurierte Admin-Adressen
  3. Webhook fuer externe Monitoring-Systeme (Zabbix, Grafana)
  4. Server-API /api/v1/admin/alerts fuer Polling

Zusammenfassung

Das Logging- und Monitoring-Konzept:

  • ist strukturiert — JSON, nicht Freitexte
  • ist verteilt — lokal auf Player + zentral auf Server
  • ist speicherbewusst — Rotation und Kompression
  • gibt Ueberblick — Heartbeat + Metriken fuer jeden Screen
  • ermoeglicht Diagnose — detaillierte Logs im Fehlerfall
  • skaliert — Verfahren gilt fuer beliebig viele Player