Sessions leave redacted logs; viewers wait for a member relay's source before opening it
Logs: one redaction pass (query strings to names, member tokens,
passwords, cookies, auth headers) for every line; daily files with size
and age caps in the app (hosted server included) and the dedicated
server (--log-dir); swaps, relay channels, per-member /r /m /j outcomes,
server fetches, mpv load errors, yt-dlp runs. Settings > About: Copy
diagnostics, Open logs folder.
Bilibili through someone else: the viewer opened /r/ before the source
dialled in; the server holds that request and mpv gives up after 5 s
("could not be opened"). Viewers now open a member relay once it is
live, the putter starts its source before its own player, and track
sizes are read in parallel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
572104f49e
commit
4ab37c55cf
25 files changed
+1124
-108
No files matched your search
+73
-4
@@ -33,8 +33,8 @@ size (`ServerIsolate.setFile`): the host's in-session File swap then sends a
|
||||
|
||||
- **Hosting from the app**: `SessionState.host()` spawns an isolate running
|
||||
`serve` (`ServerIsolate`), waits for `ready`, then joins it over
|
||||
`127.0.0.1` like any other client. Server logs go to the app's log with a
|
||||
`[server]` prefix. The isolate runs with `errorsAreFatal: false`: an
|
||||
`127.0.0.1` like any other client. Its log records go to the app's log
|
||||
as `server.<logger>` (see "Logging"). The isolate runs with `errorsAreFatal: false`: an
|
||||
uncaught async error is logged and the server keeps going. If the isolate
|
||||
dies anyway, the session shows "server crashed: ..." and leaves; the
|
||||
shell says so in a dialog (during setup the host attempt fails with it).
|
||||
@@ -61,6 +61,7 @@ size (`ServerIsolate.setFile`): the host's in-session File swap then sends a
|
||||
and shipped next to the app as `watch-together-server`. It parses flags
|
||||
(`--port`, `--secret`, `--name`, `--media`, `--advertise-host`,
|
||||
`--jellyfin <file>`, `--[no-]yt-dlp`, `--yt-dlp-dir`, `--yt-dlp-cookies`, `--config`; default port 8080) into the same map and calls `serve`
|
||||
(`--log-dir` only picks where its log files go, see "Logging")
|
||||
(`--upgrade` / `--check-update` run the [updater](#updater) instead);
|
||||
SIGINT and SIGTERM stop it. A Flutter binary cannot run without a
|
||||
display, which is why the server is its own build.
|
||||
@@ -621,6 +622,16 @@ How bytes move:
|
||||
with none. A manual swap to something else keeps it (the item is
|
||||
still first). A failed warm fetch is not promoted: the swap fetches
|
||||
again and reports the reason.
|
||||
- Before the source's channel is first up, a viewer request gets no
|
||||
answer (the server cannot send headers before it knows the size), and
|
||||
media_kit's mpv gives up on a silent request after 5 s
|
||||
(`network-timeout`) with "could not be opened". So viewers open a
|
||||
member relay's `/r/` URL only once `relay.live` is set, as for a
|
||||
server fetch (`_waitForSource`; "waiting for <name> to pass the video
|
||||
on…" meanwhile), unless the source already left (then they open what
|
||||
the server has); Up next preloads wait the same way. The putter starts
|
||||
its source before opening its own player, and the source reads its
|
||||
tracks' sizes at once.
|
||||
- Source away: viewers keep playing from the buffer; a request waits up
|
||||
to 60 s for missing bytes. The source reconnects with backoff (same
|
||||
token after a resume); a different size (or piece count and roles)
|
||||
@@ -784,8 +795,7 @@ all three link modes. Nothing is bundled or installed by hand.
|
||||
cannot); when the server cannot read it, the putter gets an `err` with
|
||||
the reason. Viewers open a fetched
|
||||
link's `/r/` URL only once `relay.live` is set ("the server is getting
|
||||
the video…" until then): resolving takes seconds and media_kit's mpv
|
||||
gives up on a silent request after 5 s (`network-timeout`).
|
||||
the video…" until then; see "Relay" for why).
|
||||
|
||||
## Jellyfin in a session
|
||||
|
||||
@@ -1492,6 +1502,65 @@ Window chrome is frameless (`window_manager`): the party strip is the drag
|
||||
area and holds the min/max/close buttons (`widgets/window_buttons.dart`);
|
||||
fullscreen has no drag area and shows the buttons in the player's top bar.
|
||||
|
||||
## Logging
|
||||
|
||||
`packages/core/lib/src/logging.dart`, one setup for the app and the
|
||||
dedicated server (`package:logging` loggers named by area: `relay`,
|
||||
`yt-dlp`, `link`, `server_hub`, `media`, `queue`, `session`, `mpv`,
|
||||
`app`).
|
||||
|
||||
- **Where**: the app writes `<app support>/logs/app-YYYY-MM-DD.log`
|
||||
(Linux `~/.local/share/moe.senkochan.watch_together/logs`); its hosted
|
||||
server's records arrive over the isolate's `events` port and go in the
|
||||
same file as `server.<logger>` (`relogLine`). The dedicated server
|
||||
writes `server-YYYY-MM-DD.log` in `--log-dir`, else `logs` next to
|
||||
`--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.
|
||||
- **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
|
||||
`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
|
||||
once Flutter is up) are kept and written first.
|
||||
- **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
|
||||
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
|
||||
members as `name (id)`, never their token, headers by name only, and
|
||||
yt-dlp's cookie source as `--cookies <file>`.
|
||||
- **Floods** (`LogThrottle`): a few lines per key per 30 s, the next one
|
||||
says how many were dropped: mpv's warnings and errors, upstream
|
||||
refusals per host and status, each member's media requests per route
|
||||
and outcome.
|
||||
- **What is logged**: the server: joins and leaves, every swap (who, how
|
||||
it plays, a link with its query cut), relay channels up and down (who,
|
||||
tracks and sizes, how long, the close frame), relay live changes, warm
|
||||
relays created, promoted and dropped, server fetches (start, tracks,
|
||||
time, failure reason), each member's `/r /m /j` requests (status,
|
||||
range, bytes, time to headers: a 503 after 60 s is a source that never
|
||||
came up), broken viewer reads, a member's `report.error` as it changes,
|
||||
queue ops, and relay stats every 30 s (source rate, need, buffered,
|
||||
each reader's rate). The app: `debugPrint` (routed into the log at
|
||||
start), uncaught errors, each media opened (relay, the host's file,
|
||||
Jellyfin, a link fetched here), mpv's warnings and errors (`mpv`; why a
|
||||
media could not be opened, and whether ffmpeg logged an HTTP status),
|
||||
probes, yt-dlp runs (arguments, time, exit code, the tail of stderr,
|
||||
the picked formats with protocol, host and header names), the 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).
|
||||
- **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.
|
||||
|
||||
## Health reporting
|
||||
|
||||
Every failure must show in the UI with a cause and a next step; nothing
|
||||
|
||||
+19
-2
@@ -127,8 +127,8 @@ updater skips the check when absent. Workflows live in `.gitea/workflows`
|
||||
When hosting, the server runs on its own isolate (`ServerIsolate`,
|
||||
`apps/desktop/lib/src/state/server_isolate.dart`), in the same process.
|
||||
Breakpoints in `packages/server` work under `flutter run`: the debugger
|
||||
attaches to spawned isolates too (it shows as `server`). Its log lines are
|
||||
printed with a `[server]` prefix. Hot reload reloads the server isolate's
|
||||
attaches to spawned isolates too (it shows as `server`). Its log lines
|
||||
carry `server.<logger>` (e.g. `server.relay`). Hot reload reloads the server isolate's
|
||||
code too, but its state (members, clock, sockets) survives until hosting
|
||||
restarts; changes to startup code (`SyncServer.start`, routes) need a new
|
||||
Host. Hot restart kills the server isolate (flutter_tools kills every
|
||||
@@ -136,6 +136,23 @@ non-UI isolate first), so a `[server] server crashed: the server stopped
|
||||
unexpectedly` line just before it is expected. Without the app:
|
||||
`dart run packages/server/bin/server.dart`.
|
||||
|
||||
## Logs
|
||||
|
||||
After a session, read the log files rather than reproducing
|
||||
("Logging" in [ARCHITECTURE.md](ARCHITECTURE.md) has what goes in them):
|
||||
the app's are `~/.local/share/moe.senkochan.watch_together/logs/app-*.log`
|
||||
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
|
||||
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:`
|
||||
(the resolve and its picked formats) and `upstream` (CDN refusals with
|
||||
the headers sent). A viewer that got no bytes while the source was
|
||||
slow to come up shows mpv's error with "no HTTP error logged".
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Relic gives Range as a typed `RangeHeader`; convert from unit + first range.
|
||||
|
||||
Reference in new issue
Block a user