thats-me/documentation/logging.md
Kevin Adametz ab23c9f4ef Client-Logging, getrennte Rate-Limits und Ladezustand für Bilder
Auf dem Testserver lief die App beim Scrollen langer Timelines in ein 429:
nach jeder Scroll-Pause ging sofort ein PUT /settings raus, und jedes
sichtbare Vorschaubild zählte auf dasselbe Limit von 300/min. Danach
blieben Bilder leer und "Einstellungen konnten nicht gespeichert werden"
erschien.

Backend: Medien-Reads bekommen einen eigenen Limiter (media, 1200/min),
POST /client-logs nimmt Warnungen und Fehler der App an (client-logs,
30/min) und schreibt sie in den täglichen Kanal `client`; Exceptions
tragen user_id, path und method im Kontext.

Frontend: Einstellungen gehen frühestens 1,5 s nach der letzten Änderung
und nur bei geändertem Inhalt zum Server, 429/Netzfehler werden still mit
Backoff wiederholt, nur eine Ablehnung wird gemeldet. Bilder laufen durch
ein Gate (6 parallel, Retry-After, 3 Versuche) und fallen auf einen
Platzhalter statt auf einen kaputten Pfad zurück. Neuer clientLog-Service
mit IndexedDB-Puffer, Dedupe und Batch-Versand, globaler Fehlerfang im
Boot, Protokoll im Entwickler-Tab. Drei pulsierende Punkte statt weißer
Fläche in Timeline-Punkt, Key Image und Medienkacheln.

Dokumentation in documentation/logging.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-25 09:31:35 +00:00

4.7 KiB
Raw Blame History

Fehler-Logging

Stand: 25.09.2026. Wo Fehler landen, wie man sie liest und was der Client automatisch meldet.

Überblick

Quelle Ablage Lesen
Browser-App (Warnungen, Fehler) backend/storage/logs/client-YYYY-MM-DD.log tail -f storage/logs/client-*.log oder php artisan pail
Browser-App, lokal Einstellungen → Entwickler → Protokoll im Browser, „Kopieren“ liefert JSON
Laravel (Exceptions, 5xx) backend/storage/logs/laravel.log php artisan pail

Der Kanal client ist in config/logging.php definiert (täglich rotierend, 30 Tage, per LOG_CLIENT_DAYS und LOG_CLIENT_LEVEL einstellbar). Für den Server-Log empfiehlt sich auf dem Testserver LOG_STACK=daily, damit laravel.log nicht unbegrenzt wächst.

Was der Client meldet

Der Logger liegt in frontend/src/services/clientLog.js. Er hält bis zu 200 Einträge im Speicher, spiegelt sie in die IndexedDB-Tabelle clientLogs (überlebt einen Reload) und schickt Warnungen und Fehler gebündelt an POST /api/client-logs:

  • frühestens 5 Sekunden nach dem ersten neuen Eintrag, höchstens 50 Einträge pro Batch,
  • nur online und nur für Konten mit Server-Anbindung (Demo-Nutzer behalten alles lokal),
  • beim Verlassen der Seite mit keepalive, damit der letzte Batch noch rausgeht,
  • gleiche Meldung aus gleicher Quelle innerhalb von 10 Sekunden erhöht einen Zähler statt eine Zeile zu erzeugen ((×12) im Log).

Erfasst wird automatisch:

  • jede fehlgeschlagene API-Antwort außer 401 ([api] PUT /settings → 429, mit Retry-After),
  • Netzwerkfehler ohne Antwort (→ keine Antwort, mit online: false),
  • Bilder, die nach drei Versuchen nicht geladen werden konnten ([image] …, mit Status und Pfad),
  • Einstellungen, die nicht übertragen werden konnten, mit Versuchszähler,
  • Sync-Warteschlange, Sprachnotizen, Dexie-Fehler,
  • unbehandelte Fehler: Vue errorHandler, window.onerror, unhandledrejection, fehlgeschlagene Ressourcen-Loads (src/boot/errorLog.js).

Jeder Eintrag trägt user_id, app_version, session_id, user_agent, ip, die Route in der App (url) und den Zeitpunkt im Browser (occurred_at).

Eigene Meldungen

import { clientLog } from 'src/services/clientLog'

clientLog.warn('scope', 'Was passiert ist', error)          // Error oder flaches Objekt
clientLog.error('scope', 'Was passiert ist', { status: 500 })
clientLog.info('scope', 'nur lokal sichtbar')

scope ist ein kurzes Wort (api, image, settings, sync, events, voice), damit sich im Log nach Quelle filtern lässt. Kontext wird auf eine Ebene mit Skalaren abgeflacht, verschachtelte Objekte werden als JSON-String übernommen.

Rate Limits

Limiter Grenze Gilt für
api 300/min pro Nutzer alle Schreib- und Lesezugriffe der App außer Medien
media 1200/min pro Nutzer GET /event-media/{id}/{variant}, GET /settings/media/background
uploads 30/min pro Nutzer Medien- und Hintergrund-Uploads
client-logs 30/min pro Nutzer POST /client-logs

Warum getrennt: Eine lange Timeline lädt pro sichtbarem Ereignis ein Vorschaubild. Vor dem 25.09.2026 zählten diese Bilder und die Einstellungs-Schreibvorgänge auf dasselbe Limit. Beim Scrollen wurde zusätzlich nach jeder Scroll-Pause sofort ein PUT /settings gesendet. Nach etwa einer Minute Scrollen antwortete die API mit 429, die Bilder blieben leer und die App meldete „Einstellungen konnten nicht gespeichert werden“.

Verhalten des Clients bei 429 und Netzproblemen

  • Einstellungen werden lokal sofort gespeichert, zum Server frühestens 1,5 Sekunden nach der letzten Änderung und nur, wenn sich der Inhalt geändert hat. Ein 429 oder Netzfehler wird still mit wachsendem Abstand wiederholt (3 s, 6 s, … bis 60 s) und beim Wieder-Online-Gehen. Nur eine Ablehnung des Inhalts (z. B. 422) zeigt die Meldung.
  • Bilder laufen durch ein Gate mit höchstens 6 gleichzeitigen Anfragen. Nach einem 429 warten alle Bildanfragen die Retry-After-Zeit ab. Jedes Bild wird bis zu dreimal versucht, dann bleibt die Kachel leer (Platzhalter-Icon), statt ein kaputtes Bild zu zeigen. Während des Ladens pulsieren drei Punkte.
  • Sync-Warteschlange (Events, Medien) hatte diese Logik bereits: 429 und 5xx gelten als vorübergehend und werden wiederholt.

Nachschauen auf dem Server

# Client-Meldungen der letzten Minuten
tail -n 100 storage/logs/client-$(date +%F).log

# Nur Fehler eines Nutzers
grep '"user_id":7' storage/logs/client-*.log | grep ERROR

# Live, beide Kanäle
php artisan pail --timeout=0

Jede Laravel-Exception im Server-Log trägt seit dem 25.09.2026 user_id, path und method im Kontext (bootstrap/app.php), damit sich Server- und Client-Zeilen einander zuordnen lassen.