Skip to content

Commit 3be83bf

Browse files
committed
feat(logs): introduce logs viewer and file logging configuration
Adds a dedicated "Logs" page for viewing server and request error logs. Implements a setting to enable/disable logging to file in `Settings`. Integrates Tauri file system plugin for backend log access. Provides API endpoints for fetching, listing, downloading, and clearing logs. Introduces new UI components: Sheet, Switch, and Tabs from Radix UI. Updates application navigation and routing for the new logs page. Adds internationalization keys for all new log-related texts. Why: To provide users with direct access to application logs for debugging and monitoring, and to allow control over file logging.
1 parent c42b30f commit 3be83bf

20 files changed

Lines changed: 654 additions & 3 deletions

File tree

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@tailwindcss/vite": "^4.1.18",
2121
"@tauri-apps/api": "^2",
2222
"@tauri-apps/plugin-dialog": "^2.4.2",
23+
"@tauri-apps/plugin-fs": "^2.4.5",
2324
"@tauri-apps/plugin-process": "^2.3.1",
2425
"@tauri-apps/plugin-shell": "^2.3.3",
2526
"@tauri-apps/plugin-updater": "^2.9.0",
@@ -28,6 +29,7 @@
2829
"clsx": "^2.1.1",
2930
"i18next": "^25.7.1",
3031
"lucide-react": "^0.562.0",
32+
"radix-ui": "^1.4.3",
3133
"react": "^19.1.0",
3234
"react-dom": "^19.1.0",
3335
"react-i18next": "^16.4.0",

src-tauri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ tauri = { version = "2", features = ["tray-icon"] }
1717
tauri-plugin-opener = "2"
1818
tauri-plugin-shell = "2"
1919
tauri-plugin-dialog = "2"
20+
tauri-plugin-fs = "2"
2021
tauri-plugin-updater = "2"
2122
tauri-plugin-process = "2"
2223
serde = { version = "1", features = ["derive"] }

src-tauri/capabilities/default.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"shell:default",
1010
"dialog:default",
1111
"updater:default",
12-
"process:allow-restart"
12+
"process:allow-restart",
13+
"fs:default"
1314
]
1415
}

src-tauri/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ pub fn run() {
7373
.plugin(tauri_plugin_opener::init())
7474
.plugin(tauri_plugin_shell::init())
7575
.plugin(tauri_plugin_dialog::init())
76+
.plugin(tauri_plugin_fs::init())
7677
.plugin(tauri_plugin_process::init())
7778
.setup(|app| {
7879
#[cfg(desktop)]

src/constants/routes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import {
44
Users,
55
Settings,
66
Info,
7+
FileText,
78
} from 'lucide-react';
89

910
export const NAV_ITEMS = [
1011
{ path: '/dashboard', icon: LayoutDashboard, labelKey: 'nav.dashboard' },
1112
{ path: '/quota', icon: BarChart3, labelKey: 'nav.quota' },
1213
{ path: '/providers', icon: Users, labelKey: 'providers.title' },
14+
{ path: '/logs', icon: FileText, labelKey: 'nav.logs' },
1315
{ path: '/settings', icon: Settings, labelKey: 'nav.settings' },
1416
{ path: '/about', icon: Info, labelKey: 'nav.about' },
1517
] as const;
@@ -18,6 +20,7 @@ export const ROUTE_PATHS = {
1820
DASHBOARD: '/dashboard',
1921
QUOTA: '/quota',
2022
PROVIDERS: '/providers',
23+
LOGS: '/logs',
2124
SETTINGS: '/settings',
2225
ABOUT: '/about',
2326
LOGIN: '/login',

src/features/settings/SettingsPage.tsx

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { useConfigStore } from '@/features/settings/config.store';
1212
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/shared/components/ui/card';
1313
import { Button } from '@/shared/components/ui/button';
1414
import { Label } from '@/shared/components/ui/label';
15-
import { Sun, Moon, Monitor, LogOut, Globe, Server, FolderOpen, Play, Square, CheckCircle2, BarChart3, Loader2 } from 'lucide-react';
15+
import { Sun, Moon, Monitor, LogOut, Globe, Server, FolderOpen, Play, Square, CheckCircle2, BarChart3, Loader2, FileText } from 'lucide-react';
1616
import { toast } from 'sonner';
1717

1818
export function SettingsPage() {
@@ -21,7 +21,7 @@ export function SettingsPage() {
2121
const { language, setLanguage } = useLanguageStore();
2222
const { logout, connectionStatus } = useAuthStore();
2323
const { exePath, autoStart, runInBackground, isServerRunning, setAutoStart, setRunInBackground, browseForExe, startServer, stopServer } = useCliProxyStore();
24-
const { config, fetchConfig, updateUsageStatistics, updatingUsageStats } = useConfigStore();
24+
const { config, fetchConfig, updateUsageStatistics, updatingUsageStats, updateLoggingToFile, updatingLogging } = useConfigStore();
2525

2626
useEffect(() => {
2727
if (connectionStatus === 'connected' && !config) {
@@ -39,6 +39,16 @@ export function SettingsPage() {
3939
}
4040
};
4141

42+
const loggingToFileEnabled = Boolean(config?.['logging-to-file'] ?? false);
43+
44+
const handleToggleLogging = async () => {
45+
try {
46+
await updateLoggingToFile(!loggingToFileEnabled);
47+
} catch {
48+
toast.error(t('logging.error'));
49+
}
50+
};
51+
4252
const themeOptions = [
4353
{ value: 'light', label: t('settings.light'), icon: Sun },
4454
{ value: 'dark', label: t('settings.dark'), icon: Moon },
@@ -206,6 +216,47 @@ export function SettingsPage() {
206216
</CardContent>
207217
</Card>
208218

219+
{/* Logging Settings */}
220+
<Card>
221+
<CardHeader>
222+
<CardTitle className="flex items-center gap-2">
223+
<FileText className="h-5 w-5" />
224+
{t('logging.title')}
225+
</CardTitle>
226+
<CardDescription>{t('logging.description')}</CardDescription>
227+
</CardHeader>
228+
<CardContent>
229+
<div className="flex items-center justify-between">
230+
<div className="space-y-0.5">
231+
<Label>{t('logging.enabled')}</Label>
232+
<p className="text-xs text-muted-foreground">
233+
{t('logging.enabledDesc')}
234+
</p>
235+
</div>
236+
<button
237+
type="button"
238+
role="switch"
239+
aria-checked={loggingToFileEnabled}
240+
onClick={handleToggleLogging}
241+
disabled={updatingLogging || connectionStatus !== 'connected'}
242+
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${
243+
loggingToFileEnabled ? 'bg-primary' : 'bg-muted'
244+
}`}
245+
>
246+
{updatingLogging ? (
247+
<Loader2 className="h-4 w-4 animate-spin mx-auto text-muted-foreground" />
248+
) : (
249+
<span
250+
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
251+
loggingToFileEnabled ? 'translate-x-6' : 'translate-x-1'
252+
}`}
253+
/>
254+
)}
255+
</button>
256+
</div>
257+
</CardContent>
258+
</Card>
259+
209260
{/* Theme Settings */}
210261
<Card>
211262
<CardHeader>

src/features/settings/config.store.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ interface ConfigState {
99
error: string | null;
1010
lastFetch: number;
1111
updatingUsageStats: boolean;
12+
updatingLogging: boolean;
1213
fetchConfig: (forceRefresh?: boolean) => Promise<Config>;
1314
clearCache: () => void;
1415
updateUsageStatistics: (enabled: boolean) => Promise<void>;
16+
updateLoggingToFile: (enabled: boolean) => Promise<void>;
1517
}
1618

1719
let configRequestToken = 0;
@@ -23,6 +25,7 @@ export const useConfigStore = create<ConfigState>((set, get) => ({
2325
error: null,
2426
lastFetch: 0,
2527
updatingUsageStats: false,
28+
updatingLogging: false,
2629

2730
fetchConfig: async (forceRefresh = false) => {
2831
const { lastFetch, config } = get();
@@ -88,4 +91,18 @@ export const useConfigStore = create<ConfigState>((set, get) => ({
8891
set({ updatingUsageStats: false });
8992
}
9093
},
94+
95+
updateLoggingToFile: async (enabled: boolean) => {
96+
set({ updatingLogging: true, error: null });
97+
try {
98+
await configApi.updateLoggingToFile(enabled);
99+
const { fetchConfig } = get();
100+
await fetchConfig(true);
101+
} catch (error: unknown) {
102+
set({ error: (error as Error).message || 'Failed to update logging setting' });
103+
throw error;
104+
} finally {
105+
set({ updatingLogging: false });
106+
}
107+
},
91108
}));

src/i18n/locales/en.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,40 @@
190190
"tokensUpper": "TOKENS",
191191
"modelsBreakdown": "MODELS BREAKDOWN"
192192
},
193+
"logging": {
194+
"title": "Logging",
195+
"description": "Configure request logging to file",
196+
"enabled": "Log to File",
197+
"enabledDesc": "Save request and error logs to file for debugging",
198+
"error": "Failed to update logging setting"
199+
},
200+
"logs": {
201+
"title": "Logs Viewer",
202+
"downloadLogs": "Download Logs",
203+
"clearLogs": "Clear Logs",
204+
"saved": "Logs saved",
205+
"saveFailed": "Failed to save logs",
206+
"loggingDisabled": "Logging to file is disabled. Enable it in Settings → Logging to capture request logs.",
207+
"serverLogs": "Server Logs",
208+
"errorLogs": "Request Error Logs",
209+
"errorLogsDesc": "Pick an error request log file to download (only generated when request logging is off).",
210+
"loadingErrorLogs": "Loading error logs...",
211+
"noErrorLogs": "No error logs found",
212+
"noErrorLogsDesc": "Your API requests are running smoothly.",
213+
"searchPlaceholder": "Search logs by content or keyword",
214+
"hideMgmtLogs": "Hide /v0/management logs",
215+
"showRawLogs": "Show Raw Logs",
216+
"autoRefresh": "Auto Refresh",
217+
"scrollUp": "Scroll up to load more",
218+
"loaded": "Loaded",
219+
"filtered": "Filtered",
220+
"hidden": "Hidden",
221+
"loadingServerLogs": "Loading server logs...",
222+
"noLogsAvailable": "No logs available.",
223+
"noMatchingLogs": "No log records match your current filters.",
224+
"errorPayloadTitle": "Error Request Payload",
225+
"downloadRaw": "Download RAW"
226+
},
193227
"dashboard": {
194228
"title": "Dashboard",
195229
"hello": "Hello",

src/i18n/locales/id.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,40 @@
190190
"tokensUpper": "TOKEN",
191191
"modelsBreakdown": "RINCIAN MODEL"
192192
},
193+
"logging": {
194+
"title": "Pencatatan",
195+
"description": "Konfigurasi pencatatan permintaan ke file",
196+
"enabled": "Catat ke File",
197+
"enabledDesc": "Simpan log permintaan dan kesalahan ke file untuk debugging",
198+
"error": "Gagal memperbarui pengaturan pencatatan"
199+
},
200+
"logs": {
201+
"title": "Penampil Log",
202+
"downloadLogs": "Unduh Log",
203+
"clearLogs": "Hapus Log",
204+
"saved": "Log tersimpan",
205+
"saveFailed": "Gagal menyimpan log",
206+
"loggingDisabled": "Pencatatan ke file dinonaktifkan. Aktifkan di Pengaturan → Pencatatan untuk merekam log permintaan.",
207+
"serverLogs": "Log Server",
208+
"errorLogs": "Log Kesalahan Permintaan",
209+
"errorLogsDesc": "Pilih file log kesalahan untuk diunduh (hanya dibuat saat pencatatan permintaan nonaktif).",
210+
"loadingErrorLogs": "Memuat log kesalahan...",
211+
"noErrorLogs": "Tidak ada log kesalahan",
212+
"noErrorLogsDesc": "Permintaan API Anda berjalan lancar.",
213+
"searchPlaceholder": "Cari log berdasarkan konten atau kata kunci",
214+
"hideMgmtLogs": "Sembunyikan log /v0/management",
215+
"showRawLogs": "Tampilkan Log Mentah",
216+
"autoRefresh": "Segarkan Otomatis",
217+
"scrollUp": "Gulir ke atas untuk memuat lebih banyak",
218+
"loaded": "Dimuat",
219+
"filtered": "Difilter",
220+
"hidden": "Disembunyikan",
221+
"loadingServerLogs": "Memuat log server...",
222+
"noLogsAvailable": "Tidak ada log tersedia.",
223+
"noMatchingLogs": "Tidak ada catatan log yang cocok dengan filter Anda.",
224+
"errorPayloadTitle": "Payload Kesalahan Permintaan",
225+
"downloadRaw": "Unduh RAW"
226+
},
193227
"dashboard": {
194228
"title": "Dasbor",
195229
"hello": "Halo",

src/i18n/locales/ja.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,40 @@
189189
"tokensUpper": "トークン",
190190
"modelsBreakdown": "モデル別内訳"
191191
},
192+
"logging": {
193+
"title": "ログ記録",
194+
"description": "リクエストログのファイル記録を設定",
195+
"enabled": "ファイルに記録",
196+
"enabledDesc": "デバッグ用にリクエストとエラーのログをファイルに保存",
197+
"error": "ログ設定の更新に失敗しました"
198+
},
199+
"logs": {
200+
"title": "ログビューア",
201+
"downloadLogs": "ログをダウンロード",
202+
"clearLogs": "ログをクリア",
203+
"saved": "ログを保存しました",
204+
"saveFailed": "ログの保存に失敗しました",
205+
"loggingDisabled": "ファイルへのログ記録が無効です。リクエストログを記録するには、設定 → ログ記録で有効にしてください。",
206+
"serverLogs": "サーバーログ",
207+
"errorLogs": "リクエストエラーログ",
208+
"errorLogsDesc": "ダウンロードするエラーリクエストログファイルを選択してください(リクエストログがオフの場合のみ生成)。",
209+
"loadingErrorLogs": "エラーログを読み込んでいます...",
210+
"noErrorLogs": "エラーログが見つかりません",
211+
"noErrorLogsDesc": "API リクエストは正常に動作しています。",
212+
"searchPlaceholder": "内容またはキーワードでログを検索",
213+
"hideMgmtLogs": "/v0/management ログを非表示",
214+
"showRawLogs": "生ログを表示",
215+
"autoRefresh": "自動更新",
216+
"scrollUp": "上にスクロールして続きを読み込む",
217+
"loaded": "読み込み済み",
218+
"filtered": "フィルター済み",
219+
"hidden": "非表示",
220+
"loadingServerLogs": "サーバーログを読み込んでいます...",
221+
"noLogsAvailable": "ログがありません。",
222+
"noMatchingLogs": "現在のフィルターに一致するログ記録がありません。",
223+
"errorPayloadTitle": "エラーリクエストペイロード",
224+
"downloadRaw": "RAW をダウンロード"
225+
},
192226
"dashboard": {
193227
"title": "ダッシュボード",
194228
"hello": "こんにちは",

0 commit comments

Comments
 (0)