From 328766131894d0fcb6c2f5a8c3a572216bc10a6a Mon Sep 17 00:00:00 2001 From: SikongJueluo <1822250894@qq.com> Date: Fri, 21 Nov 2025 14:22:46 +0800 Subject: [PATCH] refactor(logging): restructure exports and consolidate processors --- src/lib/ccStructLog/index.ts | 146 +---------- src/lib/ccStructLog/processors.ts | 401 ++++++++++++++---------------- src/lib/ccStructLog/renderers.ts | 28 +-- src/lib/ccStructLog/streams.ts | 23 +- 4 files changed, 216 insertions(+), 382 deletions(-) diff --git a/src/lib/ccStructLog/index.ts b/src/lib/ccStructLog/index.ts index 148121b..dd1ba0b 100644 --- a/src/lib/ccStructLog/index.ts +++ b/src/lib/ccStructLog/index.ts @@ -7,150 +7,14 @@ */ // Re-export all core types and classes -export { - LogLevel, - LogEvent, - Processor, - Renderer, - Stream, - LoggerOptions, - ILogger, -} from "./types"; -export { Logger } from "./Logger"; +export * from "./types"; +export * from "./Logger"; // Re-export all processors -export { - addTimestamp, - addFormattedTimestamp, - addFullTimestamp, - filterByLevel, - addSource, - addComputerId, - addComputerLabel, - filterBy, - transformField, - removeFields, - addStaticFields, -} from "./processors"; +export * from "./processors"; // Re-export all renderers -export { jsonRenderer, textRenderer } from "./renderers"; +export * from "./renderers"; // Re-export all streams -export { - ConsoleStream, - FileStream, - BufferStream, - NullStream, - SECOND, - MINUTE, - HOUR, - DAY, - WEEK, -} from "./streams"; - -import { Logger } from "./Logger"; -import { LogLevel, LogEvent } from "./types"; -import { - addFormattedTimestamp, - addFullTimestamp, - addComputerId, -} from "./processors"; -import { textRenderer, jsonRenderer } from "./renderers"; -import { ConsoleStream, FileStream, DAY } from "./streams"; - -/** - * Create a development logger with console output and colored formatting. - * - * This logger is optimized for development and debugging, with: - * - Debug level and above - * - Formatted timestamps - * - Computer ID tracking - * - Human-readable console output with colors - * - * @param options - Optional configuration to override defaults - * @returns A configured Logger instance for development - */ -export function createDevLogger( - options: { - level?: LogLevel; - source?: string; - includeComputerId?: boolean; - } = {}, -): Logger { - const processors = [addFormattedTimestamp()]; - - if (options.includeComputerId !== false) { - processors.push(addComputerId()); - } - - if (options.source) { - processors.push((event: LogEvent) => { - event.set("source", options.source); - return event; - }); - } - - return new Logger({ - processors, - renderer: textRenderer, - streams: [new ConsoleStream()], - }); -} - -/** - * Create a production logger with file output and JSON formatting. - * - * This logger is optimized for production environments, with: - * - Info level and above - * - Full timestamps - * - Computer ID and label tracking - * - JSON output for machine processing - * - Daily file rotation - * - * @param filename - Base filename for log files - * @param options - Optional configuration to override defaults - * @returns A configured Logger instance for production - */ -export function createProdLogger( - filename: string, - options: { - level?: LogLevel; - source?: string; - rotationInterval?: number; - includeConsole?: boolean; - } = {}, -): Logger { - const processors = [ - addFullTimestamp(), - addComputerId(), - (event: LogEvent) => { - const label = os.getComputerLabel(); - if (label) { - event.set("computer_label", label); - } - return event; - }, - ]; - - if (options.source) { - processors.push((event: LogEvent) => { - event.set("source", options.source); - return event; - }); - } - - const streams: Array = [ - new FileStream(filename, options.rotationInterval ?? DAY), - ]; - - if (options.includeConsole) { - streams.push(new ConsoleStream()); - } - - return new Logger({ - processors, - renderer: jsonRenderer, - streams, - }); -} +export * from "./streams"; diff --git a/src/lib/ccStructLog/processors.ts b/src/lib/ccStructLog/processors.ts index 9c0bd8f..95cded4 100644 --- a/src/lib/ccStructLog/processors.ts +++ b/src/lib/ccStructLog/processors.ts @@ -8,213 +8,196 @@ import { LogEvent, Processor, LogLevel } from "./types"; -/** - * Adds a timestamp to the log event. - * - * This processor adds the current time as a structured timestamp object - * using CC:Tweaked's os.date() function. The timestamp includes year, - * month, day, hour, minute, and second components. - * - * Performance note: os.date() is relatively expensive, so this should - * typically be placed early in the processor chain and used only once. - * - * @param event - The log event to process - * @returns The event with timestamp added - */ -export function addTimestamp(): Processor { - return (event) => { - const timestamp = os.date("!*t") as LuaDate; - event.set("timestamp", timestamp); - return event; - }; -} - -/** - * Adds a human-readable timestamp string to the log event. - * - * This processor adds a formatted timestamp string that's easier to read - * in log output. The format is "HH:MM:SS" in UTC time. - * - * @param event - The log event to process - * @returns The event with formatted timestamp added - */ -export function addFormattedTimestamp(): Processor { - return (event) => { - const timestamp = os.date("!*t") as LuaDate; - const timeStr = `${string.format("%02d", timestamp.hour)}:${string.format("%02d", timestamp.min)}:${string.format("%02d", timestamp.sec)}`; - event.set("time", timeStr); - return event; - }; -} - -/** - * Adds a full ISO-like timestamp string to the log event. - * - * This processor adds a complete timestamp in YYYY-MM-DD HH:MM:SS format - * which is useful for file logging and structured output. - * - * @param event - The log event to process - * @returns The event with full timestamp added - */ -export function addFullTimestamp(): Processor { - return (event) => { - const timestamp = os.date("!*t") as LuaDate; - const fullTimeStr = `${timestamp.year}-${string.format("%02d", timestamp.month)}-${string.format("%02d", timestamp.day)} ${string.format("%02d", timestamp.hour)}:${string.format("%02d", timestamp.min)}:${string.format("%02d", timestamp.sec)}`; - event.set("datetime", fullTimeStr); - return event; - }; -} - -/** - * Filters log events by minimum level. - * - * This processor drops log events that are below the specified minimum level. - * Note: The Logger class already does early filtering for performance, but - * this processor can be useful for dynamic filtering or when you need - * different levels for different streams. - * - * @param minLevel - The minimum log level to allow through - * @returns A processor function that filters by level - */ -export function filterByLevel(minLevel: LogLevel): Processor { - return (event) => { - const eventLevel = event.get("level") as LogLevel | undefined; - if (eventLevel === undefined) { - return event; // Pass through if no level is set - } - - if (eventLevel !== undefined && eventLevel < minLevel) { - return undefined; // Drop the log event - } - - return event; - }; -} - -/** - * 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. - * - * In CC:Tweaked environments, this can help identify which computer - * generated the log when logs are aggregated from multiple sources. - * - * @param event - The log event to process - * @returns The event with computer ID added - */ -export function addComputerId(): Processor { - return (event) => { - event.set("computer_id", os.getComputerID()); - return event; - }; -} - -/** - * Adds the current computer label to the log event. - * - * If the computer has a label set, this adds it to the log event. - * This can be more human-readable than the computer ID. - * - * @param event - The log event to process - * @returns The event with computer label added (if available) - */ -export function addComputerLabel(): Processor { - return (event) => { - const label = os.getComputerLabel(); - if (label !== undefined && label !== null) { - event.set("computer_label", label); - } - return event; - }; -} - -/** - * Filters out events that match a specific condition. - * - * This is a generic processor that allows you to filter events based on - * any custom condition. The predicate function should return true to keep - * the event and false to drop it. - * - * @param predicate - Function that returns true to keep the event - * @returns A processor function that filters based on the predicate - */ -export function filterBy(predicate: (event: LogEvent) => boolean): Processor { - return (event) => { - return predicate(event) ? event : undefined; - }; -} - -/** - * Transforms a specific field in the log event. - * - * This processor allows you to modify the value of a specific field - * using a transformation function. - * - * @param fieldName - The name of the field to transform - * @param transformer - Function to transform the field value - * @returns A processor function that transforms the specified field - */ -export function transformField( - fieldName: string, - transformer: (value: unknown) => unknown, -): Processor { - return (event) => { - if (event.has(fieldName)) { - const currentValue = event.get(fieldName); - const newValue = transformer(currentValue); - event.set(fieldName, newValue); - } - return event; - }; -} - -/** - * Removes specified fields from the log event. - * - * This processor can be used to strip sensitive or unnecessary information - * from log events before they are rendered and output. - * - * @param fieldNames - Array of field names to remove - * @returns A processor function that removes the specified fields - */ -export function removeFields(fieldNames: string[]): Processor { - return (event) => { - for (const fieldName of fieldNames) { - event.delete(fieldName); - } - return event; - }; -} - -/** - * Adds static fields to every log event. - * - * This processor adds the same set of fields to every log event that - * passes through it. Useful for adding application name, version, - * environment, etc. - * - * @param fields - Object containing the static fields to add - * @returns A processor function that adds the static fields - */ -export function addStaticFields(fields: Record): Processor { - return (event) => { - for (const [key, value] of Object.entries(fields)) { - event.set(key, value); - } - return event; - }; +export namespace processor { + /** + * Configuration options for the timestamp processor. + */ + interface TimestampConfig { + /** + * The format string takes the same formats as C's strftime function. + */ + format?: string; + } + + /** + * Adds a timestamp to each log event. + * + * This processor adds a "time" field to each log event with the current + * timestamp. The timestamp format can be customized using the `format` + * option. + * + * @param config - Configuration options for the timestamp processor. + * @returns A processor function that adds a timestamp to each log event. + */ + export function addTimestamp(config: TimestampConfig = {}): Processor { + return (event) => { + let time: string; + if (config.format === undefined) { + time = os.date("%F %T") as string; + } else { + time = os.date(config.format) as string; + } + + event.set("timestamp", time); + return event; + }; + } + + /** + * Filters log events by minimum level. + * + * This processor drops log events that are below the specified minimum level. + * Note: The Logger class already does early filtering for performance, but + * this processor can be useful for dynamic filtering or when you need + * different levels for different streams. + * + * @param minLevel - The minimum log level to allow through + * @returns A processor function that filters by level + */ + export function filterByLevel(minLevel: LogLevel): Processor { + return (event) => { + const eventLevel = event.get("level") as LogLevel | undefined; + if (eventLevel === undefined) { + return event; // Pass through if no level is set + } + + if (eventLevel !== undefined && eventLevel < minLevel) { + return undefined; // Drop the log event + } + + return event; + }; + } + + /** + * 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. + * + * In CC:Tweaked environments, this can help identify which computer + * generated the log when logs are aggregated from multiple sources. + * + * @param event - The log event to process + * @returns The event with computer ID added + */ + export function addComputerId(): Processor { + return (event) => { + event.set("computer_id", os.getComputerID()); + return event; + }; + } + + /** + * Adds the current computer label to the log event. + * + * If the computer has a label set, this adds it to the log event. + * This can be more human-readable than the computer ID. + * + * @param event - The log event to process + * @returns The event with computer label added (if available) + */ + export function addComputerLabel(): Processor { + return (event) => { + const label = os.getComputerLabel(); + if (label !== undefined && label !== null) { + event.set("computer_label", label); + } + return event; + }; + } + + /** + * Filters out events that match a specific condition. + * + * This is a generic processor that allows you to filter events based on + * any custom condition. The predicate function should return true to keep + * the event and false to drop it. + * + * @param predicate - Function that returns true to keep the event + * @returns A processor function that filters based on the predicate + */ + export function filterBy( + predicate: (event: LogEvent) => boolean, + ): Processor { + return (event) => { + return predicate(event) ? event : undefined; + }; + } + + /** + * Transforms a specific field in the log event. + * + * This processor allows you to modify the value of a specific field + * using a transformation function. + * + * @param fieldName - The name of the field to transform + * @param transformer - Function to transform the field value + * @returns A processor function that transforms the specified field + */ + export function transformField( + fieldName: string, + transformer: (value: unknown) => unknown, + ): Processor { + return (event) => { + if (event.has(fieldName)) { + const currentValue = event.get(fieldName); + const newValue = transformer(currentValue); + event.set(fieldName, newValue); + } + return event; + }; + } + + /** + * Removes specified fields from the log event. + * + * This processor can be used to strip sensitive or unnecessary information + * from log events before they are rendered and output. + * + * @param fieldNames - Array of field names to remove + * @returns A processor function that removes the specified fields + */ + export function removeFields(fieldNames: string[]): Processor { + return (event) => { + for (const fieldName of fieldNames) { + event.delete(fieldName); + } + return event; + }; + } + + /** + * Adds static fields to every log event. + * + * This processor adds the same set of fields to every log event that + * passes through it. Useful for adding application name, version, + * environment, etc. + * + * @param fields - Object containing the static fields to add + * @returns A processor function that adds the static fields + */ + export function addStaticFields( + fields: Record, + ): Processor { + return (event) => { + for (const [key, value] of Object.entries(fields)) { + event.set(key, value); + } + return event; + }; + } } diff --git a/src/lib/ccStructLog/renderers.ts b/src/lib/ccStructLog/renderers.ts index aab84b6..54d9489 100644 --- a/src/lib/ccStructLog/renderers.ts +++ b/src/lib/ccStructLog/renderers.ts @@ -6,7 +6,7 @@ * different output formats (JSON, console-friendly, etc.). */ -import { Renderer } from "./types"; +import { LogLevel, Renderer } from "./types"; /** * Renders log events as JSON strings. @@ -43,44 +43,30 @@ export const jsonRenderer: Renderer = (event) => { * timestamp, level, message, and additional context fields formatted * in a readable way. * - * Format: [HH:MM:SS] [LEVEL] message { key=value, key2=value2 } + * Format: [YYYY-MM-DD HH:MM:SS] [LEVEL] message key=value, key2=value2 * * @param event - The log event to render * @returns Human-readable string representation */ export const textRenderer: Renderer = (event) => { // Extract core components - const timestamp = event.get("timestamp") as LuaDate | undefined; - const timeStr = event.get("time") as string | undefined; - const level = (event.get("level") as string)?.toUpperCase() ?? "UNKNOWN"; + 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) ?? ""; - // Format timestamp - let timestampStr = ""; - if (timeStr) { - timestampStr = timeStr; - } else if (timestamp) { - timestampStr = `${string.format("%02d", timestamp.hour)}:${string.format("%02d", timestamp.min)}:${string.format("%02d", timestamp.sec)}`; - } - // Start building the output - let output = `[${timestampStr}] [${level}] ${message}`; + let output = `[${timeStr}] [${level}] ${message} \t`; // Add context fields (excluding the core fields we already used) const contextFields: string[] = []; for (const [key, value] of event.entries()) { - if ( - key !== "timestamp" && - key !== "time" && - key !== "level" && - key !== "message" - ) { + if (key !== "timestamp" && key !== "level" && key !== "message") { contextFields.push(`${key}=${tostring(value)}`); } } if (contextFields.length > 0) { - output += ` { ${contextFields.join(", ")} }`; + output += contextFields.join(", "); } return output; diff --git a/src/lib/ccStructLog/streams.ts b/src/lib/ccStructLog/streams.ts index acc2691..b79569f 100644 --- a/src/lib/ccStructLog/streams.ts +++ b/src/lib/ccStructLog/streams.ts @@ -6,7 +6,7 @@ * implements the Stream interface and handles its own output logic. */ -import { Stream, LogEvent } from "./types"; +import { LogLevel, Stream, LogEvent } from "./types"; /** * Console stream that outputs to the CC:Tweaked terminal. @@ -16,14 +16,14 @@ import { Stream, LogEvent } from "./types"; * color after writing each message. */ export class ConsoleStream implements Stream { - private levelColors: Map = new Map([ - ["trace", colors.lightGray], - ["debug", colors.gray], - ["info", colors.white], - ["warn", colors.orange], - ["error", colors.red], - ["fatal", colors.red], - ]); + private levelColors: { [key: string]: number } = { + Trace: colors.lightGray, + Debug: colors.gray, + Info: colors.green, + Warn: colors.orange, + Error: colors.red, + Fatal: colors.red, + }; /** * Write a formatted log message to the terminal. @@ -32,8 +32,9 @@ export class ConsoleStream implements Stream { * @param event - The original log event for context (used for level-based coloring) */ public write(message: string, event: LogEvent): void { - const level = event.get("level") as string | undefined; - const color = level ? this.levelColors.get(level) : undefined; + const level: string | undefined = + LogLevel[event.get("level") as LogLevel]; + const color = level !== undefined ? this.levelColors[level] : undefined; if (color !== undefined) { const originalColor = term.getTextColor();