mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
feat(logging): add runtime log levels and debug log export (#241)
Add a System setting for Off/Normal/Debug logging, apply readable local timestamps to backend logs, and enable exporting buffered runtime logs to a file when debug mode is active. Co-authored-by: Maxim Isaev <im@friclub.ru>
This commit is contained in:
committed by
GitHub
parent
fa21379dbb
commit
3b3833007b
@@ -147,6 +147,7 @@ function AppShell() {
|
||||
const setMusicFolders = useAuthStore(s => s.setMusicFolders);
|
||||
const useCustomTitlebar = useAuthStore(s => s.useCustomTitlebar);
|
||||
const linuxWebkitKineticScroll = useAuthStore(s => s.linuxWebkitKineticScroll);
|
||||
const loggingMode = useAuthStore(s => s.loggingMode);
|
||||
const setEntityRatingSupport = useAuthStore(s => s.setEntityRatingSupport);
|
||||
const offlineAlbums = useOfflineStore(s => s.albums);
|
||||
const hasOfflineContent = Object.values(offlineAlbums).some(a => a.serverId === serverId);
|
||||
@@ -176,6 +177,10 @@ function AppShell() {
|
||||
invoke('set_linux_webkit_smooth_scrolling', { enabled: linuxWebkitKineticScroll }).catch(() => {});
|
||||
}, [linuxWebkitKineticScroll]);
|
||||
|
||||
useEffect(() => {
|
||||
invoke('set_logging_mode', { mode: loggingMode }).catch(() => {});
|
||||
}, [loggingMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoggedIn || !activeServerId) return;
|
||||
const serverAtStart = activeServerId;
|
||||
|
||||
@@ -682,6 +682,14 @@ export const deTranslation = {
|
||||
shortcutNativeFullscreen: 'Nativer Vollbildmodus',
|
||||
shortcutOpenMiniPlayer: 'Mini-Player öffnen',
|
||||
tabSystem: 'System',
|
||||
loggingTitle: 'Protokollierung',
|
||||
loggingModeDesc: 'Steuert die Ausführlichkeit der Backend-Protokolle im Terminal.',
|
||||
loggingModeOff: 'Aus',
|
||||
loggingModeNormal: 'Normal',
|
||||
loggingModeDebug: 'Debug',
|
||||
loggingExport: 'Protokolle exportieren',
|
||||
loggingExportSuccess: 'Protokolle exportiert ({{count}} Zeilen).',
|
||||
loggingExportError: 'Protokolle konnten nicht exportiert werden.',
|
||||
tabGeneral: 'Allgemein',
|
||||
ratingsSectionTitle: 'Bewertungen',
|
||||
ratingsSkipStarTitle: 'Skip → 1 Stern',
|
||||
|
||||
@@ -666,6 +666,14 @@ export const enTranslation = {
|
||||
tabServer: 'Server',
|
||||
tabUsers: 'Users',
|
||||
tabSystem: 'System',
|
||||
loggingTitle: 'Logging',
|
||||
loggingModeDesc: 'Controls backend log verbosity in the terminal.',
|
||||
loggingModeOff: 'Off',
|
||||
loggingModeNormal: 'Normal',
|
||||
loggingModeDebug: 'Debug',
|
||||
loggingExport: 'Export logs',
|
||||
loggingExportSuccess: 'Logs exported ({{count}} lines).',
|
||||
loggingExportError: 'Could not export logs.',
|
||||
tabGeneral: 'General',
|
||||
ratingsSectionTitle: 'Ratings',
|
||||
ratingsSkipStarTitle: 'Skip for 1 star',
|
||||
|
||||
@@ -657,6 +657,14 @@ export const esTranslation = {
|
||||
tabServer: 'Servidor',
|
||||
tabUsers: 'Usuarios',
|
||||
tabSystem: 'Sistema',
|
||||
loggingTitle: 'Registro',
|
||||
loggingModeDesc: 'Controla la verbosidad de los logs del backend en la terminal.',
|
||||
loggingModeOff: 'Apagado',
|
||||
loggingModeNormal: 'Normal',
|
||||
loggingModeDebug: 'Depuración',
|
||||
loggingExport: 'Exportar logs',
|
||||
loggingExportSuccess: 'Logs exportados ({{count}} líneas).',
|
||||
loggingExportError: 'No se pudieron exportar los logs.',
|
||||
tabGeneral: 'General',
|
||||
ratingsSectionTitle: 'Calificaciones',
|
||||
ratingsSkipStarTitle: 'Saltar para 1 estrella',
|
||||
|
||||
@@ -670,6 +670,14 @@ export const frTranslation = {
|
||||
shortcutNativeFullscreen: 'Plein écran natif',
|
||||
shortcutOpenMiniPlayer: 'Ouvrir le mini-lecteur',
|
||||
tabSystem: 'Système',
|
||||
loggingTitle: 'Journalisation',
|
||||
loggingModeDesc: 'Contrôle le niveau de verbosité des journaux backend dans le terminal.',
|
||||
loggingModeOff: 'Désactivé',
|
||||
loggingModeNormal: 'Normal',
|
||||
loggingModeDebug: 'Débogage',
|
||||
loggingExport: 'Exporter les journaux',
|
||||
loggingExportSuccess: 'Journaux exportés ({{count}} lignes).',
|
||||
loggingExportError: 'Impossible d\'exporter les journaux.',
|
||||
tabGeneral: 'Général',
|
||||
ratingsSectionTitle: 'Notes',
|
||||
ratingsSkipStarTitle: 'Passer pour 1 étoile',
|
||||
|
||||
@@ -651,6 +651,14 @@ export const nbTranslation = {
|
||||
tabServer: 'Tjener',
|
||||
tabUsers: 'Brukere',
|
||||
tabSystem: 'System',
|
||||
loggingTitle: 'Loggføring',
|
||||
loggingModeDesc: 'Styrer hvor detaljert backend-loggene i terminalen er.',
|
||||
loggingModeOff: 'Av',
|
||||
loggingModeNormal: 'Normal',
|
||||
loggingModeDebug: 'Debug',
|
||||
loggingExport: 'Eksporter logger',
|
||||
loggingExportSuccess: 'Logger eksportert ({{count}} linjer).',
|
||||
loggingExportError: 'Kunne ikke eksportere logger.',
|
||||
tabGeneral: 'Generelt',
|
||||
ratingsSectionTitle: 'Vurderinger',
|
||||
ratingsSkipStarTitle: 'Hopp for 1 stjerne',
|
||||
|
||||
@@ -669,6 +669,14 @@ export const nlTranslation = {
|
||||
shortcutNativeFullscreen: 'Systeemvolledig scherm',
|
||||
shortcutOpenMiniPlayer: 'Mini-speler openen',
|
||||
tabSystem: 'Systeem',
|
||||
loggingTitle: 'Logboek',
|
||||
loggingModeDesc: 'Bepaalt de uitgebreidheid van backendlogs in de terminal.',
|
||||
loggingModeOff: 'Uit',
|
||||
loggingModeNormal: 'Normaal',
|
||||
loggingModeDebug: 'Debug',
|
||||
loggingExport: 'Logs exporteren',
|
||||
loggingExportSuccess: 'Logs geëxporteerd ({{count}} regels).',
|
||||
loggingExportError: 'Kon logs niet exporteren.',
|
||||
tabGeneral: 'Algemeen',
|
||||
ratingsSectionTitle: 'Beoordelingen',
|
||||
ratingsSkipStarTitle: 'Overslaan voor 1 ster',
|
||||
|
||||
@@ -679,6 +679,14 @@ export const ruTranslation = {
|
||||
tabServer: 'Сервер',
|
||||
tabUsers: 'Пользователи',
|
||||
tabSystem: 'Система',
|
||||
loggingTitle: 'Логирование',
|
||||
loggingModeDesc: 'Управляет подробностью логов backend в терминале.',
|
||||
loggingModeOff: 'Выключено',
|
||||
loggingModeNormal: 'Обычное',
|
||||
loggingModeDebug: 'Дебаг',
|
||||
loggingExport: 'Экспорт логов',
|
||||
loggingExportSuccess: 'Логи экспортированы ({{count}} строк).',
|
||||
loggingExportError: 'Не удалось экспортировать логи.',
|
||||
tabGeneral: 'Общие',
|
||||
ratingsSectionTitle: 'Рейтинги',
|
||||
ratingsSkipStarTitle: 'Скипнуть для 1 звезды',
|
||||
|
||||
@@ -647,6 +647,14 @@ export const zhTranslation = {
|
||||
tabServer: '服务器',
|
||||
tabUsers: '用户',
|
||||
tabSystem: '系统',
|
||||
loggingTitle: '日志记录',
|
||||
loggingModeDesc: '控制终端中后端日志的详细程度。',
|
||||
loggingModeOff: '关闭',
|
||||
loggingModeNormal: '普通',
|
||||
loggingModeDebug: '调试',
|
||||
loggingExport: '导出日志',
|
||||
loggingExportSuccess: '日志已导出({{count}} 行)。',
|
||||
loggingExportError: '无法导出日志。',
|
||||
tabGeneral: '通用',
|
||||
ratingsSectionTitle: '评分',
|
||||
ratingsSkipStarTitle: '跳过以评 1 星',
|
||||
|
||||
+48
-3
@@ -22,7 +22,7 @@ import LastfmIcon from '../components/LastfmIcon';
|
||||
import CustomSelect from '../components/CustomSelect';
|
||||
import ThemePicker, { THEME_GROUPS } from '../components/ThemePicker';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useAuthStore, ServerProfile, MIX_MIN_RATING_FILTER_MAX_STARS, type SeekbarStyle, type LyricsSourceId, type LyricsSourceConfig } from '../store/authStore';
|
||||
import { useAuthStore, ServerProfile, MIX_MIN_RATING_FILTER_MAX_STARS, type SeekbarStyle, type LyricsSourceId, type LyricsSourceConfig, type LoggingMode } from '../store/authStore';
|
||||
import { SeekbarPreview } from '../components/WaveformSeek';
|
||||
import { IS_LINUX, IS_MACOS, IS_WINDOWS } from '../utils/platform';
|
||||
import { useThemeStore } from '../store/themeStore';
|
||||
@@ -40,7 +40,7 @@ import {
|
||||
type NdUser, type NdLibrary,
|
||||
} from '../api/navidromeAdmin';
|
||||
import { switchActiveServer } from '../utils/switchActiveServer';
|
||||
import { open as openDialog } from '@tauri-apps/plugin-dialog';
|
||||
import { open as openDialog, save as saveDialog } from '@tauri-apps/plugin-dialog';
|
||||
import ConfirmModal from '../components/ConfirmModal';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import Equalizer from '../components/Equalizer';
|
||||
@@ -1157,6 +1157,23 @@ export default function Settings() {
|
||||
}
|
||||
};
|
||||
|
||||
const exportRuntimeLogs = async () => {
|
||||
const suggestedName = `psysonic-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.log`;
|
||||
const selected = await saveDialog({
|
||||
defaultPath: suggestedName,
|
||||
filters: [{ name: 'Log files', extensions: ['log', 'txt'] }],
|
||||
title: t('settings.loggingExport'),
|
||||
});
|
||||
if (!selected || Array.isArray(selected)) return;
|
||||
try {
|
||||
const lines = await invoke<number>('export_runtime_logs', { path: selected });
|
||||
showToast(t('settings.loggingExportSuccess', { count: lines }), 3500, 'info');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showToast(t('settings.loggingExportError'), 4500, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const tabs: { id: Tab; label: string; icon: React.ReactNode }[] = [
|
||||
{ id: 'general', label: t('settings.tabGeneral'), icon: <AppWindow size={15} /> },
|
||||
{ id: 'server', label: t('settings.tabServer'), icon: <Server size={15} /> },
|
||||
@@ -2927,7 +2944,35 @@ export default function Settings() {
|
||||
|
||||
{activeTab === 'system' && (
|
||||
<>
|
||||
<BackupSection />
|
||||
<BackupSection />
|
||||
<section className="settings-section">
|
||||
<div className="settings-section-header">
|
||||
<Sliders size={18} />
|
||||
<h2>{t('settings.loggingTitle')}</h2>
|
||||
</div>
|
||||
<div className="settings-card">
|
||||
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: '0.75rem' }}>
|
||||
{t('settings.loggingModeDesc')}
|
||||
</div>
|
||||
<CustomSelect
|
||||
value={auth.loggingMode}
|
||||
onChange={(v) => auth.setLoggingMode(v as LoggingMode)}
|
||||
options={[
|
||||
{ value: 'off', label: t('settings.loggingModeOff') },
|
||||
{ value: 'normal', label: t('settings.loggingModeNormal') },
|
||||
{ value: 'debug', label: t('settings.loggingModeDebug') },
|
||||
]}
|
||||
/>
|
||||
{auth.loggingMode === 'debug' && (
|
||||
<div style={{ marginTop: '0.75rem' }}>
|
||||
<button className="btn btn-surface" onClick={exportRuntimeLogs}>
|
||||
<Download size={14} />
|
||||
{t('settings.loggingExport')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
<section className="settings-section">
|
||||
<div className="settings-section-header">
|
||||
<Info size={18} />
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface ServerProfile {
|
||||
}
|
||||
|
||||
export type SeekbarStyle = 'waveform' | 'linedot' | 'bar' | 'thick' | 'segmented' | 'neon' | 'pulsewave' | 'particletrail' | 'liquidfill' | 'retrotape';
|
||||
export type LoggingMode = 'off' | 'normal' | 'debug';
|
||||
|
||||
export type LyricsSourceId = 'server' | 'lrclib' | 'netease';
|
||||
export interface LyricsSourceConfig { id: LyricsSourceId; enabled: boolean; }
|
||||
@@ -71,6 +72,8 @@ interface AuthState {
|
||||
preloadMiniPlayer: boolean;
|
||||
/** Linux WebKitGTK: smooth wheel on when true; off only after explicit opt-out in Settings. */
|
||||
linuxWebkitKineticScroll: boolean;
|
||||
/** Runtime backend logging level. */
|
||||
loggingMode: LoggingMode;
|
||||
nowPlayingEnabled: boolean;
|
||||
lyricsServerFirst: boolean;
|
||||
enableNeteaselyrics: boolean;
|
||||
@@ -217,6 +220,7 @@ interface AuthState {
|
||||
setUseCustomTitlebar: (v: boolean) => void;
|
||||
setPreloadMiniPlayer: (v: boolean) => void;
|
||||
setLinuxWebkitKineticScroll: (v: boolean) => void;
|
||||
setLoggingMode: (v: LoggingMode) => void;
|
||||
setNowPlayingEnabled: (v: boolean) => void;
|
||||
setLyricsServerFirst: (v: boolean) => void;
|
||||
setEnableNeteaselyrics: (v: boolean) => void;
|
||||
@@ -324,6 +328,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
useCustomTitlebar: false,
|
||||
preloadMiniPlayer: false,
|
||||
linuxWebkitKineticScroll: true,
|
||||
loggingMode: 'normal',
|
||||
nowPlayingEnabled: false,
|
||||
lyricsServerFirst: true,
|
||||
enableNeteaselyrics: false,
|
||||
@@ -455,6 +460,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
setUseCustomTitlebar: (v) => set({ useCustomTitlebar: v }),
|
||||
setPreloadMiniPlayer: (v) => set({ preloadMiniPlayer: v }),
|
||||
setLinuxWebkitKineticScroll: (v) => set({ linuxWebkitKineticScroll: v }),
|
||||
setLoggingMode: (v) => set({ loggingMode: v }),
|
||||
setNowPlayingEnabled: (v) => set({ nowPlayingEnabled: v }),
|
||||
setLyricsServerFirst: (v: boolean) => set({ lyricsServerFirst: v }),
|
||||
setEnableNeteaselyrics: (v: boolean) => set({ enableNeteaselyrics: v }),
|
||||
|
||||
Reference in New Issue
Block a user