From d54d8cc7c43024421645ca064a22be853ef2bb34 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Thu, 20 Aug 2026 19:29:59 +0200 Subject: [PATCH] refactor(logging): route frontend console calls through the logger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/lib/api/repository-client.ts | 7 +- src/lib/components/FavoriteButton.svelte | 5 +- .../components/downloads/DownloadItem.svelte | 11 +- .../library/AlbumDownloadButton.svelte | 7 +- .../library/ArtistDetailView.svelte | 11 +- .../library/ClearHistoryButton.svelte | 5 +- .../components/library/DownloadButton.svelte | 19 +- .../library/GenericGenreBrowser.svelte | 7 +- .../library/GenericMediaListPage.svelte | 5 +- src/lib/components/library/MediaCard.svelte | 5 +- .../library/PersonDetailView.svelte | 5 +- .../library/PlaylistDetailView.svelte | 15 +- .../library/RelatedItemsSection.svelte | 11 +- .../library/SeasonDownloadButton.svelte | 13 +- .../library/SeriesDownloadButton.svelte | 13 +- src/lib/components/library/TrackList.svelte | 11 +- .../library/VideoDownloadButton.svelte | 17 +- .../library/WatchedToggleButton.svelte | 5 +- src/lib/components/player/AudioPlayer.svelte | 5 +- src/lib/components/player/MiniPlayer.svelte | 7 +- src/lib/components/player/Queue.svelte | 7 +- src/lib/components/player/VideoPlayer.svelte | 211 +++++++++--------- .../playlist/AddToPlaylistModal.svelte | 7 +- .../playlist/CreatePlaylistModal.svelte | 5 +- .../sessions/SessionPickerModal.svelte | 11 +- src/lib/player/adapters/html5Adapter.ts | 5 +- src/lib/player/adapters/rustReportHost.ts | 11 +- src/lib/services/deviceId.ts | 5 +- src/lib/services/favorites.ts | 5 +- src/lib/services/imageCache.ts | 7 +- src/lib/services/networkType.ts | 7 +- src/lib/services/nextEpisodeService.ts | 7 +- src/lib/services/offlineCatalog.ts | 19 +- src/lib/services/playbackCapabilities.ts | 5 +- src/lib/services/playbackReporting.ts | 25 ++- src/lib/services/playerEvents.ts | 33 +-- src/lib/services/preload.ts | 13 +- src/lib/services/syncService.ts | 13 +- src/lib/stores/auth.ts | 81 +++---- src/lib/stores/connectivity.ts | 21 +- src/lib/stores/downloads.ts | 87 ++++---- src/lib/stores/home.ts | 5 +- src/lib/stores/library.ts | 23 +- src/lib/stores/lmsSync.ts | 5 +- src/lib/stores/movies.ts | 9 +- src/lib/stores/music.ts | 9 +- src/lib/stores/playbackMode.ts | 53 ++--- src/lib/stores/queue.ts | 7 +- src/lib/stores/sessions.ts | 49 ++-- src/lib/stores/tv.ts | 9 +- src/lib/utils/backgroundAudio.ts | 10 +- src/lib/utils/haptics.ts | 6 +- src/lib/utils/immersive.ts | 10 +- src/lib/utils/pictureInPicture.ts | 14 +- src/lib/utils/safeArea.ts | 6 +- src/lib/utils/videoSurface.ts | 15 +- src/routes/+layout.svelte | 15 +- src/routes/+page.svelte | 5 +- src/routes/downloads/+page.svelte | 9 +- src/routes/library/[id]/+page.svelte | 29 +-- src/routes/library/favorites/+page.svelte | 5 +- src/routes/player/[id]/+page.svelte | 97 ++++---- src/routes/settings/+page.svelte | 17 +- 63 files changed, 686 insertions(+), 490 deletions(-) diff --git a/src/lib/api/repository-client.ts b/src/lib/api/repository-client.ts index 2c7427d0..71d153a8 100644 --- a/src/lib/api/repository-client.ts +++ b/src/lib/api/repository-client.ts @@ -19,6 +19,9 @@ import type { PlaylistEntry, PlaylistCreatedResult, } from "./types"; +import { createLogger } from "$lib/utils/logger"; + +const log = createLogger("RepositoryClient"); /** * Repository client - thin wrapper over Rust HybridRepository @@ -39,14 +42,14 @@ export class RepositoryClient { accessToken: string, serverId: string ): Promise { - console.log("[RepositoryClient] Creating Rust repository..."); + log.debug("Creating Rust repository..."); this.handle = await commands.repositoryCreate(serverUrl, userId, accessToken, serverId); // Store for URL construction this._serverUrl = serverUrl; this._accessToken = accessToken; - console.log("[RepositoryClient] Repository created with handle:", this.handle); + log.debug("Repository created with handle:", this.handle); return this.handle; } diff --git a/src/lib/components/FavoriteButton.svelte b/src/lib/components/FavoriteButton.svelte index 5ecbe6fa..12f60bf5 100644 --- a/src/lib/components/FavoriteButton.svelte +++ b/src/lib/components/FavoriteButton.svelte @@ -4,6 +4,9 @@ import { haptics } from "$lib/utils/haptics"; import { toast } from "$lib/stores/toast"; import { favoriteOverrides } from "$lib/stores/favorites"; + import { createLogger } from "$lib/utils/logger"; + + const log = createLogger("FavoriteButton"); interface Props { itemId: string; @@ -78,7 +81,7 @@ isAnimating = false; }, 600); } catch (error) { - console.error("Failed to toggle favorite:", error); + log.error("Failed to toggle favorite:", error); toast.show("Failed to update favorites", "error"); isAnimating = false; } finally { diff --git a/src/lib/components/downloads/DownloadItem.svelte b/src/lib/components/downloads/DownloadItem.svelte index c0dd0513..06ec20eb 100644 --- a/src/lib/components/downloads/DownloadItem.svelte +++ b/src/lib/components/downloads/DownloadItem.svelte @@ -1,5 +1,8 @@ diff --git a/src/lib/components/library/AlbumDownloadButton.svelte b/src/lib/components/library/AlbumDownloadButton.svelte index 851e1ce6..32fe5666 100644 --- a/src/lib/components/library/AlbumDownloadButton.svelte +++ b/src/lib/components/library/AlbumDownloadButton.svelte @@ -2,6 +2,9 @@ import { downloads } from "$lib/stores/downloads"; import { auth } from "$lib/stores/auth"; import type { MediaItem } from "$lib/api/types"; + import { createLogger } from "$lib/utils/logger"; + + const log = createLogger("AlbumDownloadButton"); interface Props { albumId: string; @@ -60,7 +63,7 @@ try { const userId = $auth.user?.id; if (!userId) { - console.error("No user ID found"); + log.error("No user ID found"); return; } @@ -95,7 +98,7 @@ await downloads.refresh(userId); } } catch (error) { - console.error("Album download operation failed:", error); + log.error("Album download operation failed:", error); } finally { isProcessing = false; } diff --git a/src/lib/components/library/ArtistDetailView.svelte b/src/lib/components/library/ArtistDetailView.svelte index 41e08aa8..2af02d53 100644 --- a/src/lib/components/library/ArtistDetailView.svelte +++ b/src/lib/components/library/ArtistDetailView.svelte @@ -8,6 +8,9 @@ import CachedImage from "$lib/components/common/CachedImage.svelte"; import FavoriteButton from "$lib/components/FavoriteButton.svelte"; import { favoriteOverrides, resolveIsFavorite } from "$lib/stores/favorites"; + import { createLogger } from "$lib/utils/logger"; + + const log = createLogger("ArtistDetailView"); interface Props { artist: MediaItem; @@ -47,7 +50,7 @@ }); albums = albumsResult.items.filter(item => item.kind === "album"); } catch (e) { - console.warn("Failed to load albums:", e); + log.warn("Failed to load albums:", e); } finally { albumsLoading = false; } @@ -62,7 +65,7 @@ }); topTracks = tracksResult.items.filter(item => item.kind === "track"); } catch (e) { - console.warn("Failed to load tracks:", e); + log.warn("Failed to load tracks:", e); } finally { tracksLoading = false; } @@ -82,14 +85,14 @@ .slice(0, 6); } } catch (e) { - console.warn("Failed to load related artists:", e); + log.warn("Failed to load related artists:", e); } finally { artistsLoading = false; } singlesLoading = false; } catch (e) { - console.error("Error loading artist content:", e); + log.error("Error loading artist content:", e); } } diff --git a/src/lib/components/library/ClearHistoryButton.svelte b/src/lib/components/library/ClearHistoryButton.svelte index 47931605..90f2092b 100644 --- a/src/lib/components/library/ClearHistoryButton.svelte +++ b/src/lib/components/library/ClearHistoryButton.svelte @@ -11,6 +11,9 @@ diff --git a/src/lib/components/library/VideoDownloadButton.svelte b/src/lib/components/library/VideoDownloadButton.svelte index 4bcdb304..3d9677db 100644 --- a/src/lib/components/library/VideoDownloadButton.svelte +++ b/src/lib/components/library/VideoDownloadButton.svelte @@ -3,6 +3,9 @@ import { auth } from "$lib/stores/auth"; import { commands } from "$lib/api/bindings"; import { QUALITY_PRESETS, type QualityPreset } from "$lib/api/quality-presets"; + import { createLogger } from "$lib/utils/logger"; + + const log = createLogger("VideoDownloadButton"); interface Props { itemId: string; @@ -57,17 +60,17 @@ try { const userId = $auth.user?.id; if (!userId) { - console.error("No user ID found"); + log.error("No user ID found"); return; } const repo = auth.getRepository(); - console.log("🎬 Starting video download for item:", itemId, "quality:", quality); + log.debug("🎬 Starting video download for item:", itemId, "quality:", quality); // Get stream URL based on quality const streamUrl = await repo.getVideoDownloadUrl(itemId, quality); - console.log(" Stream URL obtained"); + log.debug(" Stream URL obtained"); // Get target directory const targetDir = await commands.storageGetPath(); @@ -85,7 +88,7 @@ filePath = `videos/${safeName}.mp4`; } - console.log(" File path:", filePath); + log.debug(" File path:", filePath); // Queue download with video metadata const downloadId = await downloads.downloadVideo( @@ -101,16 +104,16 @@ episodeNumber, seasonNumber ); - console.log(" Download queued with ID:", downloadId); + log.debug(" Download queued with ID:", downloadId); // Pin the item metadata await downloads.pinItem(itemId); // Actually start the download await commands.startDownload(downloadId, streamUrl, targetDir); - console.log(" Download started"); + log.debug(" Download started"); } catch (error) { - console.error("Failed to start video download:", error); + log.error("Failed to start video download:", error); } finally { isProcessing = false; } diff --git a/src/lib/components/library/WatchedToggleButton.svelte b/src/lib/components/library/WatchedToggleButton.svelte index 9102b6e0..2b36272d 100644 --- a/src/lib/components/library/WatchedToggleButton.svelte +++ b/src/lib/components/library/WatchedToggleButton.svelte @@ -14,6 +14,9 @@ --> diff --git a/src/lib/components/player/MiniPlayer.svelte b/src/lib/components/player/MiniPlayer.svelte index e0f8185d..2853cd9b 100644 --- a/src/lib/components/player/MiniPlayer.svelte +++ b/src/lib/components/player/MiniPlayer.svelte @@ -38,6 +38,9 @@ import CastButton from "$lib/components/sessions/CastButton.svelte"; import VolumeControl from "./VolumeControl.svelte"; import CachedImage from "../common/CachedImage.svelte"; + import { createLogger } from "$lib/utils/logger"; + + const log = createLogger("MiniPlayer"); interface Props { media: MediaItem | null; @@ -159,7 +162,7 @@ await playerController.seek(newPosition); haptics.tap(); } catch (err) { - console.error("Failed to seek:", err); + log.error("Failed to seek:", err); toast.show("Failed to seek", "error"); } } @@ -230,7 +233,7 @@ // Vertical swipe if (Math.abs(diffY) > swipeThreshold && diffY > 0) { // Swiped up - Open full player - console.log("[MiniPlayer] Swipe-up detected, expanding player"); + log.debug("Swipe-up detected, expanding player"); haptics.tap(); onExpand?.(); } diff --git a/src/lib/components/player/Queue.svelte b/src/lib/components/player/Queue.svelte index 942a4bff..9074202e 100644 --- a/src/lib/components/player/Queue.svelte +++ b/src/lib/components/player/Queue.svelte @@ -6,6 +6,9 @@ import { auth } from "$lib/stores/auth"; import { queue } from "$lib/stores/queue"; import CachedImage from "../common/CachedImage.svelte"; + import { createLogger } from "$lib/utils/logger"; + + const log = createLogger("QueueView"); interface Props { items: MediaItem[]; @@ -82,7 +85,7 @@ // Sync with backend await playerController.moveInQueue(fromIndex, toIndex); } catch (e) { - console.error("Failed to move queue item:", e); + log.error("Failed to move queue item:", e); // The store already updated optimistically, refresh if needed } } @@ -109,7 +112,7 @@ queue.removeFromQueue(index); await playerController.removeFromQueue(index); } catch (err) { - console.error("Failed to remove from queue:", err); + log.error("Failed to remove from queue:", err); } } diff --git a/src/lib/components/player/VideoPlayer.svelte b/src/lib/components/player/VideoPlayer.svelte index 738903a9..0da38583 100644 --- a/src/lib/components/player/VideoPlayer.svelte +++ b/src/lib/components/player/VideoPlayer.svelte @@ -75,6 +75,9 @@ planHandoffReturn, type BackgroundAudioState, } from "./backgroundAudioHandoff"; + import { createLogger } from "$lib/utils/logger"; + + const log = createLogger("VideoPlayer"); interface Props { media: MediaItem | null; @@ -287,11 +290,11 @@ // TRACES: UR-021 | IR-016, JA-009 | DR-024 const audioTracks = $derived(() => { if (!media || !media.mediaStreams) { - console.log("[VideoPlayer] No media or mediaStreams available"); + log.debug("No media or mediaStreams available"); return []; } const tracks = media.mediaStreams.filter(stream => stream.kind === "audio"); - console.log("[VideoPlayer] Found audio tracks:", tracks.length, tracks); + log.debug("Found audio tracks:", tracks.length, tracks); return tracks; }); @@ -304,7 +307,7 @@ if (preference.audioTrackDisplayTitle) { const match = tracks.find(t => t.displayTitle === preference.audioTrackDisplayTitle); if (match) { - console.log("[VideoPlayer] Matched audio track by display title:", match.displayTitle); + log.debug("Matched audio track by display title:", match.displayTitle); return match.index; } } @@ -313,14 +316,14 @@ if (preference.audioTrackLanguage) { const match = tracks.find(t => t.language === preference.audioTrackLanguage); if (match) { - console.log("[VideoPlayer] Matched audio track by language:", match.language); + log.debug("Matched audio track by language:", match.language); return match.index; } } // Fall back to default track const defaultTrack = tracks.find(t => t.isDefault) || tracks[0]; - console.log("[VideoPlayer] Using default/first audio track:", defaultTrack.displayTitle || defaultTrack.language); + log.debug("Using default/first audio track:", defaultTrack.displayTitle || defaultTrack.language); return defaultTrack.index; } @@ -335,15 +338,15 @@ const preference = await commands.storageGetSeriesAudioPreference(userId, media.seriesId); if (preference) { - console.log("[VideoPlayer] Loaded series audio preference:", preference); + log.debug("Loaded series audio preference:", preference); const matchedIndex = findBestAudioTrack(preference); if (matchedIndex !== null) { selectedAudioTrackIndex = matchedIndex; - console.log("[VideoPlayer] Applied series audio preference, track index:", matchedIndex); + log.debug("Applied series audio preference, track index:", matchedIndex); } } } catch (err) { - console.warn("[VideoPlayer] Failed to load series audio preference:", err); + log.warn("Failed to load series audio preference:", err); } } @@ -355,11 +358,11 @@ // TRACES: UR-020 | DR-176 | UT-168 const subtitleTracks = $derived(() => { if (!media || !media.mediaStreams) { - console.log("[VideoPlayer] No media or mediaStreams available for subtitles"); + log.debug("No media or mediaStreams available for subtitles"); return []; } const tracks = subtitleStreamsOf(media.mediaStreams); - console.log("[VideoPlayer] Found subtitle tracks:", tracks.length, tracks); + log.debug("Found subtitle tracks:", tracks.length, tracks); return tracks; }); @@ -547,7 +550,7 @@ if (isHlsStream && Hls.isSupported()) { // Clean up existing HLS instance if any - CRITICAL for preventing dual audio if (hls) { - console.log('[VideoPlayer] Cleaning up existing HLS instance'); + log.debug('Cleaning up existing HLS instance'); // Detach from media element first to stop all audio/video hls.detachMedia(); // Stop loading and flush buffers @@ -571,7 +574,7 @@ setTimeout(() => { if (!videoElement) return; - console.log('[VideoPlayer] Creating new HLS instance for:', currentStreamUrl); + log.debug('Creating new HLS instance for:', currentStreamUrl); // Create new HLS instance hls = new Hls({ @@ -599,14 +602,14 @@ // Listen for media attached event hls.on(Hls.Events.MEDIA_ATTACHED, () => { - console.log('[VideoPlayer] HLS.js attached to video element'); + log.debug('HLS.js attached to video element'); // Load the HLS stream hls!.loadSource(currentStreamUrl); }); // Listen for manifest parsed event hls.on(Hls.Events.MANIFEST_PARSED, () => { - console.log('[VideoPlayer] HLS manifest parsed, ready to play'); + log.debug('HLS manifest parsed, ready to play'); }); // On the Android WebView the element's own `canplay` may not fire for @@ -623,7 +626,7 @@ if (canplayFallbackTimeout) clearTimeout(canplayFallbackTimeout); canplayFallbackTimeout = setTimeout(() => { if (!isMediaReady && videoElement && videoElement.readyState >= 2) { - console.warn('[VideoPlayer] HLS canplay fallback - revealing video (readyState:', videoElement.readyState, ')'); + log.warn('HLS canplay fallback - revealing video (readyState:', videoElement.readyState, ')'); markMediaReady(); } }, 5000); @@ -633,7 +636,7 @@ // Handle errors hls.on(Hls.Events.ERROR, (event, data) => { - console.error('[VideoPlayer] HLS error:', data); + log.error('HLS error:', data); if (data.fatal) { // Is this the stream ending or the stream breaking? Jellyfin's // transcoded HLS doesn't always emit #EXT-X-ENDLIST, so both arrive @@ -650,25 +653,25 @@ attempts: hlsFatalRecoveryAttempts, })) { case 'ended': - console.log('[VideoPlayer] Fatal network error near end of stream - treating as ended'); + log.debug('Fatal network error near end of stream - treating as ended'); notifyEnded(); break; case 'retry': - console.error('[VideoPlayer] Fatal network error, trying to recover (attempt', hlsFatalRecoveryAttempts, ')'); + log.error('Fatal network error, trying to recover (attempt', hlsFatalRecoveryAttempts, ')'); hls!.startLoad(); break; case 'giveUp': - console.error('[VideoPlayer] Fatal network error, max recovery attempts reached'); + log.error('Fatal network error, max recovery attempts reached'); hls!.destroy(); break; } break; case Hls.ErrorTypes.MEDIA_ERROR: - console.error('[VideoPlayer] Fatal media error, trying to recover'); + log.error('Fatal media error, trying to recover'); hls!.recoverMediaError(); break; default: - console.error('[VideoPlayer] Unrecoverable HLS error'); + log.error('Unrecoverable HLS error'); hls!.destroy(); break; } @@ -678,7 +681,7 @@ // Cleanup on effect re-run return () => { - console.log('[VideoPlayer] Effect cleanup: destroying HLS instance'); + log.debug('Effect cleanup: destroying HLS instance'); if (hls) { hls.detachMedia(); hls.stopLoad(); @@ -691,11 +694,11 @@ }; } else if (isHlsStream && videoElement.canPlayType('application/vnd.apple.mpegurl')) { // Native HLS support (Safari) - console.log('[VideoPlayer] Using native HLS support'); + log.debug('Using native HLS support'); videoElement.src = currentStreamUrl; } else { // Not an HLS stream, use regular video element - console.log('[VideoPlayer] Using regular video element for non-HLS stream'); + log.debug('Using regular video element for non-HLS stream'); } }); @@ -704,24 +707,24 @@ if (videoElement) { videoElement.muted = false; videoElement.volume = 1.0; - console.log("[VideoPlayer] Video element configured: muted=", videoElement.muted, "volume=", videoElement.volume); + log.debug("Video element configured: muted=", videoElement.muted, "volume=", videoElement.volume); // DIAGNOSTIC: Check if video has audio tracks if ((videoElement as any).audioTracks) { - console.log("[VideoPlayer] Audio tracks count:", (videoElement as any).audioTracks.length); + log.debug("Audio tracks count:", (videoElement as any).audioTracks.length); // Set initial audio track (prefer default track) if (selectedAudioTrackIndex === null && audioTracks().length > 0) { const defaultTrack = audioTracks().find(t => t.isDefault); selectedAudioTrackIndex = defaultTrack ? defaultTrack.index : audioTracks()[0].index; - console.log("[VideoPlayer] Selected default audio track:", selectedAudioTrackIndex); + log.debug("Selected default audio track:", selectedAudioTrackIndex); } } if ((videoElement as any).mozHasAudio !== undefined) { - console.log("[VideoPlayer] mozHasAudio:", (videoElement as any).mozHasAudio); + log.debug("mozHasAudio:", (videoElement as any).mozHasAudio); } if ((videoElement as any).webkitAudioDecodedByteCount !== undefined) { - console.log("[VideoPlayer] webkitAudioDecodedByteCount:", (videoElement as any).webkitAudioDecodedByteCount); + log.debug("webkitAudioDecodedByteCount:", (videoElement as any).webkitAudioDecodedByteCount); } } }); @@ -749,7 +752,7 @@ return; } untrack(() => { - console.log("[VideoPlayer] Initial position changed, seeking to:", pos); + log.debug("Initial position changed, seeking to:", pos); lastAppliedInitialPosition = pos; if (videoElement) { videoElement.currentTime = pos; @@ -775,7 +778,7 @@ selectedQuality = settings.streamingQuality ?? "original"; }) .catch((err) => { - console.warn("[VideoPlayer] Failed to load streaming qualities:", err); + log.warn("Failed to load streaming qualities:", err); }); }); @@ -808,8 +811,8 @@ // Initialize player via Rust - Rust will decide which backend to use based on platform if (media && currentStreamUrl) { try { - console.log("[VideoPlayer] Initializing player for:", media.name); - console.log("[VideoPlayer] Stream URL:", currentStreamUrl); + log.debug("Initializing player for:", media.name); + log.debug("Stream URL:", currentStreamUrl); // Resolve subtitle URLs for the native (ExoPlayer) path. These must be // in hand *before* the play request: ExoPlayer sideloads subtitles as @@ -827,7 +830,7 @@ sentSubtitleTracks = mediaSourceId ? await resolveSubtitleTracks(media.mediaStreams, (index) => getSubtitleUrl(index)) : []; - console.log(`[VideoPlayer] Sending ${sentSubtitleTracks.length} subtitle tracks to the backend`); + log.debug(`Sending ${sentSubtitleTracks.length} subtitle tracks to the backend`); // Call Rust backend to start playback // Rust will choose ExoPlayer (Android), libmpv (Linux), or tell us to use HTML5 @@ -847,7 +850,7 @@ // Rust tells us which backend it's using useHtml5Element = response.useHtml5Element; backendChosen = true; - console.log(`[VideoPlayer] Backend: ${response.backend}, useHtml5Element: ${useHtml5Element}`); + log.debug(`Backend: ${response.backend}, useHtml5Element: ${useHtml5Element}`); // Rust reported a native backend (Android/ExoPlayer). Honour it only if // the user opted into the experimental native path; otherwise fall back @@ -858,13 +861,13 @@ // just started, or ExoPlayer and the