### 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>
470 lines
10 KiB
Markdown
470 lines
10 KiB
Markdown
# 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**
|
|
```json
|
|
{
|
|
"ts": "2025-03-23T14:22:00Z",
|
|
"level": "info",
|
|
"component": "agent",
|
|
"event": "startup",
|
|
"config_file": "/etc/signage/config.yml",
|
|
"screen_id": "info01"
|
|
}
|
|
```
|
|
|
|
- **Server-Sync**
|
|
```json
|
|
{
|
|
"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**
|
|
```json
|
|
{
|
|
"ts": "2025-03-23T14:22:10Z",
|
|
"level": "info",
|
|
"component": "agent.mqtt",
|
|
"event": "playlist_changed",
|
|
"source": "mqtt",
|
|
"cause": "playlist-changed-event"
|
|
}
|
|
```
|
|
|
|
- **Fehler**
|
|
```json
|
|
{
|
|
"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**
|
|
```json
|
|
{
|
|
"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**
|
|
```json
|
|
{
|
|
"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**
|
|
```json
|
|
{
|
|
"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)
|
|
```json
|
|
{
|
|
"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)
|
|
```json
|
|
{
|
|
"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**
|
|
```json
|
|
{
|
|
"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**
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```yaml
|
|
# /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
|