/** * Audit Logging Augmentation * Provides comprehensive audit trail for all Brainy operations */ import { BaseAugmentation } from './brainyAugmentation.js' import { AugmentationManifest } from './manifest.js' import { createHash } from '../universal/crypto.js' export interface AuditLogConfig { enabled?: boolean logLevel?: 'minimal' | 'standard' | 'detailed' includeData?: boolean includeMetadata?: boolean retention?: number // Days to keep logs storage?: 'memory' | 'file' | 'database' filePath?: string maxMemoryLogs?: number } export interface AuditLogEntry { id: string timestamp: number operation: string params: any result?: any error?: any duration: number userId?: string sessionId?: string metadata?: Record } /** * Audit Log Augmentation */ export class AuditLogAugmentation extends BaseAugmentation { readonly name = 'auditLogger' readonly timing = 'around' as const readonly metadata = 'readonly' as const // Read metadata for context operations = ['all'] as any // Audit all operations readonly priority = 90 // Low priority, runs last // Augmentation metadata readonly category = 'core' as const readonly description = 'Comprehensive audit logging for compliance and debugging' private logs: AuditLogEntry[] = [] private sessionId: string constructor(config: AuditLogConfig = {}) { super(config) // Merge with defaults this.config = { enabled: config.enabled ?? true, logLevel: config.logLevel ?? 'standard', includeData: config.includeData ?? false, includeMetadata: config.includeMetadata ?? true, retention: config.retention ?? 90, // 90 days default storage: config.storage ?? 'memory', filePath: config.filePath, maxMemoryLogs: config.maxMemoryLogs ?? 10000 } // Generate session ID this.sessionId = this.generateId() } getManifest(): AugmentationManifest { return { id: 'audit-logger', name: 'Audit Logger', version: '1.0.0', description: 'Comprehensive audit trail for all operations', longDescription: 'Records detailed audit logs of all Brainy operations for compliance, debugging, and analytics purposes.', category: 'analytics', configSchema: { type: 'object', properties: { enabled: { type: 'boolean', default: true, description: 'Enable audit logging' }, logLevel: { type: 'string', enum: ['minimal', 'standard', 'detailed'], default: 'standard', description: 'Level of detail to log' }, includeData: { type: 'boolean', default: false, description: 'Include actual data in logs (privacy concern)' }, includeMetadata: { type: 'boolean', default: true, description: 'Include metadata in logs' }, retention: { type: 'number', default: 90, minimum: 1, maximum: 365, description: 'Days to retain logs' }, storage: { type: 'string', enum: ['memory', 'file', 'database'], default: 'memory', description: 'Where to store audit logs' }, maxMemoryLogs: { type: 'number', default: 10000, description: 'Maximum logs to keep in memory' } } }, configDefaults: { enabled: true, logLevel: 'standard', includeData: false, includeMetadata: true, retention: 90, storage: 'memory', maxMemoryLogs: 10000 }, minBrainyVersion: '3.0.0', keywords: ['audit', 'logging', 'compliance', 'analytics'], documentation: 'https://docs.brainy.dev/augmentations/audit-log', status: 'stable', performance: { memoryUsage: 'medium', cpuUsage: 'low', networkUsage: 'none' }, features: ['operation-logging', 'configurable-detail', 'retention-management'], enhancedOperations: ['all'], metrics: [ { name: 'audit_logs_created', type: 'counter', description: 'Total audit logs created' }, { name: 'audit_log_size', type: 'gauge', description: 'Current audit log size' } ] } } protected async onInitialize(): Promise { if (!this.config.enabled) { this.log('Audit logger disabled by configuration') return } this.log(`Audit logger initialized (level: ${this.config.logLevel}, storage: ${this.config.storage})`) // Start retention cleanup if using memory storage if (this.config.storage === 'memory') { setInterval(() => { this.cleanupOldLogs() }, 3600000) // Every hour } } protected async onShutdown(): Promise { // Save any pending logs if using file storage if (this.config.storage === 'file' && this.logs.length > 0) { await this.flushLogs() } this.log('Audit logger shut down') } /** * Execute augmentation - log operations */ async execute( operation: string, params: any, next: () => Promise ): Promise { // If audit logging is disabled, just pass through if (!this.config.enabled) { return next() } const startTime = Date.now() const logEntry: Partial = { id: this.generateId(), timestamp: startTime, operation, sessionId: this.sessionId } // Add params based on log level if (this.config.logLevel !== 'minimal') { logEntry.params = this.sanitizeParams(params) } try { const result = await next() // Log successful operation logEntry.duration = Date.now() - startTime // Add result based on log level and config if (this.config.logLevel === 'detailed' && this.config.includeData) { logEntry.result = this.sanitizeResult(result) } await this.writeLog(logEntry as AuditLogEntry) return result } catch (error) { // Log failed operation logEntry.duration = Date.now() - startTime logEntry.error = this.sanitizeError(error) await this.writeLog(logEntry as AuditLogEntry) throw error } } /** * Sanitize parameters to remove sensitive data */ private sanitizeParams(params: any): any { if (!params) return params // Don't include actual data unless configured if (!this.config.includeData && params.data) { return { ...params, data: '[REDACTED]' } } // Redact common sensitive fields const sanitized = { ...params } const sensitiveFields = ['password', 'token', 'apiKey', 'secret'] for (const field of sensitiveFields) { if (sanitized[field]) { sanitized[field] = '[REDACTED]' } } return sanitized } /** * Sanitize result data */ private sanitizeResult(result: any): any { if (!result) return result // For arrays, just log count if (Array.isArray(result)) { return { count: result.length, type: 'array' } } // For objects, remove sensitive fields if (typeof result === 'object') { const sanitized: any = {} for (const key in result) { if (!['password', 'token', 'apiKey', 'secret'].includes(key)) { sanitized[key] = result[key] } } return sanitized } return result } /** * Sanitize error information */ private sanitizeError(error: any): any { if (!error) return error return { message: error.message || 'Unknown error', code: error.code, statusCode: error.statusCode, stack: this.config.logLevel === 'detailed' ? error.stack : undefined } } /** * Write log entry */ private async writeLog(entry: AuditLogEntry): Promise { switch (this.config.storage) { case 'memory': this.logs.push(entry) // Enforce max memory logs if (this.logs.length > this.config.maxMemoryLogs!) { this.logs.shift() // Remove oldest } break case 'file': // In production, would write to file // For now, just add to memory this.logs.push(entry) break case 'database': // In production, would write to database // For now, just add to memory this.logs.push(entry) break } } /** * Flush logs to persistent storage */ private async flushLogs(): Promise { // In production, would write to file/database // For now, just clear old logs if (this.logs.length > this.config.maxMemoryLogs!) { this.logs = this.logs.slice(-this.config.maxMemoryLogs!) } } /** * Clean up old logs based on retention */ private cleanupOldLogs(): void { const cutoffTime = Date.now() - (this.config.retention! * 24 * 60 * 60 * 1000) this.logs = this.logs.filter(log => log.timestamp >= cutoffTime) } /** * Generate unique ID */ private generateId(): string { return createHash('sha256') .update(`${Date.now()}-${Math.random()}`) .digest('hex') .substring(0, 16) } /** * Query audit logs */ queryLogs(filter?: { operation?: string startTime?: number endTime?: number sessionId?: string hasError?: boolean }): AuditLogEntry[] { let results = [...this.logs] if (filter) { if (filter.operation) { results = results.filter(log => log.operation === filter.operation) } if (filter.startTime) { results = results.filter(log => log.timestamp >= filter.startTime!) } if (filter.endTime) { results = results.filter(log => log.timestamp <= filter.endTime!) } if (filter.sessionId) { results = results.filter(log => log.sessionId === filter.sessionId) } if (filter.hasError !== undefined) { results = results.filter(log => (log.error !== undefined) === filter.hasError) } } return results } /** * Get audit statistics */ getStats(): { totalLogs: number operations: Record averageDuration: number errorRate: number } { const stats: any = { totalLogs: this.logs.length, operations: {}, averageDuration: 0, errorRate: 0 } let totalDuration = 0 let errorCount = 0 for (const log of this.logs) { // Count by operation stats.operations[log.operation] = (stats.operations[log.operation] || 0) + 1 // Sum duration totalDuration += log.duration // Count errors if (log.error) errorCount++ } if (this.logs.length > 0) { stats.averageDuration = totalDuration / this.logs.length stats.errorRate = errorCount / this.logs.length } return stats } /** * Export logs for analysis */ exportLogs(): AuditLogEntry[] { return [...this.logs] } /** * Clear all logs */ clearLogs(): void { this.logs = [] } } /** * Create audit log augmentation */ export function createAuditLogAugmentation(config?: AuditLogConfig): AuditLogAugmentation { return new AuditLogAugmentation(config) }