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 { Modal } from './ui/Modal'
import { useLogger } from '../hooks/useLogger'
import {
RefreshCw,
Trash2,
@@ -82,6 +83,7 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
const [deleting, setDeleting] = useState<Set<string>>(new Set())
const [allUsers, setAllUsers] = useState<string[]>([])
const [selectedUsers, setSelectedUsers] = useState<string[]>([])
const logger = useLogger('OperationHistory')
const isAdmin = user?.userType === 'Admin'
@@ -116,9 +118,11 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
setAllUsers(usernames)
}
} 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(
async (batchId: string) => {
@@ -133,10 +137,13 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
setBatchDetails((prev) => new Map(prev).set(batchId, result.data!))
}
} 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

View File

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

View File

@@ -12,6 +12,7 @@ import { Modal } from './ui/Modal'
import { showSuccess, showError, showInfo } from '../stores/useAppStore'
import { ConfirmDialog } from './ui/ConfirmDialog'
import { useConfirmDialog } from './ui/useConfirmDialog'
import { useLogger } from '../hooks/useLogger'
interface MaterialTypeRecord {
id?: number
@@ -48,6 +49,7 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
const [editingCell, setEditingCell] = useState<{ rowIndex: number; field: string } | null>(null)
const [editValue, setEditValue] = useState('')
const [selectedRowIndex, setSelectedRowIndex] = useState<number | null>(null)
const logger = useLogger('MaterialType')
const tableRef = useRef<HTMLTableElement>(null)
const inputRef = useRef<HTMLInputElement>(null)
@@ -96,11 +98,15 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
)
setSelectedRowIndex(null)
} 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 {
setLoading(false)
}
}, [currentUsername, isAdmin])
}, [currentUsername, isAdmin, logger])
// Load data when dialog opens
useEffect(() => {
@@ -286,6 +292,12 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
}
} catch (error) {
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 {
setSaving(false)
}

View File

@@ -1,5 +1,6 @@
import { useState, useEffect, useMemo, useRef, useCallback } from 'react'
import { showSuccess, showError, showWarning, formatListMessage } from '../stores/useAppStore'
import { useLogger } from './useLogger'
import { ConfirmDialogProps } from '../components/ui/ConfirmDialog'
import {
buildDeletionPlan,
@@ -20,6 +21,8 @@ import {
import type { CleanerProgress, CleanerReportData, ValidationResult } from './cleaner/types'
export function useCleaner() {
const logger = useLogger('Cleaner')
// Authentication & permissions
const [isAdmin, setIsAdmin] = useState(false)
const [currentUsername, setCurrentUsername] = useState<string>('')
@@ -108,11 +111,13 @@ export function useCleaner() {
setSelectedManagers(new Set([result.currentUsername]))
}
} catch (err) {
console.error('Initialization failed:', err)
logger.error('Cleaner page initialization failed', {
error: err instanceof Error ? err.message : String(err)
})
}
}
initializePage()
}, [])
}, [logger])
// Subscribe to cleaner progress events
useEffect(() => {
@@ -135,11 +140,13 @@ export function useCleaner() {
setProcessConcurrency(result.processConcurrency)
}
} 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()
}, [])
}, [logger])
useEffect(() => {
sessionStorage.setItem('cleaner_dryRun', dryRun.toString())
@@ -155,7 +162,10 @@ export function useCleaner() {
try {
await window.electron.config.updateCleaner({ processConcurrency: clamped })
} 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 type { LogLevel } from '../stores/extractorStore'
import { useExtractorStore } from '../stores/extractorStore'
import { useLogger } from './useLogger'
function isLogLevel(value: string): value is LogLevel {
return ['info', 'success', 'warning', 'error', 'system'].includes(value)
}
export function useExtractor() {
const logger = useLogger('Extractor')
const {
isRunning,
isComplete,
@@ -73,6 +76,11 @@ export function useExtractor() {
'success',
`提取完成:下载 ${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) {
addLog('warning', `存在 ${data.errors.length} 个错误`)
// Log each error detail for debugging
@@ -83,11 +91,13 @@ export function useExtractor() {
} else {
setError(response.error || '提取失败')
addLog('error', response.error || '提取失败')
logger.error('Extraction failed', { error: response.error })
}
} catch (err) {
const errMsg = err instanceof Error ? err.message : '发生未知错误'
setError(errMsg)
addLog('error', errMsg)
logger.error('Extraction exception', { error: errMsg })
} finally {
setRunning(false)
setProgress(null)