mirror of
https://github.com/SikongJueluo/cc-utils.git
synced 2025-11-29 12:57:50 +08:00
feat(logging): add auto-cleanup functionality to FileStream
This commit is contained in:
@@ -8,6 +8,29 @@
|
||||
|
||||
import { LogLevel, Stream, LogEvent } from "./types";
|
||||
|
||||
/**
|
||||
* Configuration interface for FileStream with auto-cleanup options.
|
||||
*/
|
||||
interface FileStreamConfig {
|
||||
/** Path to the log file */
|
||||
filePath: string;
|
||||
/** Time in seconds between file rotations (0 = no rotation) */
|
||||
rotationInterval?: number;
|
||||
/** Auto-cleanup configuration */
|
||||
autoCleanup?: {
|
||||
/** Whether to enable auto-cleanup */
|
||||
enabled: boolean;
|
||||
/** Maximum number of log files to keep */
|
||||
maxFiles?: number;
|
||||
/** Maximum total size in bytes for all log files */
|
||||
maxSizeBytes?: number;
|
||||
/** Directory to search for log files (defaults to log file directory) */
|
||||
logDir?: string;
|
||||
/** File pattern to match (defaults to base filename pattern) */
|
||||
pattern?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Console stream that outputs to the CC:Tweaked terminal.
|
||||
*
|
||||
@@ -59,20 +82,18 @@ export class FileStream implements Stream {
|
||||
private filePath: string;
|
||||
private rotationInterval: number;
|
||||
private lastRotationTime: number;
|
||||
private baseFilename: string;
|
||||
private autoCleanupConfig?: FileStreamConfig["autoCleanup"];
|
||||
|
||||
/**
|
||||
* Create a new file stream.
|
||||
* Create a new file stream with configuration object.
|
||||
*
|
||||
* @param filePath - Path to the log file
|
||||
* @param rotationInterval - Time in seconds between file rotations (0 = no rotation)
|
||||
* @param config - FileStream configuration object
|
||||
*/
|
||||
constructor(filePath: string, rotationInterval: number = 0) {
|
||||
this.filePath = filePath;
|
||||
this.rotationInterval = rotationInterval;
|
||||
constructor(config: FileStreamConfig) {
|
||||
this.filePath = config.filePath;
|
||||
this.rotationInterval = config.rotationInterval || 0;
|
||||
this.autoCleanupConfig = config.autoCleanup;
|
||||
this.lastRotationTime = os.time();
|
||||
this.baseFilename = filePath;
|
||||
|
||||
this.openFile();
|
||||
}
|
||||
|
||||
@@ -94,6 +115,9 @@ export class FileStream implements Stream {
|
||||
return;
|
||||
}
|
||||
this.fileHandle = handle;
|
||||
|
||||
// Perform auto-cleanup when opening file
|
||||
this.performAutoCleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,9 +133,9 @@ export class FileStream implements Stream {
|
||||
const timestamp = `${date.year}-${string.format("%02d", date.month)}-${string.format("%02d", date.day)}_${string.format("%02d", date.hour)}-${string.format("%02d", date.min)}`;
|
||||
|
||||
// Split filename and extension
|
||||
const splitStrs = this.baseFilename.split(".");
|
||||
const splitStrs = this.filePath.split(".");
|
||||
if (splitStrs.length === 1) {
|
||||
return `${this.baseFilename}_${timestamp}.log`;
|
||||
return `${this.filePath}_${timestamp}.log`;
|
||||
}
|
||||
|
||||
const name = splitStrs[0];
|
||||
@@ -136,9 +160,49 @@ export class FileStream implements Stream {
|
||||
this.close();
|
||||
this.lastRotationTime = currentTime;
|
||||
this.openFile();
|
||||
// Auto-cleanup is performed in openFile()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform auto-cleanup based on configuration.
|
||||
* This method is called automatically when opening files or rotating.
|
||||
*/
|
||||
private performAutoCleanup(): void {
|
||||
if (!this.autoCleanupConfig || !this.autoCleanupConfig.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = this.autoCleanupConfig;
|
||||
|
||||
// Cleanup by file count if configured
|
||||
if (config.maxFiles !== undefined && config.maxFiles > 0) {
|
||||
this.cleanupOldLogFiles(
|
||||
config.maxFiles,
|
||||
config.logDir,
|
||||
config.pattern,
|
||||
);
|
||||
}
|
||||
|
||||
// Cleanup by total size if configured
|
||||
if (config.maxSizeBytes !== undefined && config.maxSizeBytes > 0) {
|
||||
this.cleanupLogFilesBySize(
|
||||
config.maxSizeBytes,
|
||||
config.logDir,
|
||||
config.pattern,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or update auto-cleanup configuration at runtime.
|
||||
*
|
||||
* @param config - Auto-cleanup configuration
|
||||
*/
|
||||
public setAutoCleanup(config: FileStreamConfig["autoCleanup"]): void {
|
||||
this.autoCleanupConfig = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a formatted log message to the file.
|
||||
*
|
||||
@@ -163,6 +227,127 @@ export class FileStream implements Stream {
|
||||
this.fileHandle = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for log files matching the specified pattern in a directory.
|
||||
*
|
||||
* @param logDir - Directory containing log files (defaults to directory of current log file)
|
||||
* @param fileName - Base File Name
|
||||
* @returns Array of log file information including path, size, and modification time
|
||||
*/
|
||||
private searchLogFiles(
|
||||
logDir?: string,
|
||||
fileName?: string,
|
||||
): Array<{ path: string; size: number; modified: number }> {
|
||||
const directory = logDir || fs.getDir(this.filePath);
|
||||
const baseFileName =
|
||||
fileName || fs.getName(this.filePath).split(".")[0];
|
||||
|
||||
if (!fs.exists(directory) || !fs.isDir(directory)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const logFiles: Array<{
|
||||
path: string;
|
||||
size: number;
|
||||
modified: number;
|
||||
}> = [];
|
||||
const files = fs.list(directory);
|
||||
|
||||
for (const file of files) {
|
||||
const fullPath = fs.combine(directory, file);
|
||||
if (fs.isDir(fullPath) || !file.startsWith(baseFileName)) continue;
|
||||
|
||||
const attributes = fs.attributes(fullPath);
|
||||
if (attributes !== undefined) {
|
||||
logFiles.push({
|
||||
path: fullPath,
|
||||
size: attributes.size,
|
||||
modified: attributes.modified,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return logFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old log files by keeping only the specified number of most recent files.
|
||||
*
|
||||
* @param maxFiles - Maximum number of log files to keep
|
||||
* @param logDir - Directory containing log files (defaults to directory of current log file)
|
||||
* @param fileName - Base File Name
|
||||
*/
|
||||
public cleanupOldLogFiles(
|
||||
maxFiles: number,
|
||||
logDir?: string,
|
||||
fileName?: string,
|
||||
): void {
|
||||
if (maxFiles <= 0) return;
|
||||
|
||||
const logFiles = this.searchLogFiles(logDir, fileName);
|
||||
if (logFiles.length <= maxFiles) return;
|
||||
|
||||
// Sort by modification time (newest first)
|
||||
logFiles.sort((a, b) => b.modified - a.modified);
|
||||
|
||||
// Delete files beyond the limit
|
||||
for (let i = maxFiles; i < logFiles.length; i++) {
|
||||
try {
|
||||
fs.delete(logFiles[i].path);
|
||||
} catch (err) {
|
||||
printError(
|
||||
`Failed to delete old log file ${logFiles[i].path}: ${err}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up log files by total size, deleting oldest files until total size is under limit.
|
||||
*
|
||||
* @param maxSizeBytes - Maximum total size in bytes for all log files
|
||||
* @param logDir - Directory containing log files (defaults to directory of current log file)
|
||||
* @param fileName - Base File Name
|
||||
*/
|
||||
public cleanupLogFilesBySize(
|
||||
maxSizeBytes: number,
|
||||
logDir?: string,
|
||||
fileName?: string,
|
||||
): void {
|
||||
if (maxSizeBytes <= 0) return;
|
||||
|
||||
const logFiles = this.searchLogFiles(logDir, fileName);
|
||||
if (logFiles.length === 0) return;
|
||||
|
||||
// Calculate total size
|
||||
let totalSize = 0;
|
||||
for (const logFile of logFiles) {
|
||||
totalSize += logFile.size;
|
||||
}
|
||||
|
||||
// If total size is within limit, no cleanup needed
|
||||
if (totalSize <= maxSizeBytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort by modification time (oldest first for deletion)
|
||||
logFiles.sort((a, b) => a.modified - b.modified);
|
||||
|
||||
// Delete oldest files until we're under the size limit
|
||||
for (const logFile of logFiles) {
|
||||
if (totalSize <= maxSizeBytes) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
fs.delete(logFile.path);
|
||||
totalSize -= logFile.size;
|
||||
} catch (err) {
|
||||
printError(`Failed to delete log file ${logFile.path}: ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,3 +492,7 @@ export const MINUTE = 60 * SECOND;
|
||||
export const HOUR = 60 * MINUTE;
|
||||
export const DAY = 24 * HOUR;
|
||||
export const WEEK = 7 * DAY;
|
||||
|
||||
// Byte constants for file rotation
|
||||
export const MB = 1024 * 1024;
|
||||
export const KB = 1024;
|
||||
|
||||
Reference in New Issue
Block a user