/**
* Audit Trail
*
* Comprehensive audit logging for all operations.
* Tracks every action with correlation IDs for complete traceability.
*/
import { logger } from './logger.js';
/**
* Audit Trail Class
*/
export class AuditTrail {
constructor() {
this.enabled = true;
this.buffer = [];
this.bufferSize = 100;
this.flushInterval = 30000; // 30 seconds
this.flushTimer = null;
this.startFlushTimer();
}
/**
* Log an audit entry
* @param {Object} entry - Audit entry
* @returns {Promise<void>}
*/
async log(entry) {
if (!this.enabled) {
return;
}
// Add timestamp if not present
if (!entry.timestamp) {
entry.timestamp = new Date().toISOString();
}
// Add environment
if (typeof globalThis !== 'undefined' && globalThis.ENV) {
entry.environment = globalThis.ENV.ENVIRONMENT;
}
// Buffer the entry
this.buffer.push(entry);
// Flush if buffer is full
if (this.buffer.length >= this.bufferSize) {
await this.flush();
}
// Also log to console for immediate visibility
logger.info('Audit log entry', {
correlationId: entry.correlationId,
action: entry.action,
success: entry.success
});
}
/**
* Flush buffered entries
* @returns {Promise<void>}
*/
async flush() {
if (this.buffer.length === 0) {
return;
}
const entries = [...this.buffer];
this.buffer = [];
try {
// In production, you would send this to an audit log service
// For now, we'll log to console
for (const entry of entries) {
logger.info('Audit entry flushed', entry);
}
logger.info('Audit trail flushed', {
count: entries.length
});
} catch (error) {
logger.error('Failed to flush audit trail', {
error: error.message,
count: entries.length
});
// Re-buffer failed entries
this.buffer = [...entries, ...this.buffer];
}
}
/**
* Start flush timer
*/
startFlushTimer() {
if (typeof setInterval !== 'undefined') {
this.flushTimer = setInterval(() => {
this.flush().catch(error => {
logger.error('Scheduled flush failed', { error: error.message });
});
}, this.flushInterval);
}
}
/**
* Stop flush timer
*/
stopFlushTimer() {
if (this.flushTimer) {
clearInterval(this.flushTimer);
this.flushTimer = null;
}
}
/**
* Enable audit logging
*/
enable() {
this.enabled = true;
logger.info('Audit trail enabled');
}
/**
* Disable audit logging
*/
disable() {
this.enabled = false;
logger.info('Audit trail disabled');
}
/**
* Force immediate flush
* @returns {Promise<void>}
*/
async forceFlush() {
await this.flush();
}
/**
* Get buffer statistics
* @returns {Object} Buffer stats
*/
getBufferStats() {
return {
size: this.buffer.length,
maxSize: this.bufferSize,
flushInterval: this.flushInterval,
enabled: this.enabled
};
}
/**
* Clear buffer
*/
clearBuffer() {
this.buffer = [];
logger.info('Audit buffer cleared');
}
/**
* Get recent audit entries
* @param {number} limit - Number of entries to return
* @returns {Array} Recent entries
*/
getRecentEntries(limit = 10) {
return this.buffer.slice(-limit);
}
/**
* Query audit entries by correlation ID
* @param {string} correlationId - Correlation ID to search for
* @returns {Array} Matching entries
*/
queryByCorrelationId(correlationId) {
return this.buffer.filter(entry => entry.correlationId === correlationId);
}
/**
* Query audit entries by user ID
* @param {string} userId - User ID to search for
* @returns {Array} Matching entries
*/
queryByUserId(userId) {
return this.buffer.filter(entry => entry.userId === userId);
}
/**
* Query audit entries by action
* @param {string} action - Action to search for
* @returns {Array} Matching entries
*/
queryByAction(action) {
return this.buffer.filter(entry => entry.action === action);
}
/**
* Query audit entries by success status
* @param {boolean} success - Success status to filter by
* @returns {Array} Matching entries
*/
queryBySuccess(success) {
return this.buffer.filter(entry => entry.success === success);
}
/**
* Get audit statistics
* @returns {Object} Statistics
*/
getStatistics() {
const stats = {
total: this.buffer.length,
successful: 0,
failed: 0,
byAction: {},
byUser: {}
};
for (const entry of this.buffer) {
// Count success/failure
if (entry.success) {
stats.successful++;
} else {
stats.failed++;
}
// Count by action
if (entry.action) {
stats.byAction[entry.action] = (stats.byAction[entry.action] || 0) + 1;
}
// Count by user
if (entry.userId) {
stats.byUser[entry.userId] = (stats.byUser[entry.userId] || 0) + 1;
}
}
return stats;
}
}
// Export singleton instance
export const auditTrail = new AuditTrail();