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

@@ -3,6 +3,9 @@ import { ErpAuthService } from './erp-auth'
import type { CleanerInput, CleanerResult, OrderCleanDetail } from '../../types/cleaner.types'
import type { ErpSession } from '../../types/erp.types'
import type { FrameLocator, Locator, Page } from 'playwright'
import { createLogger } from '../logger'
const log = createLogger('CleanerService')
/**
* Cleaner Service Options
@@ -103,6 +106,13 @@ export class CleanerService {
}
const totalOrders = input.orderNumbers.length
const dryRun = input.dryRun ?? this.dryRun
log.info('Starting cleaner', {
totalOrders,
materialCount: input.materialCodes.length,
dryRun
})
// Create delete set for O(1) lookup
const deleteSet = new Set(input.materialCodes)
@@ -121,6 +131,7 @@ export class CleanerService {
const orderNumber = input.orderNumbers[i]
try {
log.debug('Processing order', { orderNumber, index: i + 1, total: totalOrders })
const detail = await this.processOrder({
workFrame,
popupPage,
@@ -138,6 +149,7 @@ export class CleanerService {
result.materialsSkipped += detail.materialsSkipped
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error'
log.error('Order processing failed', { orderNumber, error: message })
result.errors.push(`Order ${orderNumber}: ${message}`)
// Add error detail
@@ -153,8 +165,15 @@ export class CleanerService {
// Close popup page
await popupPage.close()
log.info('Cleaner completed', {
ordersProcessed: result.ordersProcessed,
materialsDeleted: result.materialsDeleted,
materialsSkipped: result.materialsSkipped,
errorCount: result.errors.length
})
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error'
log.error('Cleaner failed', { error: message })
result.errors.push(`Clean failed: ${message}`)
}

View File

@@ -6,6 +6,9 @@ import type {
ExtractorCoreResult,
ExtractionProgress
} from '../../types/extractor.types'
import { createLogger } from '../logger'
const log = createLogger('ExtractorCore')
/**
* ExtractorCore - Handles all web page operations for data extraction

View File

@@ -11,6 +11,9 @@ import type {
ExtractionProgress
} from '../../types/extractor.types'
import { DataImportService } from '../database/data-importer'
import { createLogger } from '../logger'
const log = createLogger('ExtractorService')
/**
* ERP Data Extractor Service
@@ -127,7 +130,7 @@ export class ExtractorService {
return { mergedFile: null, recordCount: 0 }
}
console.log(`[Extractor] Starting merge of ${filePaths.length} files`)
log.info('Starting merge', { fileCount: filePaths.length })
const parser = new ExcelParser({ verbose: true })
// Collect all orders with full order info and materials
@@ -137,17 +140,17 @@ export class ExtractorService {
// Parse each downloaded file and collect orders
for (const filePath of filePaths) {
try {
console.log(`[Extractor] Parsing file: ${filePath}`)
log.debug('Parsing file', { filePath })
await parser.parse(filePath)
// After parse(), the parser stores orders internally as lastOrders
// After parse(), the parser store orders internally as lastOrders
const orders = (parser as any).lastOrders
console.log(`[Extractor] Parsed ${orders?.length || 0} orders from ${filePath}`)
log.debug('File parsed', { filePath, orderCount: orders?.length || 0 })
if (orders && Array.isArray(orders)) {
allOrders.push(...orders)
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error)
console.error(`[Extractor] Failed to parse file ${filePath}:`, errorMsg)
log.error('Failed to parse file', { filePath, error: errorMsg })
}
}
@@ -157,10 +160,10 @@ export class ExtractorService {
recordCount += order.materials.length
}
console.log(`[Extractor] Total orders: ${allOrders.length}, total records: ${recordCount}`)
log.info('Merge summary', { orderCount: allOrders.length, recordCount })
if (recordCount === 0) {
console.warn('[Extractor] No records found in any of the downloaded files')
log.warn('No records found in any downloaded files')
return { mergedFile: null, recordCount: 0 }
}
@@ -174,17 +177,16 @@ export class ExtractorService {
// Save with error handling
try {
console.log(`[Extractor] Saving merged file to: ${outputPath}`)
log.info('Saving merged file', { outputPath })
await this.saveMergedOrders(allOrders, outputPath)
console.log(`[Extractor] Successfully saved merged file with ${recordCount} records`)
log.info('Merged file saved successfully', { recordCount })
return { mergedFile: outputPath, recordCount }
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error)
const errorStack = error instanceof Error ? error.stack : ''
console.error(`[Extractor] Failed to save merged file: ${errorMsg}`)
console.error(`[Extractor] Error stack: ${errorStack}`)
log.error('Failed to save merged file', { error: errorMsg, stack: errorStack })
// Return parsed record count and error info even if save fails
return { mergedFile: null, recordCount, error: `保存合并文件失败: ${errorMsg}` }
return { mergedFile: null, recordCount, error: `保存合并文件失败${errorMsg}` }
}
}
@@ -196,11 +198,11 @@ export class ExtractorService {
orders: Array<{ orderInfo: any; materials: any[] }>,
outputPath: string
): Promise<void> {
console.log(`[Extractor] Loading ExcelJS...`)
log.debug('Loading ExcelJS')
const ExcelJSModule = await import('exceljs')
// Handle both ESM and CommonJS module formats
const ExcelJS = ExcelJSModule.default || ExcelJSModule
console.log(`[Extractor] ExcelJS loaded, creating workbook...`)
log.debug('ExcelJS loaded, creating workbook')
const workbook = new ExcelJS.Workbook()
const worksheet = workbook.addWorksheet('Data')
@@ -239,7 +241,7 @@ export class ExtractorService {
{ header: '打印日期', key: 'printDate', width: 20 }
]
console.log(`[Extractor] Adding ${orders.length} orders to worksheet...`)
log.debug('Adding orders to worksheet', { orderCount: orders.length })
// Add data rows - merge orderInfo with each material
for (const order of orders) {
const { orderInfo, materials } = order
@@ -283,9 +285,9 @@ export class ExtractorService {
}
}
console.log(`[Extractor] Writing file to ${outputPath}...`)
log.debug('Writing file', { outputPath })
await workbook.xlsx.writeFile(outputPath)
console.log(`[Extractor] File saved successfully: ${outputPath}`)
log.debug('File saved successfully', { outputPath })
}
/**
@@ -296,10 +298,10 @@ export class ExtractorService {
for (const filePath of filePaths) {
try {
await fs.unlink(filePath)
console.log(`Deleted temporary file: ${filePath}`)
log.debug('Deleted temporary file', { filePath })
} catch (error) {
// Log error but don't fail the main process
console.error(`Failed to delete temporary file ${filePath}:`, error)
log.error('Failed to delete temporary file', { filePath, error })
}
}
}
@@ -310,14 +312,14 @@ export class ExtractorService {
* @returns Import result with statistics
*/
private async importToDatabase(filePath: string): Promise<ImportResult> {
console.log(`[Extractor] Starting database import from: ${filePath}`)
log.info('Starting database import', { filePath })
const importService = new DataImportService()
try {
const result = await importService.importFromExcel(filePath, 1000)
console.log(`[Extractor] Import completed`, {
log.info('Import completed', {
success: result.success,
recordsRead: result.recordsRead,
recordsDeleted: result.recordsDeleted,
@@ -327,7 +329,7 @@ export class ExtractorService {
return result
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error)
console.error(`[Extractor] Import failed: ${errorMsg}`)
log.error('Import failed', { error: errorMsg })
return {
success: false,
@@ -350,7 +352,7 @@ export class ExtractorService {
filePath: string,
onLog?: (level: LogLevel, message: string) => void
): Promise<ImportResult> {
console.log(`[Extractor] Starting database import from: ${filePath}`)
log.info('Starting database import', { filePath })
onLog?.('info', `开始导入数据到数据库...`)
const importService = new DataImportService()
@@ -358,7 +360,7 @@ export class ExtractorService {
try {
const result = await importService.importFromExcel(filePath, 1000)
console.log(`[Extractor] Import completed`, {
log.info('Import completed', {
success: result.success,
recordsRead: result.recordsRead,
recordsDeleted: result.recordsDeleted,
@@ -377,7 +379,7 @@ export class ExtractorService {
return result
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error)
console.error(`[Extractor] Import failed: ${errorMsg}`)
log.error('Import failed', { error: errorMsg })
onLog?.('error', `导入失败:${errorMsg}`)
return {