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:
131
src/main/services/logger/seq-transport.ts
Normal file
131
src/main/services/logger/seq-transport.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Seq logging transport using @datalust/winston-seq
|
||||
*
|
||||
* Provides asynchronous batched logging to Seq server
|
||||
* with configurable parameters for production use.
|
||||
*
|
||||
* Features:
|
||||
* - Non-blocking async batch sending (via dynamic import)
|
||||
* - Configurable batch size, period, queue limits
|
||||
* - Automatic retry on failure
|
||||
* - Graceful shutdown with flush
|
||||
* - Error suppression (Seq failures don't crash app)
|
||||
*
|
||||
* Note: Uses dynamic import because @datalust/winston-seq is an ESM module
|
||||
*/
|
||||
|
||||
import type { SeqConfig } from '../../types/config.schema'
|
||||
import logger from './index'
|
||||
|
||||
/**
|
||||
* Seq transport constructor type (from dynamic import)
|
||||
*/
|
||||
type SeqTransportClass = new (options: {
|
||||
serverUrl: string
|
||||
apiKey?: string
|
||||
batchSizeLimit?: number
|
||||
maxBatchingTime?: number
|
||||
maxRetries?: number
|
||||
onError?: (error: Error) => void
|
||||
}) => any
|
||||
|
||||
/**
|
||||
* Create and configure Seq transport dynamically
|
||||
*
|
||||
* This function handles the ESM module loading and returns
|
||||
* a configured transport instance compatible with Winston.
|
||||
*
|
||||
* @param config - Seq configuration from config.schema
|
||||
* @returns Promise resolving to transport instance or null if disabled/failed
|
||||
*/
|
||||
export async function createSeqTransport(config: SeqConfig): Promise<any> {
|
||||
// Don't create transport if disabled
|
||||
if (!config.enabled || !config.serverUrl) {
|
||||
logger.info('Seq logging is disabled or server URL not provided', {
|
||||
enabled: config.enabled,
|
||||
hasServerUrl: !!config.serverUrl,
|
||||
context: 'seq-transport'
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
// Dynamically import the ESM module
|
||||
const { SeqTransport } = await import('@datalust/winston-seq')
|
||||
const SeqTransportClass = SeqTransport as SeqTransportClass
|
||||
|
||||
// Create transport instance with mapped config
|
||||
const transport = new SeqTransportClass({
|
||||
serverUrl: config.serverUrl,
|
||||
apiKey: config.apiKey || undefined,
|
||||
batchSizeLimit: config.batchPostingLimit,
|
||||
maxBatchingTime: config.period,
|
||||
maxRetries: config.maxRetries,
|
||||
onError: (error: Error) => {
|
||||
// Log errors but don't throw - Seq failures should not crash app
|
||||
logger.error('Seq transport error', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
context: 'seq-transport'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
logger.info('Seq transport created successfully', {
|
||||
serverUrl: config.serverUrl,
|
||||
batchPostingLimit: config.batchPostingLimit,
|
||||
period: config.period,
|
||||
maxRetries: config.maxRetries,
|
||||
context: 'seq-transport'
|
||||
})
|
||||
|
||||
return transport
|
||||
} catch (error) {
|
||||
logger.error('Failed to create Seq transport', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
context: 'seq-transport'
|
||||
})
|
||||
// Return null to allow app to continue without Seq
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync factory wrapper for backward compatibility
|
||||
*
|
||||
* Note: This creates the transport asynchronously internally.
|
||||
* The transport is cached and reused.
|
||||
*/
|
||||
let cachedTransport: any = null
|
||||
let isInitializing = false
|
||||
|
||||
export function createSeqTransportSync(config: SeqConfig): any {
|
||||
if (!config.enabled || !config.serverUrl) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (cachedTransport) {
|
||||
return cachedTransport
|
||||
}
|
||||
|
||||
if (!isInitializing) {
|
||||
isInitializing = true
|
||||
createSeqTransport(config)
|
||||
.then((transport) => {
|
||||
cachedTransport = transport
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Seq transport initialization failed', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
isInitializing = false
|
||||
})
|
||||
}
|
||||
|
||||
// Return null on first call, transport will be available on next call
|
||||
return null
|
||||
}
|
||||
|
||||
export default createSeqTransportSync
|
||||
Reference in New Issue
Block a user