Protocol 15: one shared session log on the server, chat never logged, Mark a problem
Every app streams its redacted diagnostic lines (server time, batched every 10 s, gzip, buffered while offline) to POST /l/; the server merges them with its own into session-YYYY-MM-DD.log in time order, tagged by who. Opt-out in Settings > About; Mark a problem (Ctrl+Shift+B, the player menus, About) writes a marker with an optional note. Copy diagnostics can include the server's session log. Chat text never reaches any log. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4ab37c55cf
commit
7487b4deb1
29 files changed
+1380
-47
No files matched your search
+47
-3
@@ -1517,9 +1517,14 @@ dedicated server (`package:logging` loggers named by area: `relay`,
|
||||
`--config`, else `logs` next to the binary (systemd:
|
||||
`$STATE_DIRECTORY/logs`), else `~/.cache/watch-together/logs`; it
|
||||
prints the folder at start. Both also print every line to stdout.
|
||||
The session log (below) sits next to them: the hosting app's server
|
||||
writes it in the app's logs folder (not for a solo session), the
|
||||
dedicated one in its log folder.
|
||||
- **Files** (`LogFiles`): one per day, the next `.1.log`, `.2.log` once a
|
||||
file passes 10 MiB; files older than 7 days, then the oldest beyond
|
||||
100 MiB in all, are deleted whenever one is opened. A line is
|
||||
100 MiB in all (the app's or server's files and the session log's
|
||||
together, the oldest of either first, never the other writer's file of
|
||||
today), are deleted whenever one is opened. A line is
|
||||
`2026-10-12 18:44:27.202 LEVEL logger: message`, the error and stack
|
||||
trace on the lines below. Writes are synchronous appends (nothing lost
|
||||
in a crash). Lines from before the folder is known (the app finds it
|
||||
@@ -1527,7 +1532,7 @@ dedicated server (`package:logging` loggers named by area: `relay`,
|
||||
- **Redaction** (`redactLog`): every line, in one place
|
||||
(`formatLogLine`), before console or file: URL query strings cut to
|
||||
their parameter names (signed CDN links, Jellyfin `api_key`),
|
||||
`user:pass@` in URLs, member tokens in `/r /m /j /a /i` paths, and the
|
||||
`user:pass@` in URLs, member tokens in `/r /m /j /a /i /l` paths, and the
|
||||
values of passwords, secrets, tokens, API keys, cookies,
|
||||
`Authorization` / `X-Emby-*` headers (`name=value`, `"name":"value"`,
|
||||
`Name="value"`, `Name: value`) and `Bearer` credentials. Code logs
|
||||
@@ -1554,12 +1559,51 @@ dedicated server (`package:logging` loggers named by area: `relay`,
|
||||
source (media opened and how long it took, channel up and down,
|
||||
reconnects, upstream refusals with the header names sent, re-resolves
|
||||
of expired format URLs).
|
||||
- **Never logged**: chat text (the server relays it without a log line,
|
||||
the app does not log it), and no user-typed free text other than a
|
||||
problem mark's note. Names, media titles and links (query cut) are.
|
||||
- **The session log** (`packages/server/lib/src/session_log.dart`,
|
||||
`packages/core/lib/src/diag_share.dart`): one shared log per server so
|
||||
a problem can be read afterwards from every side. While in a session
|
||||
the app (`DiagUploader`, owned by the coordinator) keeps its own
|
||||
records (not its hosted server's `server.*`, which the server logs
|
||||
itself), redacted, and sends them every 10 s from the session's 1 s
|
||||
ticker and when it leaves (before its token goes, at most 1.5 s), as
|
||||
gzip batches to `POST /l/<sid>/<token>` (PROTOCOL
|
||||
"Diagnostics"), timestamps moved to the server's clock with the ping
|
||||
offset (`ClockSync`), the app version in every batch. Until it can
|
||||
send (reconnecting, no clock sample yet) it keeps up to 2 MiB, the
|
||||
oldest dropped and counted. The server (`SessionLog`) takes every
|
||||
member's lines and its own records, holds them 15 s and writes them in
|
||||
server-time order to `session-YYYY-MM-DD.log` (day and time on the
|
||||
server's clock; a line later than that, a reconnect's backlog, goes in
|
||||
with its own time, a little out of order). A line is
|
||||
`HH:MM:SS.mmm [who] logger: message` (`LEVEL logger` from WARNING up),
|
||||
`who` = `name (id)` or `server`, continuation lines indented; every
|
||||
line is redacted again on the server. The server notes once per member
|
||||
`Bob (m2) shares diagnostics (<version>)` or `Bob (m2) doesn't share
|
||||
diagnostics`, and `N lines dropped` when the app's buffer overflowed.
|
||||
Limits and rate caps: PROTOCOL "Diagnostics"; a refused batch stays in
|
||||
the app's buffer for the next try.
|
||||
- **Opting out**: Settings → About → "Share diagnostics with the server"
|
||||
(default on). Off: no lines are kept or sent; one batch per connection
|
||||
says so (`share:false`). The local log is unchanged.
|
||||
- **Mark a problem**: Ctrl+Shift+B (`AppKeys`), the player's ⚙ card and
|
||||
Settings → About open a small card with an optional note (200
|
||||
characters, one line); the mark goes at once with the lines waiting,
|
||||
opted out or not, and the server writes `[who] === PROBLEM marked by
|
||||
<name>: <note> ===` at its time. The app's own log gets
|
||||
`=== PROBLEM marked: <note> ===` (logger `problem`, never sent). Out
|
||||
of a session only the app's log gets it. The toast says which.
|
||||
- **Getting it**: Settings → About → "Copy diagnostics" puts the app and
|
||||
OS versions, mpv/ffmpeg and yt-dlp versions, whether yt-dlp has
|
||||
cookies, and the last 6 hours of the log (up to 4 MiB) on the
|
||||
clipboard, already redacted, and saves the same text as
|
||||
`diagnostics-<time>.txt` in the logs folder (kept 7 days); "Open logs
|
||||
folder" opens it.
|
||||
folder" opens it. In a session it first asks whether to add the
|
||||
server's session log of the same 6 hours (`GET /l/...`). Any member
|
||||
may read the session log, not only the host: friends trust each other,
|
||||
and it is redacted and holds no chat.
|
||||
|
||||
## Health reporting
|
||||
|
||||
|
||||
+14
-1
@@ -145,7 +145,20 @@ on Linux (on Windows under `%APPDATA%`; Settings → About → "Open logs
|
||||
folder" finds it anywhere), with the hosted server's lines
|
||||
in the same file; a dedicated server prints its folder at start
|
||||
(`--log-dir`). Users send theirs with Settings → About → "Copy
|
||||
diagnostics". For a "can't play" report, grep both sides around the
|
||||
diagnostics".
|
||||
|
||||
Start with the session log when there is one: `session-YYYY-MM-DD.log`
|
||||
in the server's folder (the hosting app's logs folder, or the dedicated
|
||||
server's `--log-dir`), every member's lines and the server's merged in
|
||||
server-clock order, each tagged `[name (id)]` or `[server]`. Any member
|
||||
gets it with "Copy diagnostics" in a session. Find
|
||||
`=== PROBLEM marked by` first (a member pressed Ctrl+Shift+B), then read
|
||||
the lines around it from every side; `grep '\[Bob (m2)\]'` follows one
|
||||
member. `[server] diag:` lines say who shares (with their app version)
|
||||
and who doesn't, so a missing side is explained. Times are the server's
|
||||
clock; a member's local log (`app-*.log`) is on its own clock.
|
||||
|
||||
For a "can't play" report, grep both sides around the
|
||||
time: `swap by` (what went up and how), `relay channel` (when the
|
||||
source dialled in), `media:` (each viewer's requests: status and time to
|
||||
headers), `mpv:` (`cannot open the media` and ffmpeg's lines), `yt-dlp:`
|
||||
|
||||
+43
-3
@@ -1,4 +1,4 @@
|
||||
# Protocol (proto = 14)
|
||||
# Protocol (proto = 15)
|
||||
|
||||
Wire format only. Behavior (gate, clock, timeouts) is in
|
||||
[ARCHITECTURE.md](ARCHITECTURE.md). Source of truth:
|
||||
@@ -26,12 +26,14 @@ with different `proto` refuse each other at `hello` (`proto mismatch`).
|
||||
"Jellyfin media" below). `/r/<sid>/<token>/...` is the relay of a
|
||||
member's media (see "Relay" below). `GET /a/<sid>/<token>/<hash>` serves
|
||||
a member's picture (see "Avatars" below), `GET /i/<sid>/<token>/<hash>`
|
||||
the current media's art (see "Media art" below).
|
||||
the current media's art (see "Media art" below). `POST` and `GET
|
||||
/l/<sid>/<token>` send and read the session log (see "Diagnostics"
|
||||
below).
|
||||
|
||||
## Client to server
|
||||
|
||||
```
|
||||
{"t":"hello","proto":14,"secret":"","user":{"name":"","avatar":""},
|
||||
{"t":"hello","proto":15,"secret":"","user":{"name":"","avatar":""},
|
||||
"media":{"name":"","size":0,"duration":0,"url":null},"hostKey":"...",
|
||||
"resume":"<token>"}
|
||||
{"t":"ready","ready":true,"source":"manual"} source: manual | auto
|
||||
@@ -467,3 +469,41 @@ state "queue":[<item>,...], "queuePlay":"q3"
|
||||
- `state.queue` (omitted when empty) and `state.queuePlay` (omitted when
|
||||
none) ride in every state, `welcome.state` included. The first item's
|
||||
`media.relay` shows its warm relay (see "Relay").
|
||||
|
||||
## Diagnostics (proto 15)
|
||||
|
||||
Each app sends its log lines to the server, which merges them with its
|
||||
own into the session log (behavior, file and format: ARCHITECTURE.md,
|
||||
"Logging").
|
||||
|
||||
```
|
||||
POST /l/<sid>/<token> content-type application/gzip, body = gzip of:
|
||||
{"v":"Starsync 1.4.0 on linux","share":false,
|
||||
"lines":[{"t":1767225600123,"l":"WARNING","n":"mpv","m":"..."}],
|
||||
"dropped":12,"marks":[{"t":1767225600500,"note":"..."}]}
|
||||
GET /l/<sid>/<token>?hours=6
|
||||
```
|
||||
|
||||
- `POST`: one batch. Every field is optional. `v`: the app and version,
|
||||
noted once per member (and when it changes). `share:false` (omitted
|
||||
when true): the member does not share; the server notes it once and
|
||||
drops any `lines`. `lines[]`: `t` epoch ms on the server's clock (the
|
||||
client converts with its `pong` offset), `l` level name, `n` logger
|
||||
(`[A-Za-z0-9_.-]`, up to 40, else `app`), `m` the message, already
|
||||
redacted, an error and stack trace on the lines below (cut to 8192
|
||||
characters). `dropped`: lines the client dropped since its last batch
|
||||
(its buffer was full). `marks[]`: "Mark a problem" at `t` with an
|
||||
optional `note` (one line, cut to 200 characters); it goes in even
|
||||
with `share:false`.
|
||||
- Limits: a body of at most 256 KiB, unpacking to at most 1 MiB (else
|
||||
413); not gzip of a JSON object: 400. Per member and minute at most 30
|
||||
posts and 4 MiB of lines, else 429 with `retry-after`: the client keeps
|
||||
the batch and sends it later. Unknown token: 401. Success: 204, also
|
||||
on a server that keeps no session log (the batch is dropped).
|
||||
- `GET`: the session log of the last `hours` (1 to 168, default 6; at
|
||||
most 8 MiB, the newest kept), lines the server still holds for
|
||||
ordering included, as gzip of UTF-8 text (`application/gzip`). Any
|
||||
member may read it (it is redacted and holds no chat). 401 for an
|
||||
unknown token, 404 on a server that keeps no session log.
|
||||
- Clients send every 10 s and when they leave; each batch carries at
|
||||
most 256 KiB of lines.
|
||||
Reference in new issue
Block a user