refactor(logger): simplify logger configuration and improve file rotation

This commit is contained in:
2025-11-21 21:15:06 +08:00
parent 0612477325
commit de97fb4858
4 changed files with 65 additions and 71 deletions

View File

@@ -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;
}

View File

@@ -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.
*

View File

@@ -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)}`);
}
}

View File

@@ -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