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:
@@ -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
|
||||||
|
|||||||
@@ -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('')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user