diff --git a/src/main/features/core/autodiscover/index.ts b/src/main/features/core/autodiscover/index.ts index 9155b1011..115c5a8ce 100644 --- a/src/main/features/core/autodiscover/index.ts +++ b/src/main/features/core/autodiscover/index.ts @@ -1,6 +1,7 @@ import { createSocket } from 'dgram'; import { ipcMain } from 'electron'; +import { mainLogger } from '/@/main/logger'; import { DiscoveredServerItem, ServerType } from '/@/shared/types/types'; type JellyfinResponse = { @@ -26,7 +27,7 @@ function discoverJellyfin(reply: (server: DiscoveredServerItem) => void) { }); } catch (e) { // Got a spurious response, ignore? - console.error(e); + mainLogger.error('Autodiscover Jellyfin parse error', e); } }); @@ -51,5 +52,5 @@ ipcMain.on('autodiscover-ping', (ev) => { discoverAll((result) => port.postMessage(result)) .then(() => port.close()) - .catch((err) => console.error(err)); + .catch((err) => mainLogger.error('Autodiscover failed', err)); }); diff --git a/src/main/features/core/lyrics/genius.ts b/src/main/features/core/lyrics/genius.ts index ff2029d21..75ccfd566 100644 --- a/src/main/features/core/lyrics/genius.ts +++ b/src/main/features/core/lyrics/genius.ts @@ -7,6 +7,7 @@ import { LyricSearchQuery, LyricSource, } from '.'; +import { mainLogger } from '../../../logger'; import { orderSearchResults } from './shared'; const SEARCH_URL = 'https://genius.com/api/search/song'; @@ -100,7 +101,7 @@ export async function getLyricsBySongId(url: string): Promise { try { result = await axios.get(url, { responseType: 'text' }); } catch (e) { - console.error('Genius lyrics request got an error!', (e as Error)?.message); + mainLogger.error('Genius lyrics request failed', (e as Error)?.message); return null; } @@ -138,7 +139,7 @@ export async function getSearchResults( }, }); } catch (e) { - console.error('Genius search request got an error!', (e as Error)?.message); + mainLogger.error('Genius search request failed', (e as Error)?.message); return null; } @@ -193,7 +194,7 @@ async function getSongId( }, }); } catch (e) { - console.error('Genius search request got an error!', (e as Error)?.message); + mainLogger.error('Genius search request failed', (e as Error)?.message); return null; } diff --git a/src/main/features/core/lyrics/index.ts b/src/main/features/core/lyrics/index.ts index ffc5ad15f..48f4e5c1c 100644 --- a/src/main/features/core/lyrics/index.ts +++ b/src/main/features/core/lyrics/index.ts @@ -1,5 +1,6 @@ import { ipcMain } from 'electron'; +import { mainLogger } from '../../../logger'; import { store } from '../settings'; import { getLyricsBySongId as getGenius, getSearchResults as searchGenius } from './genius'; import { getLyricsBySongId as getLrcLib, getSearchResults as searchLrcLib } from './lrclib'; @@ -96,7 +97,7 @@ const searchAllSources = async ( allSearchResults.push(...result.value.searchResults); } else if (result.status === 'rejected') { const index = settled.indexOf(result); - console.error(`Error searching ${sources[index]} for lyrics:`, result.reason); + mainLogger.error(`Error searching ${sources[index]} for lyrics`, result.reason); } } return allSearchResults; @@ -160,7 +161,7 @@ const getRemoteLyrics = async (song: Song) => { }; } } catch (error) { - console.error(`Error fetching lyrics from ${bestMatch.source}:`, error); + mainLogger.error(`Error fetching lyrics from ${bestMatch.source}`, error); } if (lyricsFromSource) { diff --git a/src/main/features/core/lyrics/lrclib.ts b/src/main/features/core/lyrics/lrclib.ts index daa6e5576..eff998eb2 100644 --- a/src/main/features/core/lyrics/lrclib.ts +++ b/src/main/features/core/lyrics/lrclib.ts @@ -7,6 +7,7 @@ import { LyricSearchQuery, LyricSource, } from '.'; +import { mainLogger } from '../../../logger'; import { orderSearchResults } from './shared'; const FETCH_URL = 'https://lrclib.net/api/get'; @@ -46,7 +47,7 @@ export async function getLyricsBySongId(songId: string): Promise try { result = await axios.get(`${FETCH_URL}/${songId}`); } catch (e) { - console.error('LrcLib lyrics request got an error!', (e as Error)?.message); + mainLogger.error('LrcLib lyrics request failed', (e as Error)?.message); return null; } @@ -69,7 +70,7 @@ export async function getSearchResults( }, }); } catch (e) { - console.error('LrcLib search request got an error!', (e as Error)?.message); + mainLogger.error('LrcLib search request failed', (e as Error)?.message); return null; } @@ -107,7 +108,7 @@ export async function query( timeout: TIMEOUT_MS, }); } catch (e) { - console.error('LrcLib search request got an error!', (e as Error).message); + mainLogger.error('LrcLib search request failed', (e as Error).message); return null; } diff --git a/src/main/features/core/lyrics/netease.ts b/src/main/features/core/lyrics/netease.ts index b06cbf7ff..dd67452d7 100644 --- a/src/main/features/core/lyrics/netease.ts +++ b/src/main/features/core/lyrics/netease.ts @@ -6,6 +6,7 @@ import { LyricSearchQuery, LyricSource, } from '.'; +import { mainLogger } from '../../../logger'; import { store } from '../settings'; import { orderSearchResults } from './shared'; @@ -81,7 +82,7 @@ export async function getLyricsBySongId(songId: string): Promise }, }); } catch (e) { - console.error('NetEase lyrics request got an error!', e); + mainLogger.error('NetEase lyrics request failed', e); return null; } const enableTranslation = store.get('enableNeteaseTranslation', false) as boolean; @@ -114,7 +115,7 @@ export async function getSearchResults( }, }); } catch (e) { - console.error('NetEase search request got an error!', e); + mainLogger.error('NetEase search request failed', e); return null; } diff --git a/src/main/features/core/player/index.ts b/src/main/features/core/player/index.ts index 4ecb11457..80d1bfa85 100644 --- a/src/main/features/core/player/index.ts +++ b/src/main/features/core/player/index.ts @@ -1,4 +1,3 @@ -import console from 'console'; import { app, ipcMain } from 'electron'; import { rm } from 'fs/promises'; import uniq from 'lodash/uniq'; @@ -7,6 +6,7 @@ import { pid } from 'node:process'; import process from 'process'; import { getMainWindow, sendToastToRenderer } from '../../../index'; +import { mainLogger } from '../../../logger'; import { createLog, isWindows } from '../../../utils'; import { store } from '../settings'; @@ -109,7 +109,7 @@ const createMpv = async (data: { try { await mpv.start(); } catch (error: any) { - console.error('mpv failed to start', error); + mainLogger.error('mpv failed to start', error); } finally { await mpv.setMultipleProperties(properties || {}); } @@ -672,7 +672,7 @@ process.on('SIGTERM', async () => { // Handle uncaught exceptions - cleanup mpv before crashing process.on('uncaughtException', async (error) => { - console.error('Uncaught exception:', error); + mainLogger.error('Uncaught exception', error); await cleanupMpv(true).catch(() => { // Ignore cleanup errors during crash }); @@ -680,7 +680,7 @@ process.on('uncaughtException', async (error) => { // Handle unhandled rejections - cleanup mpv process.on('unhandledRejection', async (reason) => { - console.error('Unhandled rejection:', reason); + mainLogger.error('Unhandled rejection', reason); await cleanupMpv(true).catch(() => { // Ignore cleanup errors }); diff --git a/src/main/features/core/remote/index.ts b/src/main/features/core/remote/index.ts index 6e5a7aff1..fbee27db1 100644 --- a/src/main/features/core/remote/index.ts +++ b/src/main/features/core/remote/index.ts @@ -10,6 +10,7 @@ import { deflate, gzip } from 'zlib'; import manifest from './manifest.json'; import { getMainWindow } from '/@/main/index'; +import { mainLogger } from '/@/main/logger'; import { isLinux } from '/@/main/utils'; import { QueueSong } from '/@/shared/types/domain-types'; import { ClientEvent, ServerEvent } from '/@/shared/types/remote-types'; @@ -349,7 +350,7 @@ const enableServer = (config: RemoteConfig): Promise => { }, 10000) as unknown as number; } - ws.on('error', console.error); + ws.on('error', (err) => mainLogger.error('Remote WebSocket error', err)); ws.on('message', (data) => { try { @@ -488,7 +489,7 @@ const enableServer = (config: RemoteConfig): Promise => { } } } catch (error) { - console.error(error); + mainLogger.error('Remote message handler error', error); } }); diff --git a/src/main/index.ts b/src/main/index.ts index 711d3e207..e9db26fbf 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -29,6 +29,7 @@ import packageJson from '../../package.json'; import { disableMediaKeys, enableMediaKeys } from './features/core/player/media-keys'; import { shutdownServer } from './features/core/remote'; import { store } from './features/core/settings'; +import { mainLogger } from './logger'; import MenuBuilder from './menu'; import { autoUpdaterLogInterface, @@ -66,7 +67,7 @@ type UpdaterInstance = AppImageUpdater | MacUpdater | NsisUpdater | typeof autoU class AppUpdater { constructor() { const effectiveChannel = store.get('release_channel') as string; - console.log('Effective update channel:', effectiveChannel); + mainLogger.info('Effective update channel:', effectiveChannel); if (effectiveChannel === 'alpha') { checkAllChannelsAndGetBest().then(({ updater: updaterInstance }) => { updaterInstance.autoInstallOnAppQuit = true; @@ -103,7 +104,7 @@ async function checkAllChannelsAndGetBest(): Promise<{ alphaUpdater.allowDowngrade = true; try { - console.log('Checking for updates on alpha channel'); + mainLogger.info('Checking for updates on alpha channel'); const alphaResult = await alphaUpdater.checkForUpdates(); if ( alphaResult?.updateInfo?.version && @@ -120,7 +121,7 @@ async function checkAllChannelsAndGetBest(): Promise<{ try { autoUpdater.setFeedURL(GITHUB_UPDATER_CONFIG); configureAutoUpdaterForChannel('latest'); - console.log('Checking for updates on latest channel (GitHub)'); + mainLogger.info('Checking for updates on latest channel (GitHub)'); const latestResult = await autoUpdater.checkForUpdates(); if ( latestResult?.updateInfo?.version && @@ -155,13 +156,13 @@ function configureAndGetUpdater(): UpdaterInstance { let releaseChannel = store.get('release_channel'); const isNotConfigured = !releaseChannel; - console.log('Release channel:', releaseChannel); - console.log('Is beta version:', isBetaVersion); - console.log('Is alpha version:', isAlphaVersion); - console.log('Is not configured:', isNotConfigured); + mainLogger.info('Release channel:', releaseChannel); + mainLogger.info('Is beta version:', isBetaVersion); + mainLogger.info('Is alpha version:', isAlphaVersion); + mainLogger.info('Is not configured:', isNotConfigured); if (isNotConfigured) { - console.log('Release channel not configured, setting default channel'); + mainLogger.info('Release channel not configured, setting default channel'); const defaultChannel = isAlphaVersion ? 'alpha' : isBetaVersion ? 'beta' : 'latest'; store.set('release_channel', defaultChannel); releaseChannel = defaultChannel; @@ -235,7 +236,7 @@ function createAlphaUpdaterInstance(): AppImageUpdater | MacUpdater | NsisUpdate protocol.registerSchemesAsPrivileged([{ privileges: { bypassCSP: true }, scheme: 'feishin' }]); process.on('uncaughtException', (error: any) => { - console.error('Error in main process', error); + mainLogger.error('Uncaught exception in main process', error); }); if (store.get('ignore_ssl')) { @@ -521,12 +522,12 @@ async function createWindow(first = true): Promise { 'app-check-for-updates', async (): Promise<{ updateAvailable: boolean; version?: string }> => { if (disableAutoUpdates()) { - console.log('Auto updates are disabled'); + mainLogger.info('Auto updates are disabled'); return { updateAvailable: false }; } try { - console.log('Checking for updates'); + mainLogger.info('Checking for updates'); const effectiveChannel = store.get('release_channel') as string; let result: null | UpdateCheckResult; let updater: UpdaterInstance; @@ -541,9 +542,9 @@ async function createWindow(first = true): Promise { } const updateAvailable = result?.isUpdateAvailable ?? false; - console.log('Update available:', updateAvailable); + mainLogger.info('Update available:', updateAvailable); if (updateAvailable && store.get('disable_auto_updates') !== true) { - console.log('Downloading update'); + mainLogger.info('Downloading update'); updater.downloadUpdate(); } @@ -552,7 +553,7 @@ async function createWindow(first = true): Promise { version: result?.updateInfo?.version, }; } catch { - console.log('Error checking for updates'); + mainLogger.error('Error checking for updates'); return { updateAvailable: false }; } }, diff --git a/src/main/logger.ts b/src/main/logger.ts new file mode 100644 index 000000000..cd4194c24 --- /dev/null +++ b/src/main/logger.ts @@ -0,0 +1,36 @@ +const pad = (n: number) => String(n).padStart(2, '0'); + +const timestamp = () => { + const d = new Date(); + return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; +}; + +const format = (level: string, message: string, ...args: unknown[]) => { + const prefix = `[${timestamp()}] [${level}] ${message}`; + if (args.length > 0) { + console.log(prefix, ...args); + } else { + console.log(prefix); + } +}; + +export const mainLogger = { + debug: (message: string, ...args: unknown[]) => format('DEBUG', message, ...args), + error: (message: string, ...args: unknown[]) => { + const prefix = `[${timestamp()}] [ERROR] ${message}`; + if (args.length > 0) { + console.error(prefix, ...args); + } else { + console.error(prefix); + } + }, + info: (message: string, ...args: unknown[]) => format('INFO', message, ...args), + warn: (message: string, ...args: unknown[]) => { + const prefix = `[${timestamp()}] [WARN] ${message}`; + if (args.length > 0) { + console.warn(prefix, ...args); + } else { + console.warn(prefix); + } + }, +}; diff --git a/src/renderer/api/controller.ts b/src/renderer/api/controller.ts index dffa34e78..e45212903 100644 --- a/src/renderer/api/controller.ts +++ b/src/renderer/api/controller.ts @@ -4,6 +4,7 @@ import { NavidromeController } from '/@/renderer/api/navidrome/navidrome-control import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller'; import { mergeMusicFolderId } from '/@/renderer/api/utils-music-folder'; import { getServerById, useAuthStore, useSettingsStore } from '/@/renderer/store'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { toast } from '/@/shared/components/toast/toast'; import { AuthenticationResponse, @@ -31,6 +32,7 @@ const apiController = ( const serverType = type || useAuthStore.getState().currentServer?.type; if (!serverType) { + logFn.warn('No server selected', { category: LogCategory.API }); toast.error({ message: i18n.t('error.serverNotSelectedError', { postProcess: 'sentenceCase', @@ -43,6 +45,10 @@ const apiController = ( const controllerFn = endpoints?.[serverType]?.[endpoint]; if (typeof controllerFn !== 'function') { + logFn.warn('Endpoint not implemented', { + category: LogCategory.API, + meta: { endpoint, serverType }, + }); toast.error({ message: `Endpoint ${endpoint} is not implemented for ${serverType}`, title: i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' }) as string, @@ -57,6 +63,10 @@ const apiController = ( ); } + logFn.debug('API controller call', { + category: LogCategory.API, + meta: { endpoint, serverType }, + }); return controllerFn; }; diff --git a/src/renderer/api/navidrome/navidrome-api.ts b/src/renderer/api/navidrome/navidrome-api.ts index 9391eb8fa..2c5abf160 100644 --- a/src/renderer/api/navidrome/navidrome-api.ts +++ b/src/renderer/api/navidrome/navidrome-api.ts @@ -8,6 +8,7 @@ import qs from 'qs'; import i18n from '/@/i18n/i18n'; import { authenticationFailure } from '/@/renderer/api/utils'; import { useAuthStore } from '/@/renderer/store'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { getServerUrl } from '/@/renderer/utils/normalize-server-url'; import { ndType } from '/@/shared/api/navidrome/navidrome-types'; import { resultWithHeaders } from '/@/shared/api/utils'; @@ -367,11 +368,21 @@ axiosClient.interceptors.response.use( }) .catch((newError: any) => { if (newError !== TIMEOUT_ERROR) { - console.error('Error when trying to reauthenticate: ', newError); + logFn.error('Reauthentication failed', { + category: LogCategory.API, + meta: { + message: (newError as Error)?.message, + serverId: currentServer.id, + }, + }); if (isAxiosError(newError) && newError.code === 'ERR_NETWORK') { - console.log( + logFn.info( 'Network error during reauthentication - preserving credentials', + { + category: LogCategory.API, + meta: { serverId: currentServer.id }, + }, ); } else { limitedFail(currentServer); @@ -387,7 +398,10 @@ axiosClient.interceptors.response.use( } if (isAxiosError(error) && error.code === 'ERR_NETWORK') { - console.log('Network error during authentication - preserving credentials'); + logFn.info('Network error during authentication - preserving credentials', { + category: LogCategory.API, + meta: { serverId: useAuthStore.getState().currentServer?.id }, + }); } else { limitedFail(currentServer); } diff --git a/src/renderer/api/utils.ts b/src/renderer/api/utils.ts index 0185926e7..fad7e537d 100644 --- a/src/renderer/api/utils.ts +++ b/src/renderer/api/utils.ts @@ -1,16 +1,19 @@ import { useAuthStore } from '/@/renderer/store'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { toast } from '/@/shared/components/toast/toast'; import { ServerListItem } from '/@/shared/types/types'; export const authenticationFailure = (currentServer: null | ServerListItem) => { + logFn.error('Token expired', { + category: LogCategory.API, + meta: { serverId: currentServer?.id }, + }); toast.error({ message: 'Your session has expired.', }); if (currentServer) { const serverId = currentServer.id; - const token = currentServer.ndCredential; - console.error(`token is expired: ${token}`); useAuthStore.getState().actions.updateServer(serverId, { ndCredential: undefined }); useAuthStore.getState().actions.setCurrentServer(null); } diff --git a/src/renderer/features/login/routes/login-route.tsx b/src/renderer/features/login/routes/login-route.tsx index f82c920a2..eaa35b136 100644 --- a/src/renderer/features/login/routes/login-route.tsx +++ b/src/renderer/features/login/routes/login-route.tsx @@ -18,6 +18,7 @@ import { AnimatedPage } from '/@/renderer/features/shared/components/animated-pa import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { AppRoute } from '/@/renderer/router/routes'; import { useAuthStoreActions, useCurrentServer } from '/@/renderer/store'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { Button } from '/@/shared/components/button/button'; import { Center } from '/@/shared/components/center/center'; import { Code } from '/@/shared/components/code/code'; @@ -136,6 +137,10 @@ const LoginRoute = () => { ); if (!data) { + logFn.error('Login failed (no data returned)', { + category: LogCategory.SYSTEM, + meta: { serverName, serverType, serverUrl }, + }); return toast.error({ message: t('error.authenticationFailed', { postProcess: 'sentenceCase' }), }); @@ -159,6 +164,10 @@ const LoginRoute = () => { addServer(serverItem); setCurrentServer(serverItem); + logFn.info('Login successful', { + category: LogCategory.SYSTEM, + meta: { serverName, serverType, serverUrl, userId: data.userId }, + }); toast.success({ message: t('form.addServer.success', { postProcess: 'sentenceCase' }), }); @@ -175,6 +184,10 @@ const LoginRoute = () => { } } } catch (err: any) { + logFn.error('Login failed', { + category: LogCategory.SYSTEM, + meta: { message: err?.message, serverName, serverType, serverUrl }, + }); setIsLoading(false); return toast.error({ message: err?.message }); } diff --git a/src/renderer/features/playlists/mutations/add-to-playlist-mutation.ts b/src/renderer/features/playlists/mutations/add-to-playlist-mutation.ts index 41f8c43d4..cfae3d04f 100644 --- a/src/renderer/features/playlists/mutations/add-to-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/add-to-playlist-mutation.ts @@ -6,6 +6,7 @@ import { queryKeys } from '/@/renderer/api/query-keys'; import { useRecentPlaylists } from '/@/renderer/features/playlists/hooks/use-recent-playlists'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; import { useCurrentServerId } from '/@/renderer/store'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { AddToPlaylistArgs, AddToPlaylistResponse } from '/@/shared/types/domain-types'; export const useAddToPlaylist = (args: MutationHookArgs) => { @@ -22,6 +23,17 @@ export const useAddToPlaylist = (args: MutationHookArgs) => { apiClientProps: { serverId: args.apiClientProps.serverId }, }); }, + onError: (error, variables) => { + logFn.error('Add to playlist failed', { + category: LogCategory.API, + meta: { + message: error?.message, + playlistId: variables.query.id, + serverId: variables.apiClientProps.serverId, + }, + }); + options?.onError?.(error); + }, onSuccess: (_data, variables, context) => { const { apiClientProps } = variables; const serverId = apiClientProps.serverId; diff --git a/src/renderer/features/playlists/mutations/create-playlist-mutation.ts b/src/renderer/features/playlists/mutations/create-playlist-mutation.ts index c64e6fb21..e43f9e2d0 100644 --- a/src/renderer/features/playlists/mutations/create-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/create-playlist-mutation.ts @@ -4,6 +4,7 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { CreatePlaylistArgs, CreatePlaylistResponse } from '/@/shared/types/domain-types'; export const useCreatePlaylist = (args: MutationHookArgs) => { @@ -17,6 +18,16 @@ export const useCreatePlaylist = (args: MutationHookArgs) => { apiClientProps: { serverId: args.apiClientProps.serverId }, }); }, + onError: (error, variables) => { + logFn.error('Create playlist failed', { + category: LogCategory.API, + meta: { + message: error?.message, + serverId: variables.apiClientProps.serverId, + }, + }); + options?.onError?.(error); + }, onSuccess: (_args, variables) => { queryClient.invalidateQueries({ exact: false, diff --git a/src/renderer/features/playlists/mutations/delete-playlist-mutation.ts b/src/renderer/features/playlists/mutations/delete-playlist-mutation.ts index e1999070f..00c7c36d4 100644 --- a/src/renderer/features/playlists/mutations/delete-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/delete-playlist-mutation.ts @@ -9,6 +9,7 @@ import { restorePlaylistQueryData, } from '/@/renderer/features/playlists/mutations/playlist-optimistic-updates'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { DeletePlaylistArgs, DeletePlaylistResponse } from '/@/shared/types/domain-types'; export const useDeletePlaylist = (args: MutationHookArgs) => { @@ -24,6 +25,14 @@ export const useDeletePlaylist = (args: MutationHookArgs) => { }); }, onError: (_error, _variables, context) => { + logFn.error('Delete playlist failed', { + category: LogCategory.API, + meta: { + message: _error?.message, + playlistId: _variables.query.id, + serverId: _variables.apiClientProps.serverId, + }, + }); if (context) { restorePlaylistQueryData(queryClient, context); } diff --git a/src/renderer/features/playlists/mutations/remove-from-playlist-mutation.ts b/src/renderer/features/playlists/mutations/remove-from-playlist-mutation.ts index 084d434bb..c30345ecc 100644 --- a/src/renderer/features/playlists/mutations/remove-from-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/remove-from-playlist-mutation.ts @@ -4,6 +4,7 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationOptions } from '/@/renderer/lib/react-query'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { RemoveFromPlaylistArgs, RemoveFromPlaylistResponse } from '/@/shared/types/domain-types'; export const useRemoveFromPlaylist = (options?: MutationOptions) => { @@ -16,6 +17,17 @@ export const useRemoveFromPlaylist = (options?: MutationOptions) => { apiClientProps: { serverId: args.apiClientProps.serverId }, }); }, + onError: (error, variables) => { + logFn.error('Remove from playlist failed', { + category: LogCategory.API, + meta: { + message: error?.message, + playlistId: variables.query.id, + serverId: variables.apiClientProps.serverId, + }, + }); + options?.onError?.(error); + }, onSuccess: (_data, variables) => { const { apiClientProps } = variables; const serverId = apiClientProps.serverId; diff --git a/src/renderer/features/playlists/mutations/replace-playlist-mutation.ts b/src/renderer/features/playlists/mutations/replace-playlist-mutation.ts index b1cbb1611..a9ed85182 100644 --- a/src/renderer/features/playlists/mutations/replace-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/replace-playlist-mutation.ts @@ -6,6 +6,7 @@ import { queryKeys } from '/@/renderer/api/query-keys'; import { useRecentPlaylists } from '/@/renderer/features/playlists/hooks/use-recent-playlists'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; import { useCurrentServerId } from '/@/renderer/store'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { ReplacePlaylistArgs, ReplacePlaylistResponse } from '/@/shared/types/domain-types'; export const useReplacePlaylist = (args: MutationHookArgs) => { @@ -22,6 +23,17 @@ export const useReplacePlaylist = (args: MutationHookArgs) => { apiClientProps: { serverId: args.apiClientProps.serverId }, }); }, + onError: (error, variables) => { + logFn.error('Replace playlist failed', { + category: LogCategory.API, + meta: { + message: error?.message, + playlistId: variables.query.id, + serverId: variables.apiClientProps.serverId, + }, + }); + options?.onError?.(error); + }, onSuccess: (_data, variables, context) => { const { apiClientProps } = variables; const serverId = apiClientProps.serverId; diff --git a/src/renderer/features/playlists/mutations/update-playlist-mutation.ts b/src/renderer/features/playlists/mutations/update-playlist-mutation.ts index fbf9ba398..6e0136362 100644 --- a/src/renderer/features/playlists/mutations/update-playlist-mutation.ts +++ b/src/renderer/features/playlists/mutations/update-playlist-mutation.ts @@ -4,6 +4,7 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { UpdatePlaylistArgs, UpdatePlaylistResponse } from '/@/shared/types/domain-types'; export const useUpdatePlaylist = (args: MutationHookArgs) => { @@ -17,6 +18,17 @@ export const useUpdatePlaylist = (args: MutationHookArgs) => { apiClientProps: { serverId: args.apiClientProps.serverId }, }); }, + onError: (error, variables) => { + logFn.error('Update playlist failed', { + category: LogCategory.API, + meta: { + message: error?.message, + playlistId: variables.query?.id, + serverId: variables.apiClientProps.serverId, + }, + }); + options?.onError?.(error); + }, onSuccess: (_data, variables) => { const { apiClientProps, query } = variables; const serverId = apiClientProps.serverId; diff --git a/src/renderer/features/radio/components/edit-radio-station-form.tsx b/src/renderer/features/radio/components/edit-radio-station-form.tsx index bfe3393bd..c87bf6ead 100644 --- a/src/renderer/features/radio/components/edit-radio-station-form.tsx +++ b/src/renderer/features/radio/components/edit-radio-station-form.tsx @@ -48,9 +48,9 @@ export const EditRadioStationForm = ({ onCancel, station }: EditRadioStationForm { onError: (error) => { logFn.error('An error occurred', { - category: LogCategory.OTHER, - meta: { error: error as Error }, - }); + category: LogCategory.OTHER, + meta: { error: error as Error }, + }); toast.error({ message: (error as Error).message, diff --git a/src/renderer/features/radio/mutations/create-radio-station-mutation.ts b/src/renderer/features/radio/mutations/create-radio-station-mutation.ts index e9bb887b3..633ac6a0d 100644 --- a/src/renderer/features/radio/mutations/create-radio-station-mutation.ts +++ b/src/renderer/features/radio/mutations/create-radio-station-mutation.ts @@ -4,6 +4,7 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { CreateInternetRadioStationArgs, CreateInternetRadioStationResponse, @@ -25,6 +26,16 @@ export const useCreateRadioStation = (args: MutationHookArgs) => { apiClientProps: { serverId: args.apiClientProps.serverId }, }); }, + onError: (error, variables) => { + logFn.error('Create radio station failed', { + category: LogCategory.API, + meta: { + message: error?.message, + serverId: variables.apiClientProps.serverId, + }, + }); + options?.onError?.(error); + }, onSuccess: (_args, variables) => { queryClient.invalidateQueries({ exact: false, diff --git a/src/renderer/features/radio/mutations/delete-radio-station-mutation.ts b/src/renderer/features/radio/mutations/delete-radio-station-mutation.ts index 7d46a8240..a773c6cf4 100644 --- a/src/renderer/features/radio/mutations/delete-radio-station-mutation.ts +++ b/src/renderer/features/radio/mutations/delete-radio-station-mutation.ts @@ -4,6 +4,7 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { DeleteInternetRadioStationArgs, DeleteInternetRadioStationResponse, @@ -25,6 +26,17 @@ export const useDeleteRadioStation = (args: MutationHookArgs) => { apiClientProps: { serverId: args.apiClientProps.serverId }, }); }, + onError: (error, variables) => { + logFn.error('Delete radio station failed', { + category: LogCategory.API, + meta: { + message: error?.message, + serverId: variables.apiClientProps.serverId, + stationId: variables.query?.id, + }, + }); + options?.onError?.(error); + }, onSuccess: (_args, variables) => { queryClient.invalidateQueries({ exact: false, diff --git a/src/renderer/features/radio/mutations/update-radio-station-mutation.ts b/src/renderer/features/radio/mutations/update-radio-station-mutation.ts index a4fda2935..b8388c168 100644 --- a/src/renderer/features/radio/mutations/update-radio-station-mutation.ts +++ b/src/renderer/features/radio/mutations/update-radio-station-mutation.ts @@ -4,6 +4,7 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { UpdateInternetRadioStationArgs, UpdateInternetRadioStationResponse, @@ -25,6 +26,17 @@ export const useUpdateRadioStation = (args: MutationHookArgs) => { apiClientProps: { serverId: args.apiClientProps.serverId }, }); }, + onError: (error, variables) => { + logFn.error('Update radio station failed', { + category: LogCategory.API, + meta: { + message: error?.message, + serverId: variables.apiClientProps.serverId, + stationId: variables.query?.id, + }, + }); + options?.onError?.(error); + }, onSuccess: (_args, variables) => { queryClient.invalidateQueries({ exact: false, diff --git a/src/renderer/features/servers/components/add-server-form.tsx b/src/renderer/features/servers/components/add-server-form.tsx index 1e23caac6..d6f192458 100644 --- a/src/renderer/features/servers/components/add-server-form.tsx +++ b/src/renderer/features/servers/components/add-server-form.tsx @@ -14,6 +14,7 @@ import NavidromeIcon from '/@/renderer/features/servers/assets/navidrome.png'; import SubsonicIcon from '/@/renderer/features/servers/assets/opensubsonic.png'; import { IgnoreCorsSslSwitches } from '/@/renderer/features/servers/components/ignore-cors-ssl-switches'; import { useAuthStoreActions } from '/@/renderer/store'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { Checkbox } from '/@/shared/components/checkbox/checkbox'; import { Divider } from '/@/shared/components/divider/divider'; import { Group } from '/@/shared/components/group/group'; @@ -149,6 +150,10 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => { ); if (!data) { + logFn.error('Add server failed (no data returned)', { + category: LogCategory.SYSTEM, + meta: { name: values.name, serverType: values.type, url: values.url }, + }); return toast.error({ message: t('error.authenticationFailed', { postProcess: 'sentenceCase' }), }); @@ -189,6 +194,10 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => { setCurrentServer(serverItem); closeAllModals(); + logFn.info('Add server successful', { + category: LogCategory.SYSTEM, + meta: { name: values.name, serverId: serverItem.id, serverType: values.type, url: values.url }, + }); toast.success({ message: t('form.addServer.success', { postProcess: 'sentenceCase' }), }); @@ -205,6 +214,10 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => { } } } catch (err: any) { + logFn.error('Add server failed', { + category: LogCategory.SYSTEM, + meta: { message: err?.message, name: values.name, serverType: values.type, url: values.url }, + }); setIsLoading(false); return toast.error({ message: err?.message }); } diff --git a/src/renderer/features/shared/components/component-error-boundary.tsx b/src/renderer/features/shared/components/component-error-boundary.tsx index 4d48fc19b..1462c3c77 100644 --- a/src/renderer/features/shared/components/component-error-boundary.tsx +++ b/src/renderer/features/shared/components/component-error-boundary.tsx @@ -1,6 +1,7 @@ import { ErrorBoundary } from 'react-error-boundary'; import { useTranslation } from 'react-i18next'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { Box } from '/@/shared/components/box/box'; import { Button } from '/@/shared/components/button/button'; import { Center } from '/@/shared/components/center/center'; @@ -43,5 +44,22 @@ interface ComponentErrorBoundaryProps { } export const ComponentErrorBoundary = ({ children }: ComponentErrorBoundaryProps) => { - return {children}; + return ( + { + logFn.error('Component error boundary caught an error', { + category: LogCategory.OTHER, + meta: { + componentStack: errorInfo?.componentStack, + message: error?.message, + name: error?.name, + stack: error?.stack, + }, + }); + }} + > + {children} + + ); }; diff --git a/src/renderer/features/shared/components/page-error-boundary.tsx b/src/renderer/features/shared/components/page-error-boundary.tsx index b2bfd150d..669bf59c5 100644 --- a/src/renderer/features/shared/components/page-error-boundary.tsx +++ b/src/renderer/features/shared/components/page-error-boundary.tsx @@ -2,6 +2,7 @@ import { ErrorBoundary } from 'react-error-boundary'; import { useTranslation } from 'react-i18next'; import { ServerSelector } from '/@/renderer/features/sidebar/components/server-selector'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { Box } from '/@/shared/components/box/box'; import { Button } from '/@/shared/components/button/button'; import { Center } from '/@/shared/components/center/center'; @@ -85,9 +86,15 @@ export const PageErrorBoundary = ({ children }: PageErrorBoundaryProps) => { { - if (process.env.NODE_ENV === 'development') { - console.error('Page error boundary caught an error:', error, errorInfo); - } + logFn.error('Page error boundary caught an error', { + category: LogCategory.OTHER, + meta: { + componentStack: errorInfo?.componentStack, + message: error?.message, + name: error?.name, + stack: error?.stack, + }, + }); }} onReset={() => {}} > diff --git a/src/renderer/features/shared/components/router-error-boundary.tsx b/src/renderer/features/shared/components/router-error-boundary.tsx index 770b8259a..843fd5b6f 100644 --- a/src/renderer/features/shared/components/router-error-boundary.tsx +++ b/src/renderer/features/shared/components/router-error-boundary.tsx @@ -2,6 +2,7 @@ import { ErrorBoundary } from 'react-error-boundary'; import { useTranslation } from 'react-i18next'; import { ServerSelector } from '/@/renderer/features/sidebar/components/server-selector'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { Box } from '/@/shared/components/box/box'; import { Button } from '/@/shared/components/button/button'; import { Center } from '/@/shared/components/center/center'; @@ -91,9 +92,15 @@ export const RouterErrorBoundary = ({ children }: RouterErrorBoundaryProps) => { { - if (process.env.NODE_ENV === 'development') { - console.error('Root error boundary caught an error:', error, errorInfo); - } + logFn.error('Router error boundary caught an error', { + category: LogCategory.OTHER, + meta: { + componentStack: errorInfo?.componentStack, + message: error?.message, + name: error?.name, + stack: error?.stack, + }, + }); }} onReset={() => {}} > diff --git a/src/renderer/features/shared/mutations/create-favorite-mutation.ts b/src/renderer/features/shared/mutations/create-favorite-mutation.ts index d414f6da0..4186eea46 100644 --- a/src/renderer/features/shared/mutations/create-favorite-mutation.ts +++ b/src/renderer/features/shared/mutations/create-favorite-mutation.ts @@ -12,6 +12,7 @@ import { restoreFavoriteQueryData, } from '/@/renderer/features/shared/mutations/favorite-optimistic-updates'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { toast } from '/@/shared/components/toast/toast'; import { FavoriteArgs, FavoriteResponse, LibraryItem } from '/@/shared/types/domain-types'; @@ -33,6 +34,15 @@ export const useCreateFavorite = (args: MutationHookArgs) => { }, mutationKey: createFavoriteMutationKey, onError: (_error, variables, context) => { + logFn.error('Create favorite failed', { + category: LogCategory.API, + meta: { + id: variables.query.id, + message: _error?.message, + serverId: variables.apiClientProps.serverId, + type: variables.query.type, + }, + }); if (context) { restoreFavoriteQueryData(queryClient, context); } diff --git a/src/renderer/features/shared/mutations/delete-favorite-mutation.ts b/src/renderer/features/shared/mutations/delete-favorite-mutation.ts index 909f23ee8..e5cf4e670 100644 --- a/src/renderer/features/shared/mutations/delete-favorite-mutation.ts +++ b/src/renderer/features/shared/mutations/delete-favorite-mutation.ts @@ -12,6 +12,7 @@ import { restoreFavoriteQueryData, } from '/@/renderer/features/shared/mutations/favorite-optimistic-updates'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { toast } from '/@/shared/components/toast/toast'; import { FavoriteArgs, FavoriteResponse, LibraryItem } from '/@/shared/types/domain-types'; @@ -33,6 +34,15 @@ export const useDeleteFavorite = (args: MutationHookArgs) => { }, mutationKey: deleteFavoriteMutationKey, onError: (_error, _variables, context) => { + logFn.error('Delete favorite failed', { + category: LogCategory.API, + meta: { + id: _variables.query.id, + message: _error?.message, + serverId: _variables.apiClientProps.serverId, + type: _variables.query.type, + }, + }); if (context) { restoreFavoriteQueryData(queryClient, context); } diff --git a/src/renderer/features/shared/mutations/set-rating-mutation.ts b/src/renderer/features/shared/mutations/set-rating-mutation.ts index 6f50e07b6..f6b57bc0a 100644 --- a/src/renderer/features/shared/mutations/set-rating-mutation.ts +++ b/src/renderer/features/shared/mutations/set-rating-mutation.ts @@ -11,6 +11,7 @@ import { restoreRatingQueryData, } from '/@/renderer/features/shared/mutations/rating-optimistic-updates'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { toast } from '/@/shared/components/toast/toast'; import { LibraryItem, RatingResponse, SetRatingArgs } from '/@/shared/types/domain-types'; @@ -30,6 +31,16 @@ export const useSetRatingMutation = (args: MutationHookArgs) => { }, mutationKey: setRatingMutationKey, onError: (_error, _variables, context) => { + logFn.error('Set rating failed', { + category: LogCategory.API, + meta: { + id: _variables.query.id, + message: _error?.message, + rating: _variables.query.rating, + serverId: _variables.apiClientProps.serverId, + type: _variables.query.type, + }, + }); if (context) { restoreRatingQueryData(queryClient, context); } diff --git a/src/renderer/features/sharing/mutations/share-item-mutation.ts b/src/renderer/features/sharing/mutations/share-item-mutation.ts index 85d63439a..2fa8b8186 100644 --- a/src/renderer/features/sharing/mutations/share-item-mutation.ts +++ b/src/renderer/features/sharing/mutations/share-item-mutation.ts @@ -3,6 +3,7 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { AnyLibraryItems, ShareItemArgs, ShareItemResponse } from '/@/shared/types/domain-types'; export const useShareItem = (args: MutationHookArgs) => { @@ -20,6 +21,17 @@ export const useShareItem = (args: MutationHookArgs) => { apiClientProps: { serverId: args.apiClientProps.serverId }, }); }, + onError: (error, variables) => { + logFn.error('Share item failed', { + category: LogCategory.API, + meta: { + itemType: variables.body?.resourceType, + message: error?.message, + serverId: variables.apiClientProps.serverId, + }, + }); + options?.onError?.(error); + }, retry: false, ...options, }); diff --git a/src/renderer/hooks/use-check-for-updates.ts b/src/renderer/hooks/use-check-for-updates.ts index 6e3f7a239..0b48111fa 100644 --- a/src/renderer/hooks/use-check-for-updates.ts +++ b/src/renderer/hooks/use-check-for-updates.ts @@ -2,6 +2,8 @@ import { useQuery } from '@tanstack/react-query'; import isElectron from 'is-electron'; import { useEffect, useState } from 'react'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; + const CHECK_FOR_UPDATES_INTERVAL_MS = 6 * 60 * 60 * 1000; const utils = isElectron() ? window.api?.utils : null; @@ -21,7 +23,17 @@ export const useCheckForUpdates = () => { return useQuery({ enabled: isEnabled, - queryFn: () => utils?.checkForUpdates?.(), + queryFn: async () => { + const result = await utils?.checkForUpdates?.(); + logFn.info('Check for updates completed', { + category: LogCategory.SYSTEM, + meta: { + updateAvailable: result?.updateAvailable ?? false, + version: result?.version, + }, + }); + return result; + }, queryKey: ['app-check-for-updates'], refetchInterval: CHECK_FOR_UPDATES_INTERVAL_MS, refetchIntervalInBackground: true, diff --git a/src/renderer/hooks/use-server-authenticated.ts b/src/renderer/hooks/use-server-authenticated.ts index cae8ebf54..c50e97e0a 100644 --- a/src/renderer/hooks/use-server-authenticated.ts +++ b/src/renderer/hooks/use-server-authenticated.ts @@ -345,7 +345,16 @@ export const useServerAuthenticated = () => { const debouncedAuth = debounce( (serverWithAuth: NonNullable>) => { - authenticateServer(serverWithAuth).catch(console.error); + authenticateServer(serverWithAuth).catch((err) => { + logFn.error('Server authentication failed (debounced)', { + category: LogCategory.SYSTEM, + meta: { + message: (err as Error)?.message, + serverId: serverWithAuth.id, + serverName: serverWithAuth.name, + }, + }); + }); }, 300, ); diff --git a/src/renderer/lib/react-query.ts b/src/renderer/lib/react-query.ts index 3c6463a41..efee20044 100644 --- a/src/renderer/lib/react-query.ts +++ b/src/renderer/lib/react-query.ts @@ -8,12 +8,19 @@ import type { import { QueryCache, QueryClient } from '@tanstack/react-query'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { toast } from '/@/shared/components/toast/toast'; const queryCache = new QueryCache({ onError: (error: any, query) => { + logFn.error('Query failed', { + category: LogCategory.API, + meta: { + message: error?.message, + queryKey: query.queryKey, + }, + }); if (query.state.data !== undefined) { - console.error(error); toast.show({ message: `${error.message}`, type: 'error' }); } }, diff --git a/src/renderer/release-notes-modal.tsx b/src/renderer/release-notes-modal.tsx index 00b0d877f..4ead65689 100644 --- a/src/renderer/release-notes-modal.tsx +++ b/src/renderer/release-notes-modal.tsx @@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next'; import packageJson from '../../package.json'; import { formatHrDateTime } from '/@/renderer/utils/format'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { Button } from '/@/shared/components/button/button'; import { Center } from '/@/shared/components/center/center'; import { Group } from '/@/shared/components/group/group'; @@ -70,10 +71,22 @@ const ReleaseNotesContent = ({ onDismiss, version }: ReleaseNotesContentProps) = // Fetch list of recent releases for the selector const { data: releasesList = [] } = useQuery({ queryFn: async () => { - const response = await axios.get(GITHUB_RELEASES_URL, { - params: { per_page: RELEASES_TO_FETCH }, - }); - return response.data; + try { + const response = await axios.get(GITHUB_RELEASES_URL, { + params: { per_page: RELEASES_TO_FETCH }, + }); + logFn.info('Release notes fetched', { + category: LogCategory.GENERAL, + meta: { count: response.data?.length ?? 0 }, + }); + return response.data; + } catch (error) { + logFn.error('Release notes fetch failed', { + category: LogCategory.GENERAL, + meta: { message: (error as Error)?.message }, + }); + throw error; + } }, queryKey: ['github-releases-list'], retry: 2, diff --git a/src/renderer/store/auth.store.ts b/src/renderer/store/auth.store.ts index 09dc25e6a..af911eeb4 100644 --- a/src/renderer/store/auth.store.ts +++ b/src/renderer/store/auth.store.ts @@ -5,6 +5,7 @@ import { immer } from 'zustand/middleware/immer'; import { shallow } from 'zustand/shallow'; import { createWithEqualityFn } from 'zustand/traditional'; +import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { ServerListItem, ServerListItemWithCredential } from '/@/shared/types/domain-types'; export interface AuthSlice extends AuthState { @@ -30,6 +31,16 @@ export const useAuthStore = createWithEqualityFn()( immer((set, get) => ({ actions: { addServer: (args) => { + if (process.env.NODE_ENV === 'development') { + logFn.debug('Auth store: add server', { + category: LogCategory.SYSTEM, + meta: { + serverId: args.id, + serverName: args.name, + serverType: args.type, + }, + }); + } set((state) => { state.serverList[args.id] = args; }); @@ -49,6 +60,15 @@ export const useAuthStore = createWithEqualityFn()( return null; }, setCurrentServer: (server) => { + if (process.env.NODE_ENV === 'development') { + logFn.debug('Auth store: set current server', { + category: LogCategory.SYSTEM, + meta: { + serverId: server?.id ?? null, + serverName: server?.name ?? null, + }, + }); + } set((state) => { state.currentServer = server; }); @@ -65,6 +85,12 @@ export const useAuthStore = createWithEqualityFn()( }); }, updateServer: (id: string, args: Partial) => { + if (process.env.NODE_ENV === 'development') { + logFn.debug('Auth store: update server', { + category: LogCategory.SYSTEM, + meta: { keys: Object.keys(args || {}), serverId: id }, + }); + } set((state) => { const updatedServer = { ...state.serverList[id],