All files / backend/src/logging log-cleaner.ts

0% Statements 0/52
0% Branches 0/16
0% Functions 0/9
0% Lines 0/52

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149                                                                                                                                                                                                                                                                                                         
/**
 * LogCleaner - Enforces retention policy by deleting old log files
 */
 
import fs from 'fs-extra';
import path from 'path';
import os from 'os';
 
export class LogCleaner {
  /**
   * Clean old log files based on retention policy
   * @param retentionDays - Number of days to keep logs (0 = never delete)
   */
  async cleanOldLogs(retentionDays: number): Promise<void> {
    if (retentionDays === 0) {
      // Never delete logs
      return;
    }
 
    const cutoffDate = this.getCutoffDate(retentionDays);
 
    // Clean global application logs
    await this.cleanDirectory(
      path.join(os.homedir(), 'Quiqr', 'logs'),
      cutoffDate,
      /^application-(\d{4}-\d{2}-\d{2})\.jsonl$/
    );
 
    // Clean site logs (recursively scan all sites and workspaces)
    await this.cleanSiteLogs(
      path.join(os.homedir(), 'Quiqr', 'sites'),
      cutoffDate
    );
  }
 
  /**
   * Get cutoff date (files older than this should be deleted)
   */
  private getCutoffDate(retentionDays: number): Date {
    const cutoff = new Date();
    cutoff.setDate(cutoff.getDate() - retentionDays);
    cutoff.setHours(0, 0, 0, 0); // Start of day
    return cutoff;
  }
 
  /**
   * Clean log files in a directory matching a pattern
   */
  private async cleanDirectory(
    dirPath: string,
    cutoffDate: Date,
    pattern: RegExp
  ): Promise<void> {
    try {
      if (!await fs.pathExists(dirPath)) {
        return;
      }
 
      const files = await fs.readdir(dirPath);
 
      for (const file of files) {
        const match = pattern.exec(file);
        if (!match) {
          continue;
        }
 
        const dateStr = match[1]; // Extract date from filename
        const fileDate = this.parseDate(dateStr);
 
        if (fileDate && fileDate < cutoffDate) {
          const filePath = path.join(dirPath, file);
          try {
            await fs.remove(filePath);
            console.log(`Deleted old log file: ${filePath}`);
          } catch (error) {
            console.error(`Failed to delete log file ${filePath}:`, error);
          }
        }
      }
    } catch (error) {
      console.error(`Failed to clean directory ${dirPath}:`, error);
    }
  }
 
  /**
   * Clean site logs recursively
   */
  private async cleanSiteLogs(sitesDir: string, cutoffDate: Date): Promise<void> {
    try {
      if (!await fs.pathExists(sitesDir)) {
        return;
      }
 
      const siteKeys = await fs.readdir(sitesDir);
 
      for (const siteKey of siteKeys) {
        const siteDir = path.join(sitesDir, siteKey);
        const stat = await fs.stat(siteDir);
 
        if (!stat.isDirectory()) {
          continue;
        }
 
        // Pattern: {workspaceKey}-YYYY-MM-DD.jsonl
        const pattern = /^(.+)-(\d{4}-\d{2}-\d{2})\.jsonl$/;
        await this.cleanDirectory(siteDir, cutoffDate, pattern);
      }
    } catch (error) {
      console.error(`Failed to clean site logs in ${sitesDir}:`, error);
    }
  }
 
  /**
   * Parse date string (YYYY-MM-DD) to Date object
   */
  private parseDate(dateStr: string): Date | null {
    const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateStr);
    if (!match) {
      return null;
    }
 
    const year = parseInt(match[1], 10);
    const month = parseInt(match[2], 10) - 1; // Month is 0-indexed
    const day = parseInt(match[3], 10);
 
    const date = new Date(year, month, day);
    date.setHours(0, 0, 0, 0);
 
    return date;
  }
 
  /**
   * Schedule periodic cleanup (runs daily)
   */
  scheduleCleanup(retentionDays: number): NodeJS.Timeout {
    // Run cleanup immediately
    this.cleanOldLogs(retentionDays).catch(error => {
      console.error('Failed to run scheduled log cleanup:', error);
    });
 
    // Schedule daily cleanup (every 24 hours)
    return setInterval(() => {
      this.cleanOldLogs(retentionDays).catch(error => {
        console.error('Failed to run scheduled log cleanup:', error);
      });
    }, 24 * 60 * 60 * 1000);
  }
}