import { createWriteStream, existsSync, mkdirSync, promises as fs } from 'fs'; import { dirname } from 'path'; import type { AuditEvent, AuditConfig, ToolStartEvent, ToolSuccessEvent, ToolErrorEvent, ToolDeniedEvent, SessionCreateEvent, SessionMessageEvent, SessionDeleteEvent, SessionCompactEvent, CronTriggerEvent, WebhookReceiveEvent, HeartbeatCycleEvent, HeartbeatCheckEvent, HeartbeatFailEvent, HeartbeatRecoverEvent, GmailPollEvent, GmailNewEmailEvent, } from './types.js'; import { AuditRotator } from './rotation.js'; export class AuditLogger { private writeStream: import('fs').WriteStream | null = null; private config: AuditConfig; private rotator: AuditRotator; constructor(config: AuditConfig) { this.config = config; this.rotator = new AuditRotator(config); if (!this.config.enabled) { return; } this.ensureLogDirectory(); this.rotator.checkRotation(); this.writeStream = createWriteStream(config.path, { flags: 'a' }); } private ensureLogDirectory(): void { const logDir = dirname(this.config.path); if (!existsSync(logDir)) { mkdirSync(logDir, { recursive: true }); } } private write(event: Omit): void { if (!this.config.enabled || !this.writeStream) { return; } this.rotator.checkRotation(); const fullEvent: AuditEvent = { ...event, timestamp: Date.now() }; this.writeStream!.write(JSON.stringify(fullEvent) + '\n'); } private shouldLog(category: 'tools' | 'sessions' | 'automation', level: string): boolean { const levelOrder = { debug: 0, info: 1, warn: 2, error: 3 }; const configLevel = this.config.levels[category]; return levelOrder[level as keyof typeof levelOrder] >= levelOrder[configLevel]; } // ── Tool Events ─────────────────────────────────────────────── toolStart(event: ToolStartEvent): void { if (!this.shouldLog('tools', 'debug')) return; this.write({ level: 'debug', event_type: 'tool.start', event: event as unknown as Record }); } toolSuccess(event: ToolSuccessEvent): void { if (!this.shouldLog('tools', 'debug')) return; this.write({ level: 'debug', event_type: 'tool.success', event: event as unknown as Record }); } toolError(event: ToolErrorEvent): void { if (!this.shouldLog('tools', 'error')) return; this.write({ level: 'error', event_type: 'tool.error', event: event as unknown as Record }); } toolDenied(event: ToolDeniedEvent): void { if (!this.shouldLog('tools', 'warn')) return; this.write({ level: 'warn', event_type: 'tool.denied', event: event as unknown as Record }); } // ── Session Events ─────────────────────────────────────────── sessionCreate(event: SessionCreateEvent): void { if (!this.shouldLog('sessions', 'debug')) return; this.write({ level: 'debug', event_type: 'session.create', event: event as unknown as Record }); } sessionMessage(event: SessionMessageEvent): void { if (!this.shouldLog('sessions', 'debug')) return; this.write({ level: 'debug', event_type: 'session.message', event: event as unknown as Record }); } sessionDelete(event: SessionDeleteEvent): void { if (!this.shouldLog('sessions', 'debug')) return; this.write({ level: 'debug', event_type: 'session.delete', event: event as unknown as Record }); } sessionCompact(event: SessionCompactEvent): void { if (!this.shouldLog('sessions', 'debug')) return; this.write({ level: 'debug', event_type: 'session.compact', event: event as unknown as Record }); } sessionTransfer(from: string, to: string, messageCount: number): void { if (!this.shouldLog('sessions', 'debug')) return; this.write({ level: 'debug', event_type: 'session.transfer', event: { from_session: from, to_session: to, message_count: messageCount }, }); } // ── Automation Events ─────────────────────────────────────── // Cron cronTrigger(event: CronTriggerEvent): void { if (!this.shouldLog('automation', 'debug')) return; this.write({ level: 'debug', event_type: 'cron.trigger', event: event as unknown as Record }); } cronAdd(jobName: string, schedule: string): void { if (!this.shouldLog('automation', 'info')) return; this.write({ level: 'info', event_type: 'cron.add', event: { job_name: jobName, schedule }, }); } cronRemove(jobName: string): void { if (!this.shouldLog('automation', 'info')) return; this.write({ level: 'info', event_type: 'cron.remove', event: { job_name: jobName }, }); } // Webhook webhookReceive(event: WebhookReceiveEvent): void { if (!this.shouldLog('automation', 'debug')) return; this.write({ level: 'debug', event_type: 'webhook.receive', event: event as unknown as Record }); } webhookNotFound(webhookName: string): void { if (!this.shouldLog('automation', 'warn')) return; this.write({ level: 'warn', event_type: 'webhook.not_found', event: { webhook_name: webhookName }, }); } webhookDenied(webhookName: string, reason: string): void { if (!this.shouldLog('automation', 'warn')) return; this.write({ level: 'warn', event_type: 'webhook.denied', event: { webhook_name: webhookName, reason }, }); } // Heartbeat heartbeatCycle(event: HeartbeatCycleEvent): void { if (!this.shouldLog('automation', 'debug')) return; this.write({ level: 'debug', event_type: 'heartbeat.cycle', event: event as unknown as Record }); } heartbeatCheck(event: HeartbeatCheckEvent): void { if (!this.shouldLog('automation', 'debug')) return; this.write({ level: 'debug', event_type: 'heartbeat.check', event: event as unknown as Record }); } heartbeatFail(event: HeartbeatFailEvent): void { if (!this.shouldLog('automation', 'warn')) return; this.write({ level: 'warn', event_type: 'heartbeat.fail', event: event as unknown as Record }); } heartbeatRecover(event: HeartbeatRecoverEvent): void { if (!this.shouldLog('automation', 'info')) return; this.write({ level: 'info', event_type: 'heartbeat.recover', event: event as unknown as Record }); } // Gmail gmailPoll(event: GmailPollEvent): void { if (!this.shouldLog('automation', 'debug')) return; this.write({ level: 'debug', event_type: 'gmail.poll', event: event as unknown as Record }); } gmailNewEmail(event: GmailNewEmailEvent): void { if (!this.shouldLog('automation', 'debug')) return; this.write({ level: 'debug', event_type: 'gmail.new_email', event: event as unknown as Record }); } gmailError(error: string, context?: string): void { if (!this.shouldLog('automation', 'error')) return; this.write({ level: 'error', event_type: 'gmail.error', event: { error, context }, }); } // ── System Events ──────────────────────────────────────────── systemStart(component: string, config?: Record): void { if (!this.config.enabled) return; this.write({ level: 'info', event_type: 'system.start', event: { component, config }, }); } systemStop(component: string, reason?: string): void { if (!this.config.enabled) return; this.write({ level: 'info', event_type: 'system.stop', event: { component, reason }, }); } systemConfig(component: string, action: string, config: Record): void { if (!this.config.enabled) return; this.write({ level: 'info', event_type: 'system.config', event: { component, action, config }, }); } // ── Lifecycle ─────────────────────────────────────────────── async close(): Promise { if (this.writeStream) { await new Promise((resolve) => { this.writeStream!.end(() => resolve()); }); this.writeStream = null; } } }