diff --git a/src/lib/ccStructLog/Logger.ts b/src/lib/ccStructLog/Logger.ts index e0fab33..d538771 100644 --- a/src/lib/ccStructLog/Logger.ts +++ b/src/lib/ccStructLog/Logger.ts @@ -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) { - 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; +} diff --git a/src/lib/ccStructLog/processors.ts b/src/lib/ccStructLog/processors.ts index 95cded4..c0ccc85 100644 --- a/src/lib/ccStructLog/processors.ts +++ b/src/lib/ccStructLog/processors.ts @@ -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. * diff --git a/src/lib/ccStructLog/renderers.ts b/src/lib/ccStructLog/renderers.ts index 54d9489..18b9d15 100644 --- a/src/lib/ccStructLog/renderers.ts +++ b/src/lib/ccStructLog/renderers.ts @@ -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)}`); } } diff --git a/src/lib/ccStructLog/streams.ts b/src/lib/ccStructLog/streams.ts index d224de8..8a74af4 100644 --- a/src/lib/ccStructLog/streams.ts +++ b/src/lib/ccStructLog/streams.ts @@ -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