feat(logging): add Seq transport, global meta fields, and improve error handling

- Add Seq centralized logging transport with async ESM import
- Add appVersion and computerName to logger defaultMeta (all app logs)
- Add appVersion to audit log entries for version-level traceability
- Improve unhandledRejection to capture full stack traces for Error instances
- Add Seq config schema and template configuration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-04-04 16:33:03 +08:00
parent fce8dbc37f
commit 1e0bb1de24
10 changed files with 336 additions and 31 deletions

View File

@@ -11,11 +11,14 @@
import winston from 'winston'
import DailyRotateFile from 'winston-daily-rotate-file'
import path from 'path'
import { BrowserWindow } from 'electron'
import os from 'os'
import { app, BrowserWindow } from 'electron'
import { serializeError, sanitizeError } from './error-utils'
import { getLogDir, isProduction, cleanupOldScreenshots } from './shared'
import { IPC_CHANNELS } from '../../../shared/ipc-channels'
import { getContext, run } from './request-context'
import { createSeqTransportSync } from './seq-transport'
import type { SeqConfig } from '../../types/config.schema'
// Cache isProduction() at module load — app.isPackaged never changes at runtime
const IS_PROD = isProduction()
@@ -154,7 +157,11 @@ const createFileTransport = (level?: string, maxFiles?: string): DailyRotateFile
// File transports are added after config is loaded via applyLoggingConfig()
const logger = winston.createLogger({
level: 'info', // Default level, can be updated via setLogLevel()
defaultMeta: { service: 'erpauto' },
defaultMeta: {
service: 'erpauto',
appVersion: app.getVersion(),
computerName: os.hostname()
},
transports: [
// Console transport - always enabled
new winston.transports.Console({
@@ -181,10 +188,15 @@ export function setLogLevel(level: string): void {
/**
* Apply logging configuration from config file
* Removes existing DailyRotateFile transports and recreates them with config values
* Also configures Seq transport if enabled
*
* @param config - Logging configuration from config.yaml
* @param seqConfig - Optional Seq configuration from config.yaml
*/
export function applyLoggingConfig(config: { level: string; appRetention: number }): void {
export function applyLoggingConfig(
config: { level: string; appRetention: number },
seqConfig?: SeqConfig
): void {
// Update log level
setLogLevel(config.level)
@@ -213,6 +225,47 @@ export function applyLoggingConfig(config: { level: string; appRetention: number
)
}
// Add Seq transport if configured and enabled
// Note: Uses sync wrapper which initializes asynchronously
if (seqConfig && seqConfig.enabled && seqConfig.serverUrl) {
try {
const seqTransport = createSeqTransportSync(seqConfig)
if (seqTransport) {
logger.add(seqTransport)
logger.info('Seq transport added successfully', {
serverUrl: seqConfig.serverUrl,
batchPostingLimit: seqConfig.batchPostingLimit,
period: seqConfig.period,
context: 'seq-transport'
})
} else {
// Transport not ready yet, will be initialized on next call
logger.debug('Seq transport initialization in progress', {
serverUrl: seqConfig.serverUrl,
context: 'seq-transport'
})
// Initialize and add when ready
import('./seq-transport').then(({ createSeqTransport }) => {
createSeqTransport(seqConfig).then((transport) => {
if (transport) {
logger.add(transport)
logger.info('Seq transport added (async init complete)', {
serverUrl: seqConfig.serverUrl,
context: 'seq-transport'
})
}
})
})
}
} catch (error) {
logger.error('Failed to add Seq transport', {
error: error instanceof Error ? error.message : String(error),
context: 'seq-transport'
})
// Don't throw - allow app to continue without Seq
}
}
// Clean up old screenshot files beyond the retention window
cleanupOldScreenshots(config.appRetention)
}