refactor(logger): optimize logging architecture with 6 improvements

1. Extract shared module: consolidate getLogDir() and isProduction()
   into shared.ts, eliminate duplication across logger modules
2. Make retention config effective: delay file transport creation
   until config is loaded, apply appRetention/auditRetention from config.yaml
3. Add before-quit log flush: close logger and audit logger on
   app exit to prevent log loss
4. Unify logError entry point: remove duplicate logError from index.ts,
   re-export from error-utils.ts with richer error context
5. Renderer log level filtering: add client-side level check in preload
   to skip IPC for filtered-out messages
6. Child logger cache + audit cleanup: cache child loggers in IPC
   handler for performance, remove redundant timestamp format in audit logger

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-04-03 20:53:53 +08:00
parent 020bbcdccc
commit 883f98065a
22 changed files with 300 additions and 176 deletions

View File

@@ -259,7 +259,13 @@ export function registerExtractorHandlers(): void {
// Write per-order record counts
for (const { orderNumber, recordCount } of result.orderRecordCounts) {
await historyDao.updateRecordStatus(batchId, orderNumber, status, undefined, recordCount)
await historyDao.updateRecordStatus(
batchId,
orderNumber,
status,
undefined,
recordCount
)
}
// Update batch status without recordCount (per-order counts are set individually)

View File

@@ -68,15 +68,21 @@ export function withErrorHandling<T>(
}
if (isBaseError(error)) {
logError(log, `[${context}] ${error.name}`, error, {
code,
cause: getErrorCauseMessage(error),
handler: context
logError(log, error, {
message: `[${context}] ${error.name}`,
context: {
code,
cause: getErrorCauseMessage(error),
handler: context
}
})
} else {
logError(log, `[${context}] Error`, error, {
code,
handler: context
logError(log, error, {
message: `[${context}] Error`,
context: {
code,
handler: context
}
})
}

View File

@@ -10,7 +10,9 @@
*/
import { ipcMain } from 'electron'
import winston from 'winston'
import { createLogger } from '../services/logger'
import logger from '../services/logger'
import { IPC_CHANNELS, type LogLevel } from '../../shared/ipc-channels'
const log = createLogger('LoggerHandler')
@@ -41,6 +43,7 @@ class LoggerHandlerState {
private buffer: LogEntry[] = []
private debounceTimer: NodeJS.Timeout | null = null
private discardedCount = 0
private childLoggerCache = new Map<string, winston.Logger>()
/**
* Add log entry to buffer
@@ -131,16 +134,27 @@ class LoggerHandlerState {
}
}
/**
* Get or create a cached child logger for a component
* Avoids creating a new child logger for every log entry
* @param component - Component name for the child logger
*/
private getChildLogger(component: string): winston.Logger {
let child = this.childLoggerCache.get(component)
if (!child) {
child = log.child({ source: 'renderer', component })
this.childLoggerCache.set(component, child)
}
return child
}
/**
* Forward a single log entry to Winston logger
* @param entry - Log entry to forward
*/
private forwardToWinston(entry: LogEntry): void {
const context = (entry.context?.component as string) || 'renderer'
const childLogger = log.child({
source: 'renderer',
component: context
})
const childLogger = this.getChildLogger(context)
const message = entry.context?.message
? `[${entry.context.message}] ${entry.message}`
@@ -187,6 +201,7 @@ class LoggerHandlerState {
}
this.buffer = []
this.discardedCount = 0
this.childLoggerCache.clear()
}
}
@@ -197,6 +212,11 @@ const state = new LoggerHandlerState()
* Register IPC handlers for logger
*/
export function registerLoggerHandlers(): void {
// Return current log level to preload for client-side filtering
ipcMain.handle(IPC_CHANNELS.LOGGER_GET_LEVEL, () => {
return logger.level as LogLevel
})
// Use ipcMain.on with send() - fire-and-forget, non-blocking
ipcMain.on(IPC_CHANNELS.LOGGER_FORWARD, (_event, entry: LogEntry) => {
// Validate entry

View File

@@ -27,16 +27,13 @@ export function registerSettingsHandlers(): void {
const erpConfigService = UserErpConfigService.getInstance()
ipcMain.handle(IPC_CHANNELS.SETTINGS_GET_USER_TYPE, async (): Promise<IpcResult<UserType>> => {
return withErrorHandling(
async () => {
const userType = sessionManager.getUserType()
if (!userType) {
throw new ValidationError('未找到用户类型', 'VAL_INVALID_INPUT')
}
return userType as UserType
},
'settings:getUserType'
)
return withErrorHandling(async () => {
const userType = sessionManager.getUserType()
if (!userType) {
throw new ValidationError('未找到用户类型', 'VAL_INVALID_INPUT')
}
return userType as UserType
}, 'settings:getUserType')
})
ipcMain.handle(