• Saves a log message to the application log file

    Parameters

    • message: string

      Log message content

    • level: LogLevel = "INFO"

      Severity level of the log (default: INFO)

    • allowRotation: boolean = true

      Whether to perform log rotation if needed (default: true)

    Returns boolean

    Boolean indicating success or failure

    export function saveLog(
    message: string,
    level: LogLevel = "INFO",
    allowRotation = true,
    ): boolean {
    try {
    // Create logs directory if it doesn't exist
    if (!fs.existsSync(logsPath)) {
    fs.mkdirSync(logsPath, { recursive: true });
    }

    // Get current settings (especially log level filter)
    const settings = getSettings();
    const logLevelOrder = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"];
    const configuredLogLevel =
    settings.fileLogLevel || settings.logLevel || "INFO";
    const configuredLevelIndex = logLevelOrder.indexOf(configuredLogLevel);
    const currentLevelIndex = logLevelOrder.indexOf(level);

    // Skip logs below the configured log level
    if (currentLevelIndex < configuredLevelIndex) {
    return false;
    }

    // Format timestamp HH:MM:SS AM/PM.ms format
    const now = new Date();
    const formattedTime =
    now
    .toLocaleTimeString("en-US", {
    hour: "numeric",
    minute: "2-digit",
    second: "2-digit",
    hour12: true,
    })
    .replace(/\s/, " ") +
    `.${now.getMilliseconds().toString().padStart(3, "0")}`;

    const logLine = `[${formattedTime}] [${level}] ${message}\n`;

    // Create a key for deduplication (exclude timestamp for comparing content)
    const dedupeKey = `[${level}] ${message}`;
    const timestamp = now.getTime();

    // Check for duplicate logs within the deduplication window
    const existingLog = recentLogs.get(dedupeKey);

    if (existingLog) {
    // If the log is identical and recent, just increment the count
    if (timestamp - existingLog.timestamp < DEDUPLICATION_WINDOW_MS) {
    existingLog.count++;
    existingLog.timestamp = timestamp;
    // Don't write duplicate logs
    return true;
    }

    // If we've accumulated duplicates but it's been long enough, write a summary
    if (existingLog.count > 1) {
    const summaryLine = `[${formattedTime}] [${level}] Last message repeated ${existingLog.count} times\n`;
    fs.appendFileSync(latestLogPath, summaryLine, { encoding: "utf-8" });
    }
    }

    // Update the recent logs cache
    recentLogs.set(dedupeKey, { count: 1, timestamp });

    // Clean up old entries from the deduplication cache (older than the window)
    for (const [key, entry] of recentLogs.entries()) {
    if (timestamp - entry.timestamp > DEDUPLICATION_WINDOW_MS) {
    recentLogs.delete(key);
    }
    }

    // Append log to file
    fs.appendFileSync(latestLogPath, logLine, { encoding: "utf-8" });

    // Perform log rotation if needed
    if (allowRotation) {
    const settings = getSettings();
    const MAX_LOG_LINES = settings.logLineCount || 1000;

    if (fs.existsSync(latestLogPath)) {
    const logContent = fs.readFileSync(latestLogPath, "utf-8");
    const logLines = logContent
    .split("\n")
    .filter((line) => line.trim() !== "");

    if (logLines.length > MAX_LOG_LINES) {
    // Keep only the most recent MAX_LOG_LINES lines
    const truncatedLines = logLines.slice(-MAX_LOG_LINES);
    fs.writeFileSync(
    latestLogPath,
    truncatedLines.join("\n") + "\n",
    "utf-8",
    );
    console.log(
    `Log file rotated, kept ${MAX_LOG_LINES} most recent lines`,
    );

    // Also log this rotation event
    saveLog(
    `Log file rotated, keeping ${MAX_LOG_LINES} most recent lines`,
    "INFO",
    false,
    );

    // Check and enforce the maximum number of log files
    if (settings.maxLogFiles && settings.maxLogFiles > 0) {
    cleanupOldLogs(settings.maxLogFiles);
    }
    }
    }
    }

    return true;
    } catch (error) {
    console.error("Failed to save log:", error);
    return false;
    }
    }