/** * Audit Logger - Phase 8 Enterprise Pattern * * Tracks all state changes, user actions, and workflow transitions * for compliance, debugging, and analytics. */ class AuditLogger { constructor() { this.logs = []; this.maxLogs = 1000; this.storageKey = 'dss-audit-logs'; this.sessionId = this.generateSessionId(); this.logLevel = 'info'; // 'debug', 'info', 'warn', 'error' this.loadFromStorage(); } /** * Generate unique session ID */ generateSessionId() { return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } /** * Create audit log entry */ createLogEntry(action, category, details = {}, level = 'info') { return { id: `log-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, timestamp: new Date().toISOString(), sessionId: this.sessionId, action, category, level, details, userAgent: navigator.userAgent, }; } /** * Log user action */ logAction(action, details = {}) { const entry = this.createLogEntry(action, 'user_action', details, 'info'); this.addLog(entry); return entry.id; } /** * Log state change */ logStateChange(key, oldValue, newValue, details = {}) { const entry = this.createLogEntry( `state_change`, 'state', { key, oldValue: this.sanitize(oldValue), newValue: this.sanitize(newValue), ...details }, 'info' ); this.addLog(entry); return entry.id; } /** * Log API call */ logApiCall(method, endpoint, status, responseTime = 0, details = {}) { const entry = this.createLogEntry( `api_${method.toLowerCase()}`, 'api', { endpoint, method, status, responseTime, ...details }, status >= 400 ? 'warn' : 'info' ); this.addLog(entry); return entry.id; } /** * Log error */ logError(error, context = '') { const entry = this.createLogEntry( 'error', 'error', { message: error.message, stack: error.stack, context }, 'error' ); this.addLog(entry); console.error('[AuditLogger]', error); return entry.id; } /** * Log warning */ logWarning(message, details = {}) { const entry = this.createLogEntry( 'warning', 'warning', { message, ...details }, 'warn' ); this.addLog(entry); return entry.id; } /** * Log permission check */ logPermissionCheck(action, allowed, user, reason = '') { const entry = this.createLogEntry( 'permission_check', 'security', { action, allowed, user, reason }, allowed ? 'info' : 'warn' ); this.addLog(entry); return entry.id; } /** * Add log entry to collection */ addLog(entry) { this.logs.unshift(entry); if (this.logs.length > this.maxLogs) { this.logs.pop(); } this.saveToStorage(); } /** * Sanitize sensitive data before logging */ sanitize(value) { if (typeof value !== 'object') return value; const sanitized = { ...value }; const sensitiveKeys = ['password', 'token', 'apiKey', 'secret', 'key']; for (const key of Object.keys(sanitized)) { if (sensitiveKeys.some(sk => key.toLowerCase().includes(sk))) { sanitized[key] = '***REDACTED***'; } } return sanitized; } /** * Get logs filtered by criteria */ getLogs(filters = {}) { let result = [...this.logs]; if (filters.action) { result = result.filter(l => l.action === filters.action); } if (filters.category) { result = result.filter(l => l.category === filters.category); } if (filters.level) { result = result.filter(l => l.level === filters.level); } if (filters.startTime) { result = result.filter(l => new Date(l.timestamp) >= new Date(filters.startTime)); } if (filters.endTime) { result = result.filter(l => new Date(l.timestamp) <= new Date(filters.endTime)); } if (filters.sessionId) { result = result.filter(l => l.sessionId === filters.sessionId); } if (filters.limit) { result = result.slice(0, filters.limit); } return result; } /** * Get statistics */ getStats() { return { totalLogs: this.logs.length, sessionId: this.sessionId, byCategory: this.logs.reduce((acc, log) => { acc[log.category] = (acc[log.category] || 0) + 1; return acc; }, {}), byLevel: this.logs.reduce((acc, log) => { acc[log.level] = (acc[log.level] || 0) + 1; return acc; }, {}), oldestLog: this.logs[this.logs.length - 1]?.timestamp, newestLog: this.logs[0]?.timestamp, }; } /** * Export logs as JSON */ exportLogs(filters = {}) { const logs = this.getLogs(filters); return JSON.stringify({ exportDate: new Date().toISOString(), sessionId: this.sessionId, count: logs.length, logs }, null, 2); } /** * Clear all logs */ clearLogs() { this.logs = []; this.saveToStorage(); } /** * Save logs to localStorage */ saveToStorage() { try { localStorage.setItem(this.storageKey, JSON.stringify(this.logs)); } catch (e) { console.warn('[AuditLogger] Failed to save to storage:', e); } } /** * Load logs from localStorage */ loadFromStorage() { try { const stored = localStorage.getItem(this.storageKey); if (stored) { this.logs = JSON.parse(stored); } } catch (e) { console.warn('[AuditLogger] Failed to load from storage:', e); } } } // Create and export singleton const auditLogger = new AuditLogger(); export { AuditLogger }; export default auditLogger;