feat(logging-p0): add useLogger to renderer critical path components

Replace console.error with structured useLogger calls in 5 key renderer
files (Cleaner, LoginDialog, Extractor, OperationHistory, MaterialType)
to enable persistent log capture for frontend error diagnosis.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-04-04 12:46:41 +08:00
parent 12a17eccb7
commit 219d8ab752
5 changed files with 55 additions and 12 deletions

View File

@@ -7,6 +7,7 @@
import React, { useState, useEffect, useCallback } from 'react' import React, { useState, useEffect, useCallback } from 'react'
import { Modal } from './ui/Modal' import { Modal } from './ui/Modal'
import { useLogger } from '../hooks/useLogger'
import { import {
RefreshCw, RefreshCw,
Trash2, Trash2,
@@ -82,6 +83,7 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
const [deleting, setDeleting] = useState<Set<string>>(new Set()) const [deleting, setDeleting] = useState<Set<string>>(new Set())
const [allUsers, setAllUsers] = useState<string[]>([]) const [allUsers, setAllUsers] = useState<string[]>([])
const [selectedUsers, setSelectedUsers] = useState<string[]>([]) const [selectedUsers, setSelectedUsers] = useState<string[]>([])
const logger = useLogger('OperationHistory')
const isAdmin = user?.userType === 'Admin' const isAdmin = user?.userType === 'Admin'
@@ -116,9 +118,11 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
setAllUsers(usernames) setAllUsers(usernames)
} }
} catch (err) { } catch (err) {
console.error('Failed to fetch users:', err) logger.error('Failed to fetch users list', {
error: err instanceof Error ? err.message : String(err)
})
} }
}, []) }, [logger])
const fetchBatchDetails = useCallback( const fetchBatchDetails = useCallback(
async (batchId: string) => { async (batchId: string) => {
@@ -133,10 +137,13 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
setBatchDetails((prev) => new Map(prev).set(batchId, result.data!)) setBatchDetails((prev) => new Map(prev).set(batchId, result.data!))
} }
} catch (err) { } catch (err) {
console.error('Failed to fetch batch details:', err) logger.error('Failed to fetch batch details', {
error: err instanceof Error ? err.message : String(err),
batchId
})
} }
}, },
[batchDetails] [batchDetails, logger]
) )
// Fetch batches when modal opens // Fetch batches when modal opens

View File

@@ -9,6 +9,7 @@
import React, { useState, useRef } from 'react' import React, { useState, useRef } from 'react'
import { Modal } from './ui/Modal' import { Modal } from './ui/Modal'
import { useLogger } from '../hooks/useLogger'
interface LoginDialogProps { interface LoginDialogProps {
isOpen: boolean isOpen: boolean
@@ -31,6 +32,7 @@ export const LoginDialog: React.FC<LoginDialogProps> = ({
const [errorMessage, setErrorMessage] = useState('') const [errorMessage, setErrorMessage] = useState('')
const usernameInputRef = useRef<HTMLInputElement>(null) const usernameInputRef = useRef<HTMLInputElement>(null)
const errorRef = useRef<HTMLDivElement>(null) const errorRef = useRef<HTMLDivElement>(null)
const logger = useLogger('LoginDialog')
// Display error message with aria-live // Display error message with aria-live
const showError = (message: string): void => { const showError = (message: string): void => {
@@ -42,12 +44,14 @@ export const LoginDialog: React.FC<LoginDialogProps> = ({
setErrorMessage('') setErrorMessage('')
if (!username.trim()) { if (!username.trim()) {
logger.warn('Login validation: empty username')
showError('请输入用户名') showError('请输入用户名')
usernameInputRef.current?.focus() usernameInputRef.current?.focus()
return return
} }
if (!password.trim()) { if (!password.trim()) {
logger.warn('Login validation: empty password')
showError('请输入密码') showError('请输入密码')
return return
} }
@@ -57,8 +61,8 @@ export const LoginDialog: React.FC<LoginDialogProps> = ({
setIsLoggingIn(false) setIsLoggingIn(false)
if (!success) { if (!success) {
logger.error('Login failed: invalid credentials', { username: username.trim(), computerName })
showError('用户名或密码错误') showError('用户名或密码错误')
setPassword('')
} }
} }

View File

@@ -12,6 +12,7 @@ import { Modal } from './ui/Modal'
import { showSuccess, showError, showInfo } from '../stores/useAppStore' import { showSuccess, showError, showInfo } from '../stores/useAppStore'
import { ConfirmDialog } from './ui/ConfirmDialog' import { ConfirmDialog } from './ui/ConfirmDialog'
import { useConfirmDialog } from './ui/useConfirmDialog' import { useConfirmDialog } from './ui/useConfirmDialog'
import { useLogger } from '../hooks/useLogger'
interface MaterialTypeRecord { interface MaterialTypeRecord {
id?: number id?: number
@@ -48,6 +49,7 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
const [editingCell, setEditingCell] = useState<{ rowIndex: number; field: string } | null>(null) const [editingCell, setEditingCell] = useState<{ rowIndex: number; field: string } | null>(null)
const [editValue, setEditValue] = useState('') const [editValue, setEditValue] = useState('')
const [selectedRowIndex, setSelectedRowIndex] = useState<number | null>(null) const [selectedRowIndex, setSelectedRowIndex] = useState<number | null>(null)
const logger = useLogger('MaterialType')
const tableRef = useRef<HTMLTableElement>(null) const tableRef = useRef<HTMLTableElement>(null)
const inputRef = useRef<HTMLInputElement>(null) const inputRef = useRef<HTMLInputElement>(null)
@@ -96,11 +98,15 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
) )
setSelectedRowIndex(null) setSelectedRowIndex(null)
} catch (error) { } catch (error) {
console.error('Failed to load material types:', error) logger.error('Failed to load material types', {
error: error instanceof Error ? error.message : String(error),
isAdmin,
currentUsername
})
} finally { } finally {
setLoading(false) setLoading(false)
} }
}, [currentUsername, isAdmin]) }, [currentUsername, isAdmin, logger])
// Load data when dialog opens // Load data when dialog opens
useEffect(() => { useEffect(() => {
@@ -286,6 +292,12 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
} }
} catch (error) { } catch (error) {
showError(`保存失败:${error instanceof Error ? error.message : '未知错误'}`) showError(`保存失败:${error instanceof Error ? error.message : '未知错误'}`)
logger.error('Failed to save material types', {
error: error instanceof Error ? error.message : String(error),
inserts: toInsert.length,
updates: toUpdate.length,
deletes: toDelete.length
})
} finally { } finally {
setSaving(false) setSaving(false)
} }

View File

@@ -1,5 +1,6 @@
import { useState, useEffect, useMemo, useRef, useCallback } from 'react' import { useState, useEffect, useMemo, useRef, useCallback } from 'react'
import { showSuccess, showError, showWarning, formatListMessage } from '../stores/useAppStore' import { showSuccess, showError, showWarning, formatListMessage } from '../stores/useAppStore'
import { useLogger } from './useLogger'
import { ConfirmDialogProps } from '../components/ui/ConfirmDialog' import { ConfirmDialogProps } from '../components/ui/ConfirmDialog'
import { import {
buildDeletionPlan, buildDeletionPlan,
@@ -20,6 +21,8 @@ import {
import type { CleanerProgress, CleanerReportData, ValidationResult } from './cleaner/types' import type { CleanerProgress, CleanerReportData, ValidationResult } from './cleaner/types'
export function useCleaner() { export function useCleaner() {
const logger = useLogger('Cleaner')
// Authentication & permissions // Authentication & permissions
const [isAdmin, setIsAdmin] = useState(false) const [isAdmin, setIsAdmin] = useState(false)
const [currentUsername, setCurrentUsername] = useState<string>('') const [currentUsername, setCurrentUsername] = useState<string>('')
@@ -108,11 +111,13 @@ export function useCleaner() {
setSelectedManagers(new Set([result.currentUsername])) setSelectedManagers(new Set([result.currentUsername]))
} }
} catch (err) { } catch (err) {
console.error('Initialization failed:', err) logger.error('Cleaner page initialization failed', {
error: err instanceof Error ? err.message : String(err)
})
} }
} }
initializePage() initializePage()
}, []) }, [logger])
// Subscribe to cleaner progress events // Subscribe to cleaner progress events
useEffect(() => { useEffect(() => {
@@ -135,11 +140,13 @@ export function useCleaner() {
setProcessConcurrency(result.processConcurrency) setProcessConcurrency(result.processConcurrency)
} }
} catch (err) { } catch (err) {
console.error('Failed to load cleaner config:', err) logger.error('Failed to load cleaner config', {
error: err instanceof Error ? err.message : String(err)
})
} }
} }
loadCleanerConfig() loadCleanerConfig()
}, []) }, [logger])
useEffect(() => { useEffect(() => {
sessionStorage.setItem('cleaner_dryRun', dryRun.toString()) sessionStorage.setItem('cleaner_dryRun', dryRun.toString())
@@ -155,7 +162,10 @@ export function useCleaner() {
try { try {
await window.electron.config.updateCleaner({ processConcurrency: clamped }) await window.electron.config.updateCleaner({ processConcurrency: clamped })
} catch (err) { } catch (err) {
console.error('Failed to update cleaner config:', err) logger.error('Failed to update process concurrency', {
error: err instanceof Error ? err.message : String(err),
value: clamped
})
} }
} }

View File

@@ -1,12 +1,15 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import type { LogLevel } from '../stores/extractorStore' import type { LogLevel } from '../stores/extractorStore'
import { useExtractorStore } from '../stores/extractorStore' import { useExtractorStore } from '../stores/extractorStore'
import { useLogger } from './useLogger'
function isLogLevel(value: string): value is LogLevel { function isLogLevel(value: string): value is LogLevel {
return ['info', 'success', 'warning', 'error', 'system'].includes(value) return ['info', 'success', 'warning', 'error', 'system'].includes(value)
} }
export function useExtractor() { export function useExtractor() {
const logger = useLogger('Extractor')
const { const {
isRunning, isRunning,
isComplete, isComplete,
@@ -73,6 +76,11 @@ export function useExtractor() {
'success', 'success',
`提取完成:下载 ${data.downloadedFiles.length} 个文件,共 ${data.recordCount} 条记录` `提取完成:下载 ${data.downloadedFiles.length} 个文件,共 ${data.recordCount} 条记录`
) )
logger.info('Extraction completed', {
downloadedFiles: data.downloadedFiles.length,
recordCount: data.recordCount,
errorCount: data.errors.length
})
if (data.errors.length > 0) { if (data.errors.length > 0) {
addLog('warning', `存在 ${data.errors.length} 个错误`) addLog('warning', `存在 ${data.errors.length} 个错误`)
// Log each error detail for debugging // Log each error detail for debugging
@@ -83,11 +91,13 @@ export function useExtractor() {
} else { } else {
setError(response.error || '提取失败') setError(response.error || '提取失败')
addLog('error', response.error || '提取失败') addLog('error', response.error || '提取失败')
logger.error('Extraction failed', { error: response.error })
} }
} catch (err) { } catch (err) {
const errMsg = err instanceof Error ? err.message : '发生未知错误' const errMsg = err instanceof Error ? err.message : '发生未知错误'
setError(errMsg) setError(errMsg)
addLog('error', errMsg) addLog('error', errMsg)
logger.error('Extraction exception', { error: errMsg })
} finally { } finally {
setRunning(false) setRunning(false)
setProgress(null) setProgress(null)