- 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>
132 lines
3.7 KiB
TypeScript
132 lines
3.7 KiB
TypeScript
/**
|
|
* 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
|