refactor(logging): route frontend console calls through the logger

TRACES: | DR-204

484 ungated `console.*` calls across 63 non-test frontend files shipped to
end users with no way to turn them off. Mechanical substitution, no control
flow, error handling or message semantics changed:

  console.log / console.debug -> log.debug
  console.info                -> log.info
  console.warn                -> log.warn
  console.error               -> log.error

Hand-written `"[Scope] …"` prefixes are dropped where the logger's scope
now carries them; scope names that already existed are preserved verbatim
(`[Auth]`, `[VideoPlayer]`, `[PiP]`, …) and inferred from the filename
where a file had none. `src/routes/player/[id]/+page.svelte` keeps its
`NextEpisode` and `AutoPlay` sub-scopes as separate loggers rather than
flattening them into the page scope.

`grep -rn 'console\.' src/` now matches nothing outside the tests and the
facade itself.
This commit is contained in:
2026-08-20 19:29:59 +02:00
parent 4c82a0a025
commit d54d8cc7c4
63 changed files with 686 additions and 490 deletions
+51 -46
View File
@@ -24,6 +24,11 @@
import { reportSkippedEpisode, shouldSuppressStopReport } from "$lib/services/skipReporting";
import { cleanup as cleanupNextEpisode } from "$lib/services/nextEpisodeService";
import * as html5Adapter from "$lib/player/html5Adapter";
import { createLogger } from "$lib/utils/logger";
const log = createLogger("PlayerPage");
const nextEpisodeLog = createLogger("NextEpisode");
const autoPlayLog = createLogger("AutoPlay");
const itemId = $derived($page.params.id);
const queueParam = $derived($page.url.searchParams.get("queue"));
@@ -95,7 +100,7 @@
const id = itemId;
const restart = restartParam;
if (id && id !== loadedItemId) {
console.log("[AutoPlay] $effect triggered: loading new item", id, "(was:", loadedItemId, ") restart:", restart);
autoPlayLog.debug("$effect triggered: loading new item", id, "(was:", loadedItemId, ") restart:", restart);
// restart=true (advancing to next episode) forces start-from-beginning,
// bypassing the resume-progress check.
loadAndPlay(id, restart ? 0 : undefined, restart);
@@ -128,16 +133,16 @@
let retrievedProgressSeconds: number | null = null;
try {
console.log("loadAndPlay: Loading item", id);
log.debug("loadAndPlay: Loading item", id);
// Load item details
const item = await library.loadItem(id);
console.log("loadAndPlay: Loaded item", item.name, "kind:", item.kind);
log.debug("loadAndPlay: Loaded item", item.name, "kind:", item.kind);
currentMedia = item;
// Check if this is a non-playable collection type that should be viewed in library instead
const collectionKinds: MediaKind[] = ["album", "artist", "series", "season", "folder", "playlist", "channel"];
if (item.kind && collectionKinds.includes(item.kind)) {
console.log("loadAndPlay: Redirecting collection type to library:", item.kind);
log.debug("loadAndPlay: Redirecting collection type to library:", item.kind);
goto(`/library/${id}`);
return;
}
@@ -162,7 +167,7 @@
forceRestart,
})
) {
console.log("loadAndPlay: Track already playing, showing UI without restarting");
log.debug("loadAndPlay: Track already playing, showing UI without restarting");
isPlaying = true;
loading = false;
// hasNext/hasPrevious come from the event-driven queue store.
@@ -175,7 +180,7 @@
try {
await commands.playerStop();
queue.clear();
console.log("loadAndPlay: Stopped audio backend for video playback");
log.debug("loadAndPlay: Stopped audio backend for video playback");
} catch (e) {
// Ignore - player may not have been playing
}
@@ -185,43 +190,43 @@
// When forceRestart is set (advancing to a next episode) we always start
// from the beginning, skipping the resume check and resume dialog.
const userId = auth.getUserId();
console.log("Resume check - userId:", userId, "itemId:", id, "startPosition:", startPosition, "forceRestart:", forceRestart);
log.debug("Resume check - userId:", userId, "itemId:", id, "startPosition:", startPosition, "forceRestart:", forceRestart);
// Live streams have no fixed position - never resume.
if (!startPosition && !forceRestart && userId && !isLive) {
try {
const progress = await commands.storageGetPlaybackProgress(userId, id);
console.log("Resume check - retrieved progress:", progress);
log.debug("Resume check - retrieved progress:", progress);
if (progress && progress.positionMs > 0 && item.durationMs) {
const positionSeconds = progress.positionMs / 1000;
const totalSeconds = item.durationMs / 1000;
const progressPercent = (positionSeconds / totalSeconds) * 100;
console.log("Resume check - positionSeconds:", positionSeconds, "totalSeconds:", totalSeconds, "progressPercent:", progressPercent);
log.debug("Resume check - positionSeconds:", positionSeconds, "totalSeconds:", totalSeconds, "progressPercent:", progressPercent);
// Store for later use regardless of whether dialog is shown
retrievedProgressSeconds = positionSeconds;
// Show resume dialog if watched > 30 seconds and < 90% complete
if (positionSeconds > 30 && progressPercent < 90) {
console.log("Resume check - SHOWING RESUME DIALOG");
log.debug("Resume check - SHOWING RESUME DIALOG");
savedProgress = { positionSeconds, progressPercent };
showResumeDialog = true;
loading = false;
return; // Wait for user decision
} else {
console.log("Resume check - NOT showing dialog. Position > 30?", positionSeconds > 30, "Progress < 90?", progressPercent < 90);
log.debug("Resume check - NOT showing dialog. Position > 30?", positionSeconds > 30, "Progress < 90?", progressPercent < 90);
}
} else {
console.log("Resume check - No valid progress found. Has progress?", !!progress, "Has position?", progress?.positionMs, "Has runtime?", !!item.durationMs);
log.debug("Resume check - No valid progress found. Has progress?", !!progress, "Has position?", progress?.positionMs, "Has runtime?", !!item.durationMs);
}
} catch (e) {
console.error("Failed to check saved progress:", e);
log.error("Failed to check saved progress:", e);
// Continue with normal playback
}
} else {
console.log("Resume check - Skipped. Reason:", !userId ? "No userId" : "Has startPosition");
log.debug("Resume check - Skipped. Reason:", !userId ? "No userId" : "Has startPosition");
}
// Check if this item is downloaded locally
@@ -232,7 +237,7 @@
if (localDownload) {
// Use local file for playback
console.log("loadAndPlay: Found local download, using offline playback:", localDownload.filePath);
log.debug("loadAndPlay: Found local download, using offline playback:", localDownload.filePath);
isOfflinePlayback = true;
// Get the storage path and resolve the file's location. A completed
@@ -241,7 +246,7 @@
// TRACES: UR-071 | DR-133
const storagePath = await commands.storageGetPath();
const fullPath = downloadedFilePath(storagePath, localDownload.filePath);
console.log("loadAndPlay: Full local path:", fullPath);
log.debug("loadAndPlay: Full local path:", fullPath);
// Serve the file over the loopback media server rather than the asset
// protocol: the asset protocol answers a range-less request with the
@@ -249,7 +254,7 @@
// the URL (it holds the port and the per-session token).
// TRACES: UR-071 | DR-137
const localUrl = await commands.mediaLocalUrl(fullPath);
console.log("loadAndPlay: Local media URL resolved");
log.debug("loadAndPlay: Local media URL resolved");
if (isVideo) {
// Local video files don't need transcoding and support native seeking
@@ -260,7 +265,7 @@
videoInitialPosition = effectivePosition;
} else {
// Local audio playback via MPV backend
console.log("loadAndPlay: Using MPV backend for offline audio");
log.debug("loadAndPlay: Using MPV backend for offline audio");
// Use player_play_tracks - backend fetches all metadata from single ID
const repo = auth.getRepository();
const repositoryHandle = repo.getHandle();
@@ -286,9 +291,9 @@
if (isLive) {
// Live TV channels must be "opened" before streaming; the server returns
// a ready-to-play HLS transcoding URL. No resume, no seek, no progress.
console.log("loadAndPlay: Opening live stream for channel:", id);
log.debug("loadAndPlay: Opening live stream for channel:", id);
const liveInfo = await repo.openLiveStream(id);
console.log("loadAndPlay: Live stream URL:", liveInfo.streamUrl);
log.debug("loadAndPlay: Live stream URL:", liveInfo.streamUrl);
mediaSourceId = liveInfo.mediaSourceId;
streamUrl = liveInfo.streamUrl;
videoNeedsTranscoding = true;
@@ -298,13 +303,13 @@
return;
}
console.log("loadAndPlay: Getting playback info");
log.debug("loadAndPlay: Getting playback info");
const playbackInfo = await repo.getPlaybackInfo(id);
console.log("loadAndPlay: Got playback info, mediaSourceId:", playbackInfo.mediaSourceId);
log.debug("loadAndPlay: Got playback info, mediaSourceId:", playbackInfo.mediaSourceId);
if (isVideo) {
// Playback API now detects HEVC/10-bit and returns transcoded URL when needed
console.log("loadAndPlay: Using video stream, directPlay:", playbackInfo.directPlay, "needsTranscoding:", playbackInfo.needsTranscoding);
log.debug("loadAndPlay: Using video stream, directPlay:", playbackInfo.directPlay, "needsTranscoding:", playbackInfo.needsTranscoding);
mediaSourceId = playbackInfo.mediaSourceId;
// Prefer a completed download over streaming. Audio has done this
@@ -328,7 +333,7 @@
streamUrl = source.url;
videoNeedsTranscoding = source.needsTranscoding;
console.log(
log.debug(
source.isLocal
? "loadAndPlay: Playing downloaded file from disk"
: `loadAndPlay: Using stream URL: ${streamUrl}`
@@ -347,17 +352,17 @@
const effectivePosition = startPosition ?? retrievedProgressSeconds ?? 0;
videoInitialPosition = effectivePosition > 0 ? effectivePosition : 0;
if (videoInitialPosition > 0) {
console.log("loadAndPlay: Will seek to position after load:", videoInitialPosition);
log.debug("loadAndPlay: Will seek to position after load:", videoInitialPosition);
}
} else {
// For audio, use MPV backend
console.log("loadAndPlay: Using MPV backend for audio");
log.debug("loadAndPlay: Using MPV backend for audio");
// Check if we have a queue parameter (e.g., queue=parent:albumId)
const queueParamValue = queueParam;
if (queueParamValue?.startsWith("parent:")) {
const parentId = queueParamValue.substring(7); // Remove "parent:" prefix
console.log("loadAndPlay: Loading queue from parent:", parentId);
log.debug("loadAndPlay: Loading queue from parent:", parentId);
// Fetch all tracks from the parent (album/playlist)
const result = await repo.getItems(parentId, {
@@ -372,16 +377,16 @@
const startIndex = audioTracks.findIndex(t => t.id === id);
const actualStartIndex = startIndex >= 0 ? startIndex : 0;
console.log("loadAndPlay: Building queue with", audioTracks.length, "tracks, startIndex:", actualStartIndex);
log.debug("loadAndPlay: Building queue with", audioTracks.length, "tracks, startIndex:", actualStartIndex);
// Build queue items with stream URLs
// Add error handling and logging for each track
const queueItems = await Promise.all(audioTracks.map(async (t, idx) => {
try {
console.log(`loadAndPlay: Fetching stream URL for track ${idx + 1}/${audioTracks.length}: ${t.name}`);
log.debug(`loadAndPlay: Fetching stream URL for track ${idx + 1}/${audioTracks.length}: ${t.name}`);
const trackStreamUrl = await repo.getAudioStreamUrl(t.id);
if (!trackStreamUrl) {
console.error(`loadAndPlay: Empty stream URL for track: ${t.name}`);
log.error(`loadAndPlay: Empty stream URL for track: ${t.name}`);
throw new Error(`Failed to get stream URL for ${t.name}`);
}
return {
@@ -398,7 +403,7 @@
jellyfinItemId: t.id,
};
} catch (e) {
console.error(`loadAndPlay: Failed to build queue item for track ${t.name}:`, e);
log.error(`loadAndPlay: Failed to build queue item for track ${t.name}:`, e);
throw e; // Re-throw to fail fast and show error to user
}
}));
@@ -411,10 +416,10 @@
} as unknown as PlayQueueRequest);
// Queue will auto-update from Rust backend event
console.log("loadAndPlay: Successfully set up queue with", audioTracks.length, "tracks");
log.debug("loadAndPlay: Successfully set up queue with", audioTracks.length, "tracks");
} else {
// Fallback to single item playback
console.log("loadAndPlay: No audio tracks found in parent, falling back to single item");
log.debug("loadAndPlay: No audio tracks found in parent, falling back to single item");
// Use player_play_tracks - backend fetches all metadata from single ID
const repo = auth.getRepository();
const repositoryHandle = repo.getHandle();
@@ -430,7 +435,7 @@
});
// Queue will auto-update from Rust backend event
console.log("loadAndPlay: Set queue with single item:", item.name);
log.debug("loadAndPlay: Set queue with single item:", item.name);
}
} else {
// No queue parameter - single item playback
@@ -449,7 +454,7 @@
});
// Queue will auto-update from Rust backend event
console.log("loadAndPlay: Set queue with single item:", item.name);
log.debug("loadAndPlay: Set queue with single item:", item.name);
}
// Seek to start position if provided
@@ -463,14 +468,14 @@
loading = false;
// Fetch next episode for video episodes (for skip button)
console.log("[NextEpisode] Post-load check: isVideo=", isVideo, "currentMedia=", currentMedia?.kind, currentMedia?.name);
nextEpisodeLog.debug("Post-load check: isVideo=", isVideo, "currentMedia=", currentMedia?.kind, currentMedia?.name);
if (isVideo && currentMedia) {
fetchNextEpisode(currentMedia);
} else {
console.log("[NextEpisode] Skipped fetchNextEpisode - isVideo:", isVideo, "currentMedia:", !!currentMedia);
nextEpisodeLog.debug("Skipped fetchNextEpisode - isVideo:", isVideo, "currentMedia:", !!currentMedia);
}
} catch (e) {
console.error("loadAndPlay error:", e);
log.error("loadAndPlay error:", e);
// Show detailed error including the full error object
if (e instanceof Error) {
error = `${e.name}: ${e.message}`;
@@ -599,21 +604,21 @@
// and check for next episodes. HTML5 video plays independently of the Rust
// backend queue, so the backend needs these to know what just finished.
const mediaId = currentMedia?.id ?? null;
console.log("[AutoPlay] Video ended. currentMedia:", mediaId, currentMedia?.name, "itemId (URL):", itemId);
autoPlayLog.debug("Video ended. currentMedia:", mediaId, currentMedia?.name, "itemId (URL):", itemId);
try {
const repo = auth.getRepository();
const repoHandle = repo.getHandle();
await commands.playerOnPlaybackEnded(mediaId, repoHandle);
} catch (e) {
console.error("[AutoPlay] Failed to handle playback ended:", e);
autoPlayLog.error("Failed to handle playback ended:", e);
}
}
async function fetchNextEpisode(media: MediaItem) {
nextEpisode = null;
console.log("[NextEpisode] fetchNextEpisode called:", { kind: media.kind, seriesId: media.seriesId, seasonId: media.seasonId, indexNumber: media.indexNumber, id: media.id, name: media.name });
nextEpisodeLog.debug("fetchNextEpisode called:", { kind: media.kind, seriesId: media.seriesId, seasonId: media.seasonId, indexNumber: media.indexNumber, id: media.id, name: media.name });
if (media.kind !== "episode" || !media.seasonId || media.indexNumber == null) {
console.log("[NextEpisode] Skipping - not an episode or missing seasonId/indexNumber");
nextEpisodeLog.debug("Skipping - not an episode or missing seasonId/indexNumber");
return;
}
try {
@@ -621,18 +626,18 @@
// Fetch all episodes in the season sorted by episode number
const result = await repo.getItems(media.seasonId, { sortBy: "IndexNumber", sortOrder: "Ascending", limit: 500 });
const episodes = result.items.filter(e => e.kind === "episode");
console.log("[NextEpisode] Season has", episodes.length, "episodes, current index:", media.indexNumber);
nextEpisodeLog.debug("Season has", episodes.length, "episodes, current index:", media.indexNumber);
// Find the episode after the current one by index number
const currentIdx = episodes.findIndex(e => e.id === media.id);
if (currentIdx >= 0 && currentIdx < episodes.length - 1) {
nextEpisode = episodes[currentIdx + 1];
console.log("[NextEpisode] Set nextEpisode:", nextEpisode.name, "index:", nextEpisode.indexNumber);
nextEpisodeLog.debug("Set nextEpisode:", nextEpisode.name, "index:", nextEpisode.indexNumber);
} else {
console.log("[NextEpisode] No next episode in season (current position:", currentIdx, "of", episodes.length, ")");
nextEpisodeLog.debug("No next episode in season (current position:", currentIdx, "of", episodes.length, ")");
}
} catch (e) {
console.error("[NextEpisode] Failed to fetch next episode:", e);
nextEpisodeLog.error("Failed to fetch next episode:", e);
}
}