better logger util

This commit is contained in:
lone-cloud 2025-08-31 14:25:27 -07:00
parent 4420a62395
commit 1298debaa8
17 changed files with 130 additions and 167 deletions

View file

@ -11,7 +11,7 @@ import { ScreenTransition } from '@/components/ScreenTransition';
import { TitleBar } from '@/components/TitleBar';
import { useUpdateChecker } from '@/hooks/useUpdateChecker';
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
import { Logger } from '@/utils/logger';
import { safeExecute } from '@/utils/logger';
import { TITLEBAR_HEIGHT } from '@/constants';
import type { DownloadItem } from '@/types/electron';
import type { InterfaceTab, FrontendPreference, Screen } from '@/types';
@ -46,7 +46,7 @@ export const App = () => {
useEffect(() => {
const checkInstallation = async () => {
await Logger.safeExecute(async () => {
await safeExecute(async () => {
const [currentVersion, hasSeenWelcome, preference] = await Promise.all([
window.electronAPI.kobold.getCurrentVersion(),
window.electronAPI.config.get('hasSeenWelcome') as Promise<boolean>,
@ -80,7 +80,7 @@ export const App = () => {
}, []);
const handleBinaryUpdate = async (download: DownloadItem) => {
await Logger.safeExecute(async () => {
await safeExecute(async () => {
const success = await sharedHandleDownload({
item: download,
isUpdate: true,
@ -94,7 +94,7 @@ export const App = () => {
};
const handleDownloadComplete = async () => {
await Logger.safeExecute(async () => {
await safeExecute(async () => {
await window.electronAPI.kobold.getCurrentVersion();
}, 'Error refreshing versions after download:');
@ -241,7 +241,7 @@ export const App = () => {
opened={settingsOpened}
onClose={async () => {
setSettingsOpened(false);
const preference = await Logger.safeExecute(
const preference = await safeExecute(
() =>
window.electronAPI.config.get(
'frontendPreference'

View file

@ -14,7 +14,7 @@ import { useState } from 'react';
import type { InstalledVersion, DownloadItem } from '@/types/electron';
import { getDisplayNameFromPath } from '@/utils/version';
import { GITHUB_API } from '@/constants';
import { Logger } from '@/utils/logger';
import { safeExecute } from '@/utils/logger';
interface UpdateAvailableModalProps {
opened: boolean;
@ -39,7 +39,7 @@ export const UpdateAvailableModal = ({
const handleUpdate = async () => {
setIsUpdating(true);
await Logger.safeExecute(async () => {
await safeExecute(async () => {
await onUpdate(availableUpdate);
onClose();
}, 'Failed to update:');

View file

@ -5,7 +5,7 @@ import { getPlatformDisplayName } from '@/utils/platform';
import { formatDownloadSize } from '@/utils/download';
import { getAssetDescription, sortDownloadsByType } from '@/utils/assets';
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
import { Logger } from '@/utils/logger';
import { safeExecute } from '@/utils/logger';
import type { DownloadItem } from '@/types/electron';
interface DownloadScreenProps {
@ -34,7 +34,7 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
async (download: DownloadItem) => {
setDownloadingAsset(download.name);
await Logger.safeExecute(async () => {
await safeExecute(async () => {
const success = await sharedHandleDownload({
item: download,
isUpdate: false,

View file

@ -3,7 +3,7 @@ import { useState, useEffect } from 'react';
import { InfoTooltip } from '@/components/InfoTooltip';
import { CheckboxWithTooltip } from '@/components/CheckboxWithTooltip';
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
import { Logger } from '@/utils/logger';
import { safeExecute } from '@/utils/logger';
export const AdvancedTab = () => {
const {
@ -39,7 +39,7 @@ export const AdvancedTab = () => {
useEffect(() => {
const detectBackendSupport = async () => {
const support = await Logger.safeExecute(
const support = await safeExecute(
() => window.electronAPI.kobold.detectBackendSupport(),
'Failed to detect backend support:'
);

View file

@ -4,7 +4,7 @@ import { InfoTooltip } from '@/components/InfoTooltip';
import { BackendSelectItem } from '@/components/screens/Launch/GeneralTab/BackendSelectItem';
import { GpuDeviceSelector } from '@/components/screens/Launch/GeneralTab/GpuDeviceSelector';
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
import { Logger } from '@/utils/logger';
import { safeExecute } from '@/utils/logger';
import type { BackendOption } from '@/types';
export const BackendSelector = () => {
@ -26,7 +26,7 @@ export const BackendSelector = () => {
useEffect(() => {
const loadBackends = async () => {
setIsLoadingBackends(true);
const backends = await Logger.safeExecute(
const backends = await safeExecute(
() => window.electronAPI.kobold.getAvailableBackends(true),
'Failed to detect available backends:'
);

View file

@ -1,5 +1,6 @@
import { Card, Container, Stack, Tabs, Group, Button } from '@mantine/core';
import { useState, useEffect, useCallback, useRef } from 'react';
import { tryExecute, error, safeExecute } from '@/utils/logger';
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
import { useLaunchLogic } from '@/hooks/useLaunchLogic';
import { useWarnings } from '@/hooks/useWarnings';
@ -10,7 +11,6 @@ import { ImageGenerationTab } from '@/components/screens/Launch/ImageGenerationT
import { WarningDisplay } from '@/components/WarningDisplay';
import { ConfigFileManager } from '@/components/screens/Launch/ConfigFileManager';
import { DEFAULT_MODEL_URL } from '@/constants';
import { Logger } from '@/utils/logger';
import type { ConfigFile } from '@/types';
interface LaunchScreenProps {
@ -86,7 +86,7 @@ export const LaunchScreen = ({
});
const setHappyDefaults = useCallback(async () => {
const backends = await Logger.safeExecute(
const backends = await safeExecute(
() => window.electronAPI.kobold.getAvailableBackends(),
'Failed to set defaults:'
);
@ -98,7 +98,7 @@ export const LaunchScreen = ({
const setInitialDefaults = useCallback(
async (currentModelPath: string, currentSdModel: string) => {
await Logger.tryExecute(async () => {
await tryExecute(async () => {
if (
!defaultsSetRef.current &&
!currentModelPath.trim() &&
@ -190,7 +190,7 @@ export const LaunchScreen = ({
});
const handleCreateNewConfig = async (configName: string) => {
await Logger.safeExecute(async () => {
await safeExecute(async () => {
const fullConfigName = `${configName}.json`;
const saveSuccess = await window.electronAPI.kobold.saveConfigFile(
fullConfigName,
@ -202,7 +202,7 @@ export const LaunchScreen = ({
setSelectedFile(fullConfigName);
await window.electronAPI.kobold.setSelectedConfig(fullConfigName);
} else {
Logger.error(
error(
'Failed to create new configuration',
new Error('Save operation failed')
);
@ -212,21 +212,21 @@ export const LaunchScreen = ({
const handleSaveConfig = async () => {
if (!selectedFile) {
Logger.error(
error(
'No configuration file selected for saving',
new Error('Selected file is null')
);
return false;
}
const success = await Logger.safeExecute(async () => {
const success = await safeExecute(async () => {
const saveSuccess = await window.electronAPI.kobold.saveConfigFile(
selectedFile,
buildConfigData()
);
if (!saveSuccess) {
Logger.error(
error(
'Failed to save configuration',
new Error('Save operation failed')
);

View file

@ -12,6 +12,7 @@ import {
rem,
} from '@mantine/core';
import { Github, FolderOpen } from 'lucide-react';
import { safeExecute } from '@/utils/logger';
import type { VersionInfo } from '@/types/electron';
import { PRODUCT_NAME } from '@/constants';
import iconUrl from '/icon.png';
@ -19,14 +20,12 @@ export const AboutTab = () => {
const [versionInfo, setVersionInfo] = useState<VersionInfo | null>(null);
useEffect(() => {
const loadVersionInfo = async () => {
try {
const info = await window.electronAPI.app.getVersionInfo();
const info = await safeExecute(
() => window.electronAPI.app.getVersionInfo(),
'Failed to load version info'
);
if (info) {
setVersionInfo(info);
} catch (error) {
window.electronAPI.logs.logError(
'Failed to load version info',
error as Error
);
}
};
loadVersionInfo();
@ -106,14 +105,10 @@ export const AboutTab = () => {
<FolderOpen style={{ width: rem(16), height: rem(16) }} />
}
onClick={async () => {
try {
await window.electronAPI.app.showLogsFolder();
} catch (error) {
window.electronAPI.logs.logError(
'Failed to open logs folder',
error as Error
);
}
await safeExecute(
() => window.electronAPI.app.showLogsFolder(),
'Failed to open logs folder'
);
}}
>
Show Logs

View file

@ -1,4 +1,5 @@
import { useState, useEffect } from 'react';
import { tryExecute, safeExecute } from '@/utils/logger';
import {
Stack,
Text,
@ -13,7 +14,6 @@ import { Folder, FolderOpen, Monitor } from 'lucide-react';
import styles from '@/styles/layout.module.css';
import type { FrontendPreference } from '@/types';
import { FRONTENDS } from '@/constants';
import { Logger } from '@/utils/logger';
export const GeneralTab = () => {
const [installDir, setInstallDir] = useState<string>('');
@ -30,7 +30,7 @@ export const GeneralTab = () => {
useEffect(() => {
const checkNpxAvailability = async () => {
const available = await Logger.safeExecute(
const available = await safeExecute(
() => window.electronAPI.sillytavern.isNpxAvailable(),
'Failed to check npx availability:'
);
@ -39,7 +39,7 @@ export const GeneralTab = () => {
setIsNpxAvailable(isAvailable);
if (!isAvailable && FrontendPreference === 'sillytavern') {
await Logger.tryExecute(
await tryExecute(
() =>
window.electronAPI.config.set('frontendPreference', 'koboldcpp'),
'Failed to reset frontend preference:'
@ -54,7 +54,7 @@ export const GeneralTab = () => {
}, [FrontendPreference]);
const loadCurrentInstallDir = async () => {
const currentDir = await Logger.safeExecute(
const currentDir = await safeExecute(
() => window.electronAPI.kobold.getCurrentInstallDir(),
'Failed to load install directory:'
);
@ -64,7 +64,7 @@ export const GeneralTab = () => {
};
const loadFrontendPreference = async () => {
const frontendPreference = await Logger.safeExecute(
const frontendPreference = await safeExecute(
() => window.electronAPI.config.get('frontendPreference'),
'Failed to load frontend preference:'
);
@ -76,7 +76,7 @@ export const GeneralTab = () => {
};
const handleSelectInstallDir = async () => {
const selectedDir = await Logger.safeExecute(
const selectedDir = await safeExecute(
() => window.electronAPI.kobold.selectInstallDirectory(),
'Failed to select install directory:'
);
@ -88,7 +88,7 @@ export const GeneralTab = () => {
const handleFrontendPreferenceChange = async (value: string | null) => {
if (!value || (value !== 'koboldcpp' && value !== 'sillytavern')) return;
const success = await Logger.tryExecute(
const success = await tryExecute(
() => window.electronAPI.config.set('frontendPreference', value),
'Failed to save frontend preference:'
);

View file

@ -18,7 +18,7 @@ import {
stripAssetExtensions,
compareVersions,
} from '@/utils/version';
import { Logger } from '@/utils/logger';
import { safeExecute } from '@/utils/logger';
import { formatDownloadSize } from '@/utils/download';
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
@ -63,7 +63,7 @@ export const VersionsTab = () => {
const loadInstalledVersions = useCallback(async () => {
setLoadingInstalled(true);
await Logger.safeExecute(async () => {
await safeExecute(async () => {
const [versions, currentVersion] = await Promise.all([
window.electronAPI.kobold.getInstalledVersions(),
window.electronAPI.kobold.getCurrentVersion(),
@ -77,7 +77,7 @@ export const VersionsTab = () => {
}, []);
const loadLatestRelease = useCallback(async () => {
const release = await Logger.safeExecute(
const release = await safeExecute(
() => getLatestReleaseWithDownloadStatus(),
'Failed to load latest release:'
);
@ -174,7 +174,7 @@ export const VersionsTab = () => {
}, [downloading]);
const handleDownload = async (version: VersionInfo) => {
await Logger.safeExecute(async () => {
await safeExecute(async () => {
const download = availableDownloads.find((d) => d.name === version.name);
if (!download) {
throw new Error('Download not found');
@ -193,7 +193,7 @@ export const VersionsTab = () => {
};
const handleUpdate = async (version: VersionInfo) => {
await Logger.safeExecute(async () => {
await safeExecute(async () => {
const download = availableDownloads.find((d) => d.name === version.name);
if (!download) {
throw new Error('Download not found');
@ -214,7 +214,7 @@ export const VersionsTab = () => {
const makeCurrent = async (version: VersionInfo) => {
if (!version.installedPath) return;
await Logger.safeExecute(async () => {
await safeExecute(async () => {
const success = await window.electronAPI.kobold.setCurrentVersion(
version.installedPath!
);

View file

@ -1,4 +1,5 @@
import { useState, useCallback, useEffect } from 'react';
import { error } from '@/utils/logger';
import { compareVersions } from '@/utils/version';
import { GITHUB_API } from '@/constants';
@ -48,11 +49,8 @@ export const useAppUpdateChecker = () => {
setLastChecked(new Date());
return updateInfo;
} catch (error) {
window.electronAPI.logs.logError(
'Failed to check for app updates:',
error as Error
);
} catch (err) {
error('Failed to check for app updates:', err as Error);
return null;
} finally {
setIsChecking(false);

View file

@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from 'react';
import { error } from '@/utils/logger';
import { getROCmDownload } from '@/utils/rocm';
import { GITHUB_API } from '@/constants';
import { filterAssetsByPlatform } from '@/utils/platform';
@ -45,11 +46,8 @@ const saveToCache = (releases: DownloadItem[]) => {
timestamp: Date.now(),
};
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
} catch (error) {
window.electronAPI.logs.logError(
'Failed to save releases to cache',
error as Error
);
} catch (err) {
error('Failed to save releases to cache', err as Error);
}
};
@ -126,11 +124,8 @@ const getLatestReleaseWithDownloadStatus =
release: latestRelease,
availableAssets,
};
} catch (error) {
window.electronAPI.logs.logError(
'Failed to fetch latest release with status:',
error as Error
);
} catch (err) {
error('Failed to fetch latest release with status:', err as Error);
return null;
}
};
@ -209,12 +204,8 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
}
setAvailableDownloads(allDownloads);
} catch (error) {
window.electronAPI.logs.logError(
'Failed to load remote versions:',
error as Error
);
} catch (err) {
error('Failed to load remote versions:', err as Error);
const cached = loadFromCache();
if (cached) {
const rocm = await getROCmDownload().catch(() => null);
@ -251,11 +242,8 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
});
return result.success !== false;
} catch (error) {
window.electronAPI.logs.logError(
`Failed to download ${item.name}:`,
error as Error
);
} catch (err) {
error('Failed to download ${item.name}:', err as Error);
return false;
} finally {
setDownloading(null);

View file

@ -1,4 +1,5 @@
import { useState, useCallback } from 'react';
import { error } from '@/utils/logger';
import type { SdConvDirectMode } from '@/types';
interface UseLaunchLogicProps {
@ -285,11 +286,8 @@ export const useLaunchLogic = ({
new Error(errorMessage)
);
}
} catch (error) {
window.electronAPI.logs.logError(
'Error launching KoboldCpp:',
error as Error
);
} catch (err) {
error('Error launching KoboldCpp:', err as Error);
} finally {
setIsLaunching(false);
}

View file

@ -1,4 +1,5 @@
import { useState, useCallback, useEffect } from 'react';
import { error } from '@/utils/logger';
import {
getDisplayNameFromPath,
compareVersions,
@ -34,11 +35,8 @@ export const useUpdateChecker = () => {
setDismissedUpdates(new Set(dismissed));
}
setDismissedUpdatesLoaded(true);
} catch (error) {
window.electronAPI.logs.logError(
'Failed to load dismissed updates:',
error as Error
);
} catch (err) {
error('Failed to load dismissed updates:', err as Error);
setDismissedUpdatesLoaded(true);
}
};
@ -52,11 +50,8 @@ export const useUpdateChecker = () => {
'dismissedUpdates',
Array.from(updates)
);
} catch (error) {
window.electronAPI.logs.logError(
'Failed to save dismissed updates:',
error as Error
);
} catch (err) {
error('Failed to save dismissed updates:', err as Error);
}
}, []);
@ -110,11 +105,8 @@ export const useUpdateChecker = () => {
}
}
}
} catch (error) {
window.electronAPI.logs.logError(
'Failed to check for updates:',
error as Error
);
} catch (err) {
error('Failed to check for updates:', err as Error);
} finally {
setIsChecking(false);
}

View file

@ -1,4 +1,5 @@
import { useMemo, useEffect, useState, useCallback } from 'react';
import { safeExecute } from '@/utils/logger';
import type { BackendOption } from '@/types';
export interface Warning {
@ -100,8 +101,12 @@ const checkVramWarnings = async (backend: string): Promise<Warning[]> => {
const isGpuBackend = ['cuda', 'rocm', 'vulkan', 'clblast'].includes(backend);
if (isGpuBackend) {
try {
const gpuMemoryInfo = await window.electronAPI.kobold.detectGPUMemory();
const gpuMemoryInfo = await safeExecute(
() => window.electronAPI.kobold.detectGPUMemory(),
'Failed to detect GPU memory:'
);
if (gpuMemoryInfo) {
const lowVramGpus = gpuMemoryInfo.filter(
(gpu) =>
typeof gpu.totalMemoryMB === 'number' && gpu.totalMemoryMB < 8192
@ -120,11 +125,6 @@ const checkVramWarnings = async (backend: string): Promise<Warning[]> => {
)}). Consider using smaller models, reducing GPU layers, or enabling the "Low VRAM" option on the Advanced tab.`,
});
}
} catch (error) {
window.electronAPI.logs.logError(
'Failed to detect GPU memory:',
error as Error
);
}
}
@ -186,7 +186,7 @@ const checkBackendWarnings = async (params?: {
}): Promise<Warning[]> => {
const warnings: Warning[] = [];
try {
const result = await safeExecute(async () => {
const [backendSupport, gpuCapabilities, gpuInfo] = await Promise.all([
window.electronAPI.kobold.detectBackendSupport(),
window.electronAPI.kobold.detectGPUCapabilities(),
@ -224,13 +224,9 @@ const checkBackendWarnings = async (params?: {
}
return warnings;
} catch (error) {
window.electronAPI.logs.logError(
'Failed to check backend warnings:',
error as Error
);
return warnings;
}
}, 'Failed to check backend warnings:');
return result || warnings;
};
export const useWarnings = ({
@ -254,7 +250,7 @@ export const useWarnings = ({
return;
}
try {
const result = await safeExecute(async () => {
const [cpuCapabilitiesResult, availableBackends] = await Promise.all([
window.electronAPI.kobold.detectCPU(),
window.electronAPI.kobold.getAvailableBackends(),
@ -265,21 +261,16 @@ export const useWarnings = ({
avx2: cpuCapabilitiesResult.avx2,
};
const warnings = await checkBackendWarnings({
return checkBackendWarnings({
backend,
cpuCapabilities,
noavx2,
failsafe,
availableBackends,
});
setBackendWarnings(warnings);
} catch (error) {
window.electronAPI.logs.logError(
'Failed to check backend warnings:',
error as Error
);
setBackendWarnings([]);
}
}, 'Failed to check backend warnings:');
setBackendWarnings(result || []);
}, [backend, noavx2, failsafe]);
useEffect(() => {

View file

@ -110,6 +110,7 @@ export class WindowManager {
private setupContextMenu() {
if (!this.mainWindow) return;
// eslint-disable-next-line sonarjs/cognitive-complexity
this.mainWindow.webContents.on('context-menu', (_, params) => {
const hasLinkURL = !!params.linkURL;
const hasSelection = !!params.selectionText;

View file

@ -1,56 +1,54 @@
export class Logger {
private static logError(message: string, error: Error): void {
if (window.electronAPI?.logs?.logError) {
window.electronAPI.logs.logError(message, error);
}
const logError = (message: string, error: Error): void => {
if (window.electronAPI?.logs?.logError) {
window.electronAPI.logs.logError(message, error);
}
};
static async safeExecute<T>(
operation: () => Promise<T>,
errorMessage: string
): Promise<T | null> {
try {
return operation();
} catch (error) {
this.logError(errorMessage, error as Error);
return null;
}
export const safeExecute = async <T>(
operation: () => Promise<T>,
errorMessage: string
): Promise<T | null> => {
try {
return operation();
} catch (error) {
logError(errorMessage, error as Error);
return null;
}
};
static safeExecuteSync<T>(
operation: () => T,
errorMessage: string
): T | null {
try {
return operation();
} catch (error) {
this.logError(errorMessage, error as Error);
return null;
}
export const safeExecuteSync = <T>(
operation: () => T,
errorMessage: string
): T | null => {
try {
return operation();
} catch (error) {
logError(errorMessage, error as Error);
return null;
}
};
static async tryExecute(
operation: () => Promise<void>,
errorMessage: string
): Promise<boolean> {
try {
await operation();
return true;
} catch (error) {
this.logError(errorMessage, error as Error);
return false;
}
export const tryExecute = async (
operation: () => Promise<void>,
errorMessage: string
): Promise<boolean> => {
try {
await operation();
return true;
} catch (error) {
logError(errorMessage, error as Error);
return false;
}
};
static withErrorHandling<TArgs extends unknown[], TReturn>(
export const withErrorHandling =
<TArgs extends unknown[], TReturn>(
fn: (...args: TArgs) => Promise<TReturn>,
errorMessage: string
): (...args: TArgs) => Promise<TReturn | null> {
return async (...args: TArgs) =>
this.safeExecute(() => fn(...args), errorMessage);
}
): ((...args: TArgs) => Promise<TReturn | null>) =>
async (...args: TArgs) =>
safeExecute(() => fn(...args), errorMessage);
static error(message: string, error: Error): void {
this.logError(message, error);
}
}
export const error = (message: string, error: Error): void => {
logError(message, error);
};

View file

@ -1,3 +1,5 @@
import { error } from '@/utils/logger';
export const handleTerminalOutput = (
prevContent: string,
newData: string
@ -19,8 +21,8 @@ export const handleTerminalOutput = (
}
return prevContent + newData;
} catch (error) {
window.electronAPI.logs.logError('Terminal Basic Error', error as Error);
} catch (err) {
error('Terminal Basic Error', err as Error);
return prevContent + newData;
}
};