amber-backend/docs/telemetry-contract.md
Claude 418f566603 Add client_logs collection for background device diagnostics (feedback 2 §4)
The family runs Amber on hardware I can't debug directly, so the app batches
a low-volume diagnostic stream here: uncaught errors, player error codes,
per-session playback summaries, and the audio-delay a user dials in to fix
desync (the "a track falls behind" signal).

- Migration 1788000000_client_logs.js: owner-create-only, superuser-read-only
  (list/view/update/delete = null); fields kind/event/message/meta/appVersion/
  platform/device/ts + created.
- client_logs.pb.js: nightly cron trims rows older than 14 days.
- docs/telemetry-contract.md: event catalogue, how I query it, and the
  privacy/redaction guarantees (host-only, never a token or full URL).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 11:49:04 +02:00

3 KiB

Client diagnostics (client_logs)

Background diagnostics from prod devices (feedback 2 §4). The family runs Amber on hardware that can't be debugged directly, so the app batches a low-volume stream of events to the client_logs collection. Designed to be invisible on the device (in-memory ring buffer, slow best-effort flush, never on a playback thread) and safe (owner-create-only, superuser-read-only, redacted at source).

Collection client_logs

Migration 1788000000_client_logs.js. Fields: user (relation), kind, event, message, meta (json), appVersion, platform, device, ts (device clock), created (server clock).

Rules: create = owner (user = @request.auth.id); list/view/update/delete = null (superusers only). Retention: client_logs.pb.js cron trims rows older than 14 days nightly.

Event kinds

kind event meta
error uncaught {library} — Flutter framework errors
player exo_error {host, anime} — native player error
player av_delay_applied {audioMs, host} — user dialed in an audio offset (the "a track falls behind" signal)
session session_summary {host, durationS, watchedS, stalls, audioDelayMs, anime} on player close

host is only the stream's scheme://hostnever a full signed URL or an addon token (redacted client-side in TelemetryService.redactUrl before write).

How Claude queries it

Superuser token (same as releases publishing), then filter/sort the collection:

TOKEN=$(...auth as superuser...)
# recent player errors + desync signals across the fleet, newest first
curl -s "$PB/api/collections/client_logs/records?perPage=100&sort=-created&filter=$(python3 -c '
import urllib.parse;print(urllib.parse.quote("kind='"'"'player'"'"'"))')" \
  -H "Authorization: $TOKEN" | python3 -m json.tool

Useful filters: kind='player' (errors + desync), event='av_delay_applied' (who's fighting sync and by how much — the reported "track falls behind" bug), kind='session' && meta.stalls > 3 (hitchy playback). Group by device / platform / appVersion to see which hardware struggles.

What it deliberately does NOT capture (yet)

True per-track A/V PTS drift needs native instrumentation on both players (ExoPlayer exposes one clock; mpv would need audio-pts/video-pts sampling). v1 uses proxies: buffering-stall counts and the manual audio-delay the user applies to fix desync — which directly answers "is a track falling behind, on which sources/devices, and by how much". Deeper PTS sampling is a follow-up if the proxies point somewhere specific.

Privacy / kill switch

On by default; a per-device Settings toggle ("Diagnostika") disables it and drops the pending buffer. Only sends while signed in. No addon credential or full stream URL ever leaves the device.