mirror of
https://github.com/SikongJueluo/cc-utils.git
synced 2025-11-29 12:57:50 +08:00
refactor(logger): simplify logger configuration and improve file rotation
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
*/
|
||||
|
||||
import { LogLevel, LoggerOptions, LogEvent, ILogger } from "./types";
|
||||
import { processor } from "./processors";
|
||||
import { ConsoleStream } from "./streams";
|
||||
import { textRenderer } from "./renderers";
|
||||
|
||||
/**
|
||||
* The main Logger class that orchestrates the logging pipeline.
|
||||
@@ -13,26 +16,19 @@ import { LogLevel, LoggerOptions, LogEvent, ILogger } from "./types";
|
||||
*/
|
||||
export class Logger implements ILogger {
|
||||
private options: LoggerOptions;
|
||||
private loggerName?: string;
|
||||
|
||||
/**
|
||||
* Create a new Logger instance.
|
||||
*
|
||||
* @param options - Configuration options for the logger
|
||||
* @param name - The name of the logger
|
||||
*/
|
||||
constructor(options: Partial<LoggerOptions>) {
|
||||
this.options = {
|
||||
processors: options.processors ?? [],
|
||||
renderer: options.renderer ?? this.defaultRenderer,
|
||||
streams: options.streams ?? [],
|
||||
};
|
||||
constructor(options: LoggerOptions, name?: string) {
|
||||
this.options = options;
|
||||
this.loggerName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default renderer that returns an empty string.
|
||||
* Used as fallback when no renderer is provided.
|
||||
*/
|
||||
private defaultRenderer = (): string => "";
|
||||
|
||||
/**
|
||||
* Main logging method that handles the complete logging pipeline.
|
||||
*
|
||||
@@ -51,6 +47,8 @@ export class Logger implements ILogger {
|
||||
["message", message],
|
||||
...Object.entries(context),
|
||||
]);
|
||||
if (this.loggerName !== undefined)
|
||||
event.set("loggerName", this.loggerName);
|
||||
|
||||
// 2. Process through the processor chain
|
||||
for (const processor of this.options.processors) {
|
||||
@@ -62,12 +60,11 @@ export class Logger implements ILogger {
|
||||
|
||||
// 3. Render and output if event wasn't dropped
|
||||
if (event !== undefined) {
|
||||
const finalEvent = event;
|
||||
const output = this.options.renderer(finalEvent);
|
||||
const output = this.options.renderer(event);
|
||||
|
||||
// Send to all configured streams
|
||||
for (const stream of this.options.streams) {
|
||||
stream.write(output, finalEvent);
|
||||
stream.write(output, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,3 +160,17 @@ export class Logger implements ILogger {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let globalLoggerConfig: LoggerOptions = {
|
||||
processors: [processor.addTimestamp()],
|
||||
renderer: textRenderer,
|
||||
streams: [new ConsoleStream()],
|
||||
};
|
||||
|
||||
export function getStructLogger(name?: string): Logger {
|
||||
return new Logger(globalLoggerConfig, name);
|
||||
}
|
||||
|
||||
export function setStructLoggerConfig(config: LoggerOptions): void {
|
||||
globalLoggerConfig = config;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export namespace processor {
|
||||
return event; // Pass through if no level is set
|
||||
}
|
||||
|
||||
if (eventLevel !== undefined && eventLevel < minLevel) {
|
||||
if (eventLevel < minLevel) {
|
||||
return undefined; // Drop the log event
|
||||
}
|
||||
|
||||
@@ -69,22 +69,6 @@ export namespace processor {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a logger name/source to the log event.
|
||||
*
|
||||
* This processor is useful when you have multiple loggers in your application
|
||||
* and want to identify which component generated each log entry.
|
||||
*
|
||||
* @param name - The name/source to add to log events
|
||||
* @returns A processor function that adds the source name
|
||||
*/
|
||||
export function addSource(name: string): Processor {
|
||||
return (event) => {
|
||||
event.set("source", name);
|
||||
return event;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the current computer ID to the log event.
|
||||
*
|
||||
|
||||
@@ -53,14 +53,20 @@ export const textRenderer: Renderer = (event) => {
|
||||
const timeStr = event.get("timestamp") as string | undefined;
|
||||
const level: string | undefined = LogLevel[event.get("level") as LogLevel];
|
||||
const message = (event.get("message") as string) ?? "";
|
||||
const loggerName = event.get("loggerName") as string | undefined;
|
||||
|
||||
// Start building the output
|
||||
let output = `[${timeStr}] [${level}] ${message} \t`;
|
||||
let output = `${timeStr} [${level}] ${message} \t ${loggerName !== undefined ? "[" + loggerName + "]" : ""}`;
|
||||
|
||||
// Add context fields (excluding the core fields we already used)
|
||||
const contextFields: string[] = [];
|
||||
for (const [key, value] of event.entries()) {
|
||||
if (key !== "timestamp" && key !== "level" && key !== "message") {
|
||||
if (
|
||||
key !== "timestamp" &&
|
||||
key !== "level" &&
|
||||
key !== "message" &&
|
||||
key !== "loggerName"
|
||||
) {
|
||||
contextFields.push(`${key}=${tostring(value)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,11 @@ import { LogLevel, Stream, LogEvent } from "./types";
|
||||
interface FileStreamConfig {
|
||||
/** Path to the log file */
|
||||
filePath: string;
|
||||
/** Time in seconds between file rotations (0 = no rotation) */
|
||||
/**
|
||||
* Time in seconds between file rotations (0 = no rotation)
|
||||
* Time must larger than one DAY
|
||||
* @default 0
|
||||
*/
|
||||
rotationInterval?: number;
|
||||
/** Auto-cleanup configuration */
|
||||
autoCleanup?: {
|
||||
@@ -92,6 +96,8 @@ export class FileStream implements Stream {
|
||||
constructor(config: FileStreamConfig) {
|
||||
this.filePath = config.filePath;
|
||||
this.rotationInterval = config.rotationInterval || 0;
|
||||
if (this.rotationInterval !== 0 && this.rotationInterval < DAY)
|
||||
throw Error("Rotation interval must be at least one day");
|
||||
this.autoCleanupConfig = config.autoCleanup;
|
||||
this.lastRotationTime = os.time();
|
||||
this.openFile();
|
||||
@@ -124,13 +130,13 @@ export class FileStream implements Stream {
|
||||
* Generate a filename with timestamp for file rotation.
|
||||
*/
|
||||
private getRotatedFilename(): string {
|
||||
const currentTime = os.time();
|
||||
const currentTime = os.time(os.date("*t"));
|
||||
const rotationPeriod =
|
||||
Math.floor(currentTime / this.rotationInterval) *
|
||||
this.rotationInterval;
|
||||
const date = os.date("*t", rotationPeriod) as LuaDate;
|
||||
|
||||
const timestamp = `${date.year}-${string.format("%02d", date.month)}-${string.format("%02d", date.day)}_${string.format("%02d", date.hour)}-${string.format("%02d", date.min)}`;
|
||||
const timestamp = `${date.year}-${string.format("%02d", date.month)}-${string.format("%02d", date.day)}`;
|
||||
|
||||
// Split filename and extension
|
||||
const splitStrs = this.filePath.split(".");
|
||||
@@ -150,12 +156,11 @@ export class FileStream implements Stream {
|
||||
if (this.rotationInterval <= 0) return;
|
||||
|
||||
const currentTime = os.time();
|
||||
const currentPeriod = Math.floor(currentTime / this.rotationInterval);
|
||||
const lastPeriod = Math.floor(
|
||||
this.lastRotationTime / this.rotationInterval,
|
||||
);
|
||||
|
||||
if (currentPeriod > lastPeriod) {
|
||||
if (
|
||||
Math.floor(
|
||||
(currentTime - this.lastRotationTime) / this.rotationInterval,
|
||||
) > 0
|
||||
) {
|
||||
// Time to rotate
|
||||
this.close();
|
||||
this.lastRotationTime = currentTime;
|
||||
@@ -177,20 +182,12 @@ export class FileStream implements Stream {
|
||||
|
||||
// Cleanup by file count if configured
|
||||
if (config.maxFiles !== undefined && config.maxFiles > 0) {
|
||||
this.cleanupOldLogFiles(
|
||||
config.maxFiles,
|
||||
config.logDir,
|
||||
config.pattern,
|
||||
);
|
||||
this.cleanupOldLogFiles(config.maxFiles, config.logDir);
|
||||
}
|
||||
|
||||
// Cleanup by total size if configured
|
||||
if (config.maxSizeBytes !== undefined && config.maxSizeBytes > 0) {
|
||||
this.cleanupLogFilesBySize(
|
||||
config.maxSizeBytes,
|
||||
config.logDir,
|
||||
config.pattern,
|
||||
);
|
||||
this.cleanupLogFilesBySize(config.maxSizeBytes, config.logDir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,16 +229,16 @@ export class FileStream implements Stream {
|
||||
* 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];
|
||||
const splitStrs = this.filePath.split(".");
|
||||
|
||||
const name = splitStrs[0] + "_";
|
||||
const ext = splitStrs.length > 1 ? splitStrs[1] : "log";
|
||||
|
||||
if (!fs.exists(directory) || !fs.isDir(directory)) {
|
||||
return [];
|
||||
@@ -256,7 +253,12 @@ export class FileStream implements Stream {
|
||||
|
||||
for (const file of files) {
|
||||
const fullPath = fs.combine(directory, file);
|
||||
if (fs.isDir(fullPath) || !file.startsWith(baseFileName)) continue;
|
||||
if (
|
||||
fs.isDir(fullPath) ||
|
||||
!file.startsWith(name) ||
|
||||
!file.endsWith(ext)
|
||||
)
|
||||
continue;
|
||||
|
||||
const attributes = fs.attributes(fullPath);
|
||||
if (attributes !== undefined) {
|
||||
@@ -276,16 +278,11 @@ export class FileStream implements Stream {
|
||||
*
|
||||
* @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 {
|
||||
public cleanupOldLogFiles(maxFiles: number, logDir?: string): void {
|
||||
if (maxFiles <= 0) return;
|
||||
|
||||
const logFiles = this.searchLogFiles(logDir, fileName);
|
||||
const logFiles = this.searchLogFiles(logDir);
|
||||
if (logFiles.length <= maxFiles) return;
|
||||
|
||||
// Sort by modification time (newest first)
|
||||
@@ -310,14 +307,10 @@ export class FileStream implements Stream {
|
||||
* @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 {
|
||||
public cleanupLogFilesBySize(maxSizeBytes: number, logDir?: string): void {
|
||||
if (maxSizeBytes <= 0) return;
|
||||
|
||||
const logFiles = this.searchLogFiles(logDir, fileName);
|
||||
const logFiles = this.searchLogFiles(logDir);
|
||||
if (logFiles.length === 0) return;
|
||||
|
||||
// Calculate total size
|
||||
|
||||
Reference in New Issue
Block a user