Files
BIPMaterialManager/src/main/services/logger/audit-logger.ts
Misaka 1e0bb1de24 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>
2026-04-04 16:33:03 +08:00

129 lines
3.6 KiB
TypeScript

/**
* Audit Logger Service
* Writes audit logs in JSONL format with 30-day rotation using winston-daily-rotate-file
*/
import winston from 'winston'
import DailyRotateFile from 'winston-daily-rotate-file'
import path from 'path'
import { app } from 'electron'
import { getLogDir } from './shared'
/**
* Audit log entry structure
* All 8 required fields for comprehensive audit tracking
*/
export interface AuditEntry {
/** ISO 8601 timestamp of the audit event */
timestamp: string
/** The action that was performed (e.g., 'LOGIN', 'EXTRACT', 'DELETE') */
action: string
/** User ID who performed the action */
userId: string
/** Username of the user who performed the action */
username: string
/** Computer name from which the action was performed */
computerName: string
/** Application version when the action was performed */
appVersion: string
/** The resource that was affected (e.g., table name, file path) */
resource: string
/** Status of the action: 'success' | 'failure' | 'partial' */
status: 'success' | 'failure' | 'partial'
/** Additional metadata about the audit event */
metadata: Record<string, unknown>
}
/**
* JSONL formatter - outputs one JSON object per line
* This is the key difference from the standard JSON formatter
*/
const jsonlFormat = winston.format.printf(({ message }) => {
// Message should already be a JSON string
return typeof message === 'string' ? message : JSON.stringify(message)
})
/**
* Create the audit logger instance with daily rotation
* Initially silent (no transports). Call applyAuditConfig() after config is loaded.
*/
const auditLogger = winston.createLogger({
level: 'info',
silent: true,
transports: []
})
/**
* Apply audit log retention configuration
* Creates the DailyRotateFile transport with the configured retention period
*
* @param retentionDays - Number of days to retain audit logs
*/
export function applyAuditConfig(retentionDays: number): void {
// Enable logging now that config is loaded
auditLogger.silent = false
// Remove existing DailyRotateFile transports
const existingTransports = auditLogger.transports.filter((t) => t instanceof DailyRotateFile)
for (const transport of existingTransports) {
auditLogger.remove(transport)
}
// Add audit transport with configured retention
auditLogger.add(
new DailyRotateFile({
filename: path.join(getLogDir(), 'audit-%DATE%.jsonl'),
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: `${retentionDays}d`,
level: 'info',
format: jsonlFormat
})
)
}
/**
* Log an audit event
*
* @param action - The action that was performed
* @param userId - User ID who performed the action
* @param details - Additional details including username, computerName, resource, status, and optional metadata
*/
export function logAudit(
action: string,
userId: string,
details: {
username: string
computerName: string
resource: string
status: 'success' | 'failure' | 'partial'
metadata?: Record<string, unknown>
}
): void {
const entry: AuditEntry = {
timestamp: new Date().toISOString(),
action,
userId,
username: details.username,
computerName: details.computerName,
appVersion: app.getVersion(),
resource: details.resource,
status: details.status,
metadata: details.metadata || {}
}
// Write as JSONL - one JSON object per line
// Using info level with the entry stringified as the message
auditLogger.info(JSON.stringify(entry))
}
/**
* Flush and close the audit logger (call on app shutdown)
*/
export function closeAuditLogger(): void {
auditLogger.close()
}
export default auditLogger