feat(logging): Wave 2 - integrate logging throughout application

This commit integrates the logging infrastructure across the entire application:

IPC Layer:
- Add logger-handler.ts with centralized IPC logging channels
- Integrate audit logging into auth, cleaner, extractor handlers
- Add structured logging for IPC operations and data flow

Service Layer:
- Add logger integration to ERP services (extractor, cleaner)
- Integrate logging into excel-parser and user DAO
- Add operation tracking and error logging

Renderer Layer:
- Add useLogger hook for component-level logging
- Update App.tsx with session and user activity logging
- Enable frontend audit trail for critical actions

Testing:
- Add comprehensive IPC logging integration tests
- Enhance unit test coverage for logger and audit-logger
- Add end-to-end logging flow validation

Types:
- Update preload type definitions for logging APIs

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
test
2026-03-08 15:07:03 +08:00
parent 5e6898fb40
commit 6df16898da
20 changed files with 1653 additions and 305 deletions

View File

@@ -10,6 +10,7 @@
import React, { useState, useEffect } from 'react'
import { LayoutDashboard, Download, Trash2, Settings, Database, User, LogOut } from 'lucide-react'
import { useLogger } from './hooks/useLogger'
import LoginDialog from './components/LoginDialog'
import UserSelectionDialog, {
type UserInfo as SelectedUserInfo
@@ -26,6 +27,9 @@ interface CurrentUser {
}
function App(): React.JSX.Element {
// Create logger instance for App component
const logger = useLogger('App')
// Authentication state
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [isAuthenticating, setIsAuthenticating] = useState(true)
@@ -52,28 +56,30 @@ function App(): React.JSX.Element {
// Initialize authentication on mount
useEffect(() => {
console.log('=== App: Initializing auth... ===')
logger.info('=== Initializing auth... ===')
initializeAuth()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const initializeAuth = async () => {
console.log('=== App: Starting initializeAuth ===')
logger.info('=== Starting initializeAuth ===')
try {
// Get computer name
console.log('Getting computer name...')
logger.debug('Getting computer name...')
const computerNameResult = await window.electron.auth.getComputerName()
const name = computerNameResult.success && computerNameResult.data ? computerNameResult.data : ''
console.log('Computer name:', name)
const name =
computerNameResult.success && computerNameResult.data ? computerNameResult.data : ''
logger.debug('Computer name obtained', { name })
setComputerName(name)
// Try silent login
console.log('Trying silent login...')
logger.debug('Trying silent login...')
const silentLoginResult = await window.electron.auth.silentLogin()
const result = silentLoginResult.data
console.log('Silent login result:', result)
logger.debug('Silent login result', { result })
if (silentLoginResult.success && result?.success && result.userInfo) {
console.log('Silent login success:', result.userInfo)
logger.info('Silent login success', { username: result.userInfo.username })
setCurrentUser({
username: result.userInfo.username,
userType: result.userInfo.userType
@@ -81,28 +87,30 @@ function App(): React.JSX.Element {
// Check if admin needs user selection
if (result.requiresUserSelection) {
console.log('Admin user needs to select user')
logger.info('Admin user needs to select user')
// Load all users for selection
const usersResult = await window.electron.auth.getAllUsers()
setAllUsers(usersResult.success && usersResult.data ? usersResult.data : [])
setShowUserSelection(true)
} else {
console.log('Setting authenticated to true')
logger.info('Setting authenticated to true')
setIsAuthenticated(true)
}
} else {
console.log('Silent login failed, showing login dialog')
logger.info('Silent login failed, showing login dialog')
// Silent login failed, show login dialog
setShowLoginDialog(true)
}
} catch (error) {
console.error('Auth initialization error:', error)
logger.error('Auth initialization error', {
error: error instanceof Error ? error.message : String(error)
})
setShowLoginDialog(true)
} finally {
console.log('Setting isAuthenticating to false')
logger.debug('Setting isAuthenticating to false')
setIsAuthenticating(false)
}
console.log('=== App: Auth initialization complete ===')
logger.info('=== Auth initialization complete ===')
}
// Handle login dialog submit
@@ -132,7 +140,7 @@ function App(): React.JSX.Element {
}
return false
} catch (error) {
console.error('Login error:', error)
logger.error('Login error', { error: error instanceof Error ? error.message : String(error) })
return false
}
}
@@ -159,7 +167,9 @@ function App(): React.JSX.Element {
setIsSwitchedByAdmin(true)
}
} catch (error) {
console.error('User selection error:', error)
logger.error('User selection error', {
error: error instanceof Error ? error.message : String(error)
})
showError('切换用户失败')
}
}
@@ -223,12 +233,7 @@ function App(): React.JSX.Element {
// Show login dialog if not authenticated
if (!isAuthenticated) {
console.log(
'Render: not authenticated, showLoginDialog:',
showLoginDialog,
'computerName:',
computerName
)
logger.debug('Render: not authenticated', { showLoginDialog, computerName })
return (
<>
<LoginDialog
@@ -299,7 +304,7 @@ function App(): React.JSX.Element {
}
// Show main content when authenticated
console.log('Render: authenticated, currentUser:', currentUser, 'currentPage:', currentPage)
logger.debug('Render: authenticated', { currentUser, currentPage })
const navItems = [
{ id: 'extractor', label: '数据提取 (Extractor)', icon: <Download size={18} /> },

View File

@@ -0,0 +1,156 @@
import { useRef, useCallback } from 'react'
import type { LogLevel } from '../../../shared/ipc-channels'
/**
* Logger interface for renderer process
* Provides type-safe logging with component context
*/
export interface RendererLogger {
/**
* Log a message at specified level
*/
log: (level: LogLevel, message: string, meta?: Record<string, unknown>) => void
/**
* Log an info level message (business operations)
*/
info: (message: string, meta?: Record<string, unknown>) => void
/**
* Log a warning level message (recoverable issues)
*/
warn: (message: string, meta?: Record<string, unknown>) => void
/**
* Log an error level message (failures)
*/
error: (message: string, meta?: Record<string, unknown>) => void
/**
* Log a debug level message (technical details)
*/
debug: (message: string, meta?: Record<string, unknown>) => void
}
/**
* React Hook for logging from renderer process to main process Winston logger
*
* @param context - Component or feature context for log messages
* @returns Logger instance with component context
*
* @example
* ```typescript
* function MyComponent() {
* const logger = useLogger('MyComponent')
*
* const handleClick = () => {
* logger.info('User clicked button', { buttonId: 'submit' })
* }
*
* const handleError = (err: Error) => {
* logger.error('Operation failed', { error: err.message })
* }
* }
* ```
*/
export function useLogger(context: string): RendererLogger {
// Use ref to store context to avoid recreating logger on re-renders
const contextRef = useRef(context)
contextRef.current = context
// Create stable logger instance using useCallback
const logger = useCallback((level: LogLevel, message: string, meta?: Record<string, unknown>) => {
// Check if window.electron is available (safety check)
if (typeof window !== 'undefined' && window.electron?.logger?.log) {
window.electron.logger.log(level, message, {
...meta,
context: contextRef.current
})
} else {
// Fallback to console in development if IPC not available
if (process.env.NODE_ENV === 'development') {
const consoleMethod = console[level] || console.log
consoleMethod(`[${contextRef.current}] ${message}`, meta || '')
}
}
}, [])
// Return memoized logger methods
return {
log: logger,
info: useCallback(
(message: string, meta?: Record<string, unknown>) => {
logger('info', message, meta)
},
[logger]
),
warn: useCallback(
(message: string, meta?: Record<string, unknown>) => {
logger('warn', message, meta)
},
[logger]
),
error: useCallback(
(message: string, meta?: Record<string, unknown>) => {
logger('error', message, meta)
},
[logger]
),
debug: useCallback(
(message: string, meta?: Record<string, unknown>) => {
logger('debug', message, meta)
},
[logger]
)
}
}
/**
* FPS monitoring utility for detecting UI lag from excessive logging
*
* @param threshold - FPS threshold below which to warn (default: 30)
* @param logger - Logger instance to use for warnings
*
* @example
* ```typescript
* useEffect(() => {
* const stopMonitoring = monitorFps(30, logger)
* return () => stopMonitoring()
* }, [logger])
* ```
*/
export function monitorFps(threshold: number = 30, logger: RendererLogger): () => void {
let frames = 0
let lastFpsCheck = 0
let warningCooldown = 0
let animationFrameId: number
const measureFps = (currentTime: number) => {
frames++
// Check FPS every second
const elapsed = currentTime - lastFpsCheck
if (elapsed >= 1000) {
const fps = Math.round((frames * 1000) / elapsed)
if (fps < threshold && warningCooldown <= 0) {
logger.warn('Low FPS detected - logging may be causing UI lag', {
fps,
threshold,
context: 'FPSMonitor'
})
warningCooldown = 5 // Don't warn again for 5 seconds
}
warningCooldown = Math.max(0, warningCooldown - 1)
frames = 0
lastFpsCheck = currentTime
}
animationFrameId = requestAnimationFrame(measureFps)
}
// Start monitoring
animationFrameId = requestAnimationFrame(measureFps)
// Return cleanup function
return () => {
cancelAnimationFrame(animationFrameId)
}
}