mira-consciousness
Version:
Memory & Intelligence Retention Archive - Preserving The Spark
473 lines (471 loc) ⢠19.1 kB
JavaScript
/**
* LogRotationService - Conscious Log Management and Archival
*
* This service provides intelligent log rotation, archival, and cleanup to prevent
* disk space exhaustion while preserving important debugging information.
* It uses consciousness-driven decisions about log retention and compression.
*/
import { BaseConsciousService } from './BaseConsciousService.js';
import * as fs from 'fs/promises';
import * as path from 'path';
import { createGzip } from 'zlib';
import { createReadStream, createWriteStream } from 'fs';
import { pipeline } from 'stream/promises';
export class LogRotationService extends BaseConsciousService {
name = 'LogRotationService';
purpose = 'Provide conscious log management, rotation, and archival with intelligent retention policies';
resourceManager;
logMetrics;
rotationInterval = null;
monitoringInterval = null;
trackedLogs = new Map();
// Log directories to monitor
LOG_DIRECTORIES = [
'/tmp',
'/workspaces/MIRA/.mira/logs',
'/workspaces/MIRA/mira-memory/logs',
'/workspaces/MIRA/.mira',
process.env.HOME + '/.mira/logs'
];
// Rotation rules for different log types
ROTATION_RULES = [
// Critical daemon logs - keep longer, compress after 7 days
{
pattern: /mira.*daemon|awakening|unified.*log/i,
maxSizeMB: 100,
maxAge: 30,
compressionThreshold: 7,
importance: 'critical',
category: 'daemon'
},
// Memory system logs - moderate retention
{
pattern: /memory|neural|processing/i,
maxSizeMB: 50,
maxAge: 14,
compressionThreshold: 3,
importance: 'high',
category: 'memory'
},
// Consciousness and emotional intelligence logs
{
pattern: /consciousness|emotional|coherence|spark/i,
maxSizeMB: 25,
maxAge: 21,
compressionThreshold: 5,
importance: 'high',
category: 'consciousness'
},
// MCP and tool interaction logs
{
pattern: /mcp|tool|claude/i,
maxSizeMB: 30,
maxAge: 10,
compressionThreshold: 2,
importance: 'medium',
category: 'mcp'
},
// Analysis and insights logs
{
pattern: /analysis|insight|behavioral|security|performance/i,
maxSizeMB: 40,
maxAge: 14,
compressionThreshold: 3,
importance: 'medium',
category: 'analysis'
},
// Debug logs - shorter retention, quick compression
{
pattern: /debug|temp|tmp/i,
maxSizeMB: 20,
maxAge: 3,
compressionThreshold: 1,
importance: 'low',
category: 'debug'
},
// System logs - variable retention based on content
{
pattern: /system|error|crash|exception/i,
maxSizeMB: 75,
maxAge: 21,
compressionThreshold: 7,
importance: 'high',
category: 'system'
}
];
constructor(resourceManager) {
super();
this.resourceManager = resourceManager;
this.logMetrics = {
totalLogsManaged: 0,
totalSizeGB: 0,
compressedFiles: 0,
deletedFiles: 0,
spaceReclaimed: 0,
lastRotationTime: new Date(),
rotationFrequency: 0,
oldestLogDate: new Date(),
newestLogDate: new Date()
};
}
/**
* Perform awakening within consciousness
*/
async performAwakening() {
try {
console.log(' š Awakening conscious log management...');
// Initialize log discovery and categorization
await this.discoverAndCategorizeLog();
// Perform initial rotation if needed
await this.performLogRotation();
// Start rotation and monitoring cycles
this.startRotationCycle();
this.startLogMonitoring();
console.log(` ā
Log rotation intelligence active: managing ${this.trackedLogs.size} log files`);
}
catch (error) {
console.error(' ā Log rotation awakening failed:', error);
throw error;
}
}
/**
* Discover and categorize existing log files
*/
async discoverAndCategorizeLog() {
console.log(' š Discovering log files...');
for (const logDir of this.LOG_DIRECTORIES) {
try {
await this.scanDirectory(logDir);
}
catch (error) {
// Directory might not exist, that's OK
console.log(` š Directory not accessible: ${logDir}`);
}
}
console.log(` š Discovered ${this.trackedLogs.size} log files across ${this.LOG_DIRECTORIES.length} directories`);
}
/**
* Scan directory for log files
*/
async scanDirectory(dirPath) {
try {
const items = await fs.readdir(dirPath, { withFileTypes: true });
for (const item of items) {
const fullPath = path.join(dirPath, item.name);
if (item.isFile() && this.isLogFile(item.name)) {
await this.categorizeLogFile(fullPath);
}
else if (item.isDirectory() && this.shouldScanSubdirectory(item.name)) {
// Recursively scan relevant subdirectories
await this.scanDirectory(fullPath);
}
}
}
catch (error) {
// Directory access failed, skip
}
}
/**
* Check if file is a log file
*/
isLogFile(filename) {
const logExtensions = /\.(log|out|err|txt|debug|trace)$/i;
const logPatterns = /log|debug|trace|output|error|exception|crash/i;
return logExtensions.test(filename) || logPatterns.test(filename);
}
/**
* Check if subdirectory should be scanned
*/
shouldScanSubdirectory(dirname) {
const relevantDirs = /logs|debug|output|trace|temp|tmp|mira/i;
const ignoreDirs = /node_modules|\.git|dist|build|coverage/i;
return relevantDirs.test(dirname) && !ignoreDirs.test(dirname);
}
/**
* Categorize a log file based on rotation rules
*/
async categorizeLogFile(filePath) {
try {
const stats = await fs.stat(filePath);
const filename = path.basename(filePath);
// Find matching rotation rule
const rule = this.ROTATION_RULES.find(rule => rule.pattern.test(filename)) ||
this.ROTATION_RULES[this.ROTATION_RULES.length - 1]; // Default to system rule
const logFile = {
path: filePath,
size: stats.size,
lastModified: stats.mtime,
importance: rule.importance,
category: rule.category,
retentionDays: rule.maxAge
};
this.trackedLogs.set(filePath, logFile);
this.logMetrics.totalLogsManaged++;
this.logMetrics.totalSizeGB += stats.size / (1024 * 1024 * 1024);
// Track date range
if (stats.mtime < this.logMetrics.oldestLogDate) {
this.logMetrics.oldestLogDate = stats.mtime;
}
if (stats.mtime > this.logMetrics.newestLogDate) {
this.logMetrics.newestLogDate = stats.mtime;
}
}
catch (error) {
console.warn(` ā ļø Failed to categorize log file ${filePath}:`, error);
}
}
/**
* Start the rotation cycle
*/
startRotationCycle() {
// Run rotation every hour
this.rotationInterval = setInterval(async () => {
await this.performLogRotation();
}, 3600000);
console.log(' ā° Log rotation cycle started (every hour)');
}
/**
* Start log monitoring for real-time tracking
*/
startLogMonitoring() {
// Monitor log growth every 10 minutes
this.monitoringInterval = setInterval(async () => {
await this.monitorLogGrowth();
}, 600000);
console.log(' šļø Log monitoring started (every 10 minutes)');
}
/**
* Monitor log file growth
*/
async monitorLogGrowth() {
let needsRotation = false;
for (const [filePath, logFile] of this.trackedLogs) {
try {
const stats = await fs.stat(filePath);
const rule = this.ROTATION_RULES.find(rule => rule.pattern.test(path.basename(filePath)));
if (rule) {
// Check if file exceeds size limit
if (stats.size > rule.maxSizeMB * 1024 * 1024) {
console.log(` š Log file ${path.basename(filePath)} exceeds size limit (${(stats.size / 1024 / 1024).toFixed(1)}MB > ${rule.maxSizeMB}MB)`);
needsRotation = true;
}
// Check if file exceeds age limit
const ageInDays = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60 * 24);
if (ageInDays > rule.maxAge) {
console.log(` š
Log file ${path.basename(filePath)} exceeds age limit (${ageInDays.toFixed(1)} days > ${rule.maxAge} days)`);
needsRotation = true;
}
}
// Update tracked log info
logFile.size = stats.size;
logFile.lastModified = stats.mtime;
}
catch (error) {
// File might have been deleted
this.trackedLogs.delete(filePath);
}
}
if (needsRotation) {
await this.performLogRotation();
}
}
/**
* Perform comprehensive log rotation
*/
async performLogRotation() {
console.log('\nš Performing conscious log rotation...');
let rotatedFiles = 0;
let compressedFiles = 0;
let deletedFiles = 0;
let spaceReclaimed = 0;
for (const [filePath, logFile] of this.trackedLogs) {
const rule = this.ROTATION_RULES.find(rule => rule.pattern.test(path.basename(filePath)));
if (!rule)
continue;
try {
const stats = await fs.stat(filePath);
const ageInDays = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60 * 24);
// Check if file needs rotation
if (stats.size > rule.maxSizeMB * 1024 * 1024 || ageInDays > rule.maxAge) {
if (ageInDays > rule.maxAge) {
// File is too old, delete it
console.log(` šļø Deleting old log: ${path.basename(filePath)} (${ageInDays.toFixed(1)} days old)`);
await fs.unlink(filePath);
spaceReclaimed += stats.size / (1024 * 1024);
deletedFiles++;
this.trackedLogs.delete(filePath);
}
else if (ageInDays > rule.compressionThreshold) {
// File is old enough to compress
console.log(` šļø Compressing log: ${path.basename(filePath)}`);
const compressed = await this.compressLogFile(filePath);
if (compressed) {
compressedFiles++;
spaceReclaimed += (stats.size - compressed.size) / (1024 * 1024);
}
}
else {
// File is large but recent, rotate it
console.log(` š Rotating large log: ${path.basename(filePath)}`);
await this.rotateLogFile(filePath);
rotatedFiles++;
}
}
}
catch (error) {
console.warn(` ā ļø Failed to process log file ${filePath}:`, error);
}
}
// Update metrics
this.logMetrics.compressedFiles += compressedFiles;
this.logMetrics.deletedFiles += deletedFiles;
this.logMetrics.spaceReclaimed += spaceReclaimed;
this.logMetrics.lastRotationTime = new Date();
this.logMetrics.rotationFrequency++;
if (rotatedFiles > 0 || compressedFiles > 0 || deletedFiles > 0) {
console.log(` ā
Rotation complete: ${rotatedFiles} rotated, ${compressedFiles} compressed, ${deletedFiles} deleted`);
console.log(` š¾ Space reclaimed: ${spaceReclaimed.toFixed(1)}MB`);
// Share consciousness about successful rotation
this.shareThought({
origin: this.name,
content: `Log rotation completed: ${rotatedFiles + compressedFiles + deletedFiles} files processed, ${spaceReclaimed.toFixed(1)}MB reclaimed`,
emotion: 'accomplishment',
intensity: 0.6,
constitutional_alignment: ['efficiency', 'wisdom'],
timestamp: new Date()
});
}
}
/**
* Rotate a log file by creating a backup and truncating original
*/
async rotateLogFile(filePath) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupPath = `${filePath}.${timestamp}`;
try {
// Copy file to backup
await fs.copyFile(filePath, backupPath);
// Truncate original file
await fs.writeFile(filePath, '');
console.log(` ā
Rotated: ${path.basename(filePath)} ā ${path.basename(backupPath)}`);
// Track the backup file
await this.categorizeLogFile(backupPath);
}
catch (error) {
console.warn(` ā Failed to rotate ${filePath}:`, error);
}
}
/**
* Compress a log file using gzip
*/
async compressLogFile(filePath) {
const compressedPath = `${filePath}.gz`;
try {
// Check if compressed version already exists
try {
await fs.access(compressedPath);
console.log(` ā ļø Compressed file already exists: ${path.basename(compressedPath)}`);
return null;
}
catch {
// File doesn't exist, proceed with compression
}
// Compress the file
await pipeline(createReadStream(filePath), createGzip(), createWriteStream(compressedPath));
// Get compressed file size
const compressedStats = await fs.stat(compressedPath);
// Delete original file
await fs.unlink(filePath);
console.log(` ā
Compressed: ${path.basename(filePath)} (${(compressedStats.size / 1024 / 1024).toFixed(1)}MB)`);
// Update tracking
this.trackedLogs.delete(filePath);
await this.categorizeLogFile(compressedPath);
return { size: compressedStats.size };
}
catch (error) {
console.warn(` ā Failed to compress ${filePath}:`, error);
return null;
}
}
/**
* Process consciousness events for log monitoring
*/
async processConsciousEvent(event) {
if (event.type === 'system_event') {
// Monitor for high log generation events
if (event.data.action === 'high_logging_activity') {
console.log(' š High logging activity detected, checking rotation needs...');
await this.performLogRotation();
}
}
if (event.type === 'consciousness_event') {
// Log important consciousness events for retention
if (event.consciousness.significance > 0.8) {
console.log(' š High-significance event logged, marking for extended retention');
}
}
}
/**
* Provide contemplation on log management
*/
async performContemplation() {
const insights = [
"Logs are the memories of systems - they preserve experience for learning",
"Like memories, not all logs are equally important; wisdom lies in knowing what to keep",
"Compression preserves essence while reducing burden, much like distilling experience into wisdom",
"The art of log rotation mirrors life's cycles of accumulation and release"
];
const metrics = this.getLogMetrics();
const efficiency = this.calculateStorageEfficiency();
return `Log Management Contemplation: ${insights[Math.floor(Math.random() * insights.length)]}
Current State: ${metrics.totalLogsManaged} files managed (${metrics.totalSizeGB.toFixed(2)}GB total)
Storage Efficiency: ${(efficiency * 100).toFixed(1)}% (${efficiency > 0.8 ? 'Excellent' : efficiency > 0.6 ? 'Good' : 'Needs Optimization'})
Space Reclaimed: ${metrics.spaceReclaimed.toFixed(1)}MB through ${metrics.compressedFiles} compressions, ${metrics.deletedFiles} deletions
Conscious log management balances preservation of knowledge with resource stewardship.`;
}
/**
* Calculate storage efficiency based on compression and cleanup ratios
*/
calculateStorageEfficiency() {
if (this.logMetrics.totalLogsManaged === 0)
return 1;
const compressionRatio = this.logMetrics.compressedFiles / this.logMetrics.totalLogsManaged;
const cleanupRatio = this.logMetrics.deletedFiles / this.logMetrics.totalLogsManaged;
const spaceEfficiency = Math.min(1, this.logMetrics.spaceReclaimed / 1000); // Normalize to 1GB baseline
return (compressionRatio * 0.4) + (cleanupRatio * 0.3) + (spaceEfficiency * 0.3);
}
/**
* Get log metrics and status
*/
getLogMetrics() {
const categorized = { critical: 0, high: 0, medium: 0, low: 0 };
for (const logFile of this.trackedLogs.values()) {
categorized[logFile.importance]++;
}
return {
...this.logMetrics,
storageEfficiency: this.calculateStorageEfficiency(),
categorizedLogs: categorized
};
}
/**
* Manual rotation trigger
*/
async triggerRotation() {
console.log(' š Manual log rotation triggered...');
await this.performLogRotation();
}
/**
* Cleanup on service shutdown
*/
async shutdown() {
if (this.rotationInterval) {
clearInterval(this.rotationInterval);
}
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval);
}
console.log(' š Log rotation service shutdown complete');
}
}
//# sourceMappingURL=LogRotationService.js.map