'use strict'; /** * Minimal server-side logging - plain stdout lines, no logging library. * This app runs as a single Docker container watched via `docker logs`/ * journalctl (see README's Deploying section), not behind a log * aggregator, so a consistent prefix + timestamp is what actually makes * these greppable in practice, not structured JSON nobody's parsing. * * Static methods rather than a singleton instance: there's no state here * at all (unlike PartyService/MusicQueueService, which really do own * something) - a class is just a namespace for these two, kept as a class * for consistency with the rest of server/src/services' shape. */ export class Logger { /** * Party business-lifecycle events - created/joined/left/destroyed, host * handoff, queue advance, playstate transitions, pause/skip requests. * Deliberately covers only that, not connection-level noise like every * Socket.IO reconnect or the 3s playstate heartbeat re-sending the same * state (see SocketGateway, which only calls this on an actual * play/pause transition, not every heartbeat tick). */ static party(message: string): void { console.log(`[watchparty:party] ${new Date().toISOString()} ${message}`); } /** * The streaming route specifically (CatalogService's /master.m3u8 * handler) - a different concern from the party lifecycle above (it's * about one HTTP request, not party state). Deliberately logs only the * individual override values it cares about, never the full resolved * URL or params blob - Emby's own TranscodingUrl can carry an api_key, * and that has no business ending up in a log file. */ static stream(message: string): void { console.log(`[watchparty:stream] ${new Date().toISOString()} ${message}`); } /** `"name(id)"` - the same shape in every party lifecycle log line. */ static who(userId: string, userName: string): string { return `${userName}(${userId})`; } /** * Emby answering anything but success, to any proxied call - see * EmbyClient.fetch, the one place every Emby request goes through. * Added after several rounds of chasing a real error message through * browser devtools and Emby's own live log view (see docs/architecture.md's * player-track-selection section): Emby often sends back a real, * detailed text body on a failure, which neither of those reliably * surfaced - this puts it directly in `docker logs` instead, from now on. */ static emby(message: string): void { console.log(`[watchparty:emby] ${new Date().toISOString()} ${message}`); } /** * TranscodeService's own ffmpeg processes (start/end, and any stderr * captured from a process that exits before producing its first * segment) - see docs/architecture.md's "Self-hosted ffmpeg" section. * Same reasoning as `emby` above: a failure needs to be visible in * `docker logs` immediately, not just a stuck spinner in the browser. */ static transcode(message: string): void { console.log(`[watchparty:transcode] ${new Date().toISOString()} ${message}`); } /** Every per-member background write this app makes to Emby under * someone's own account - the party watch-state sync (index.ts's * periodic sweep, and PartyService.advanceQueue/MusicQueueService * .playNext's own item-transition calls - see PlaystateService), and * the parallel Sessions-API lifecycle reported alongside it (see * EmbySessionService). Logs every outgoing request (method + path, * and the POST body for the UserData write) and its outcome, not * just failures: this is a best-effort background sync with nothing * else watching it, so `docker logs | grep watchparty:playstate` * needs to be enough on its own to see exactly what was sent and * whether Emby accepted it, without cross-referencing `emby` above. * Safe to log in full here (unlike `stream` above) - the real * embyToken travels as a header, never logged, and both services' * own tests confirm it never leaks into these lines. */ static playstate(message: string): void { console.log(`[watchparty:playstate] ${new Date().toISOString()} ${message}`); } }