diff --git a/README.md b/README.md index 35611bd..9b3c894 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,13 @@ constructor > environment > logger.json > defaults "rotate": true, "maxFiles": 3, "prettyPrint": true, - "pattern": "{color:gray}{timestamp}{reset} {color:levelColor}[{level-name}]{reset} ({file-name}:{line}:{column}) {data}" + "pattern": "{color:gray}{timestamp}{reset} {color:levelColor}[{level-name}]{reset} ({file-name}:{line}:{column}) {data}", + "customPattern": "{color:gray}{pretty-timestamp}{reset} {color:tagColor}[{tag}]{reset} {color:contextColor}({context}){reset} {data}", + "customColors": { + "GET": "green", + "POST": "blue", + "DELETE": "red" + } } ``` @@ -84,20 +90,23 @@ constructor > environment > logger.json > defaults ### Supported Environment Variables -| Variable | Description | -|------------------------|------------------------------------------| -| `LOG_LEVEL` | Log level (`debug`, `info`, etc.) | -| `LOG_DIRECTORY` | Log directory path (default: `logs`) | -| `LOG_DISABLE_FILE` | Disable file output (`true` or `false`) | -| `LOG_ROTATE` | Enable daily rotation | -| `LOG_MAX_FILES` | Max rotated files to keep | -| `LOG_CONSOLE` | Enable console output | -| `LOG_CONSOLE_COLOR` | Enable ANSI color in console output | -| `LOG_DATE_FORMAT` | Date format for display timestamp | -| `LOG_TIMEZONE` | Timezone (`local` or IANA string) | -| `LOG_SILENT` | Completely disable output | -| `LOG_PATTERN` | Custom log format for console | -| `LOG_PRETTY_PRINT` | Pretty-print objects in console output | +| Variable | Description | +|------------------------|-----------------------------------------------| +| `LOG_LEVEL` | Log level (`debug`, `info`, etc.) | +| `LOG_LEVEL_COLOR` | Comma-separated list of `TAG:color` pairs | +| `LOG_DIRECTORY` | Log directory path (default: `logs`) | +| `LOG_DISABLE_FILE` | Disable file output (`true` or `false`) | +| `LOG_ROTATE` | Enable daily rotation | +| `LOG_MAX_FILES` | Max rotated files to keep | +| `LOG_CONSOLE` | Enable console output | +| `LOG_CONSOLE_COLOR` | Enable ANSI color in console output | +| `LOG_DATE_FORMAT` | Date format for display timestamp | +| `LOG_TIMEZONE` | Timezone (`local` or IANA string) | +| `LOG_SILENT` | Completely disable output | +| `LOG_PATTERN` | Custom log format for console | +| `LOG_PRETTY_PRINT` | Pretty-print objects in console output | +| `LOG_CUSTOM_PATTERN` | Pattern used for `echo.custom()` logs | +| `LOG_CUSTOM_COLORS` | Comma-separated list of `TAG:color` pairs | --- @@ -105,18 +114,45 @@ constructor > environment > logger.json > defaults These tokens are replaced in the log pattern: -| Token | Description | -|---------------|-----------------------------------------| -| `{timestamp}` | Formatted display timestamp | -| `{level-name}`| Uppercase log level (e.g. DEBUG) | -| `{level}` | Numeric log level | -| `{file-name}` | Source filename | -| `{line}` | Line number in source | -| `{column}` | Column number in source | -| `{data}` | Formatted log data (message/object) | -| `{id}` | Unique short ID for the log | -| `{color:*}` | ANSI color start (e.g. `{color:red}`) | -| `{reset}` | Resets console color | +| Token | Description | +|----------------------|-------------------------------------------------| +| `{timestamp}` | ISO timestamp string | +| `{pretty-timestamp}` | Formatted display timestamp | +| `{level-name}` | Uppercase log level (e.g. DEBUG) | +| `{level}` | Numeric log level | +| `{file-name}` | Source filename | +| `{line}` | Line number in source | +| `{column}` | Column number in source | +| `{data}` | Formatted log data (message/object) | +| `{id}` | Unique short ID for the log | +| `{tag}` | Custom tag used in `echo.custom()` | +| `{context}` | Custom context in `echo.custom()` | +| `{color:*}` | ANSI color start (e.g. `{color:red}`) | +| `{color:levelColor}` | Dynamic color based on log level | +| `{color:tagColor}` | Color for custom tag | +| `{color:contextColor}`| Color for custom context | +| `{reset}` | Resets console color | + +--- + +## Custom Log Entries + +You can log arbitrary tagged messages with `echo.custom(tag, context, message)`: + +```ts +echo.custom("GET", "/health", { status: 200 }); +``` + +The output format is controlled by: + +- `customPattern` — e.g. `{pretty-timestamp} [GET] (/health) { status: 200 }` +- `customColors` — define colors for tags like `"GET": "green"` + +### Example output + +``` +2025-05-24 16:22:00.123 [GET] (/health) { status: 200 } +``` --- diff --git a/src/index.ts b/src/index.ts index 3a29308..f1fe34f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,8 +7,10 @@ import { statSync, } from "node:fs"; import { resolve } from "node:path"; -import { getCallerInfo, parsePattern } from "@lib/char"; +import { format, inspect } from "node:util"; +import { getCallerInfo, getTimestamp, parsePattern } from "@lib/char"; import { + ansiColors, defaultConfig, loadEnvConfig, loadLoggerConfig, @@ -111,6 +113,47 @@ class Echo { public trace(data: unknown): void { this.log("trace", data); } + + public custom(tag: string, context: string, message: unknown): void { + if (this.config.silent || !this.config.console) return; + + const timestamps = getTimestamp(this.config); + + const normalizedTag = tag.toUpperCase(); + const tagColor = this.config.consoleColor + ? (ansiColors[this.config.customColors?.[normalizedTag] ?? "green"] ?? "") + : ""; + const contextColor = this.config.consoleColor ? ansiColors.cyan : ""; + const gray = this.config.consoleColor ? ansiColors.gray : ""; + const reset = this.config.consoleColor ? ansiColors.reset : ""; + + const resolvedData = + this.config.prettyPrint && typeof message === "object" && message !== null + ? inspect(message, { + depth: null, + colors: this.config.consoleColor, + breakLength: 1, + compact: false, + }) + : format(message); + + const pattern = + this.config.customPattern ?? + "{color:gray}{pretty-timestamp}{reset} {color:tagColor}[{tag}]{reset} {color:contextColor}({context}){reset} {data}"; + + const line = pattern + .replace(/{timestamp}/g, timestamps.timestamp) + .replace(/{pretty-timestamp}/g, timestamps.prettyTimestamp) + .replace(/{tag}/g, tag) + .replace(/{context}/g, context) + .replace(/{data}/g, resolvedData) + .replace(/{color:gray}/g, gray) + .replace(/{color:tagColor}/g, tagColor) + .replace(/{color:contextColor}/g, contextColor) + .replace(/{reset}/g, reset); + + console.log(line); + } } const echo = new Echo(); diff --git a/src/lib/char.ts b/src/lib/char.ts index db0f309..7625656 100644 --- a/src/lib/char.ts +++ b/src/lib/char.ts @@ -146,7 +146,7 @@ function parsePattern(ctx: PatternContext): string { config.prettyPrint && typeof data === "object" && data !== null ? inspect(data, { depth: null, - colors: true, + colors: config.consoleColor, breakLength: 1, compact: false, }) @@ -155,7 +155,8 @@ function parsePattern(ctx: PatternContext): string { const numericLevel: LogLevelValue = logLevelValues[level]; const final = config.pattern - .replace(/{timestamp}/g, config.prettyPrint ? prettyTimestamp : timestamp) + .replace(/{timestamp}/g, timestamp) + .replace(/{pretty-timestamp}/g, prettyTimestamp) .replace(/{level-name}/g, level.toUpperCase()) .replace(/{level}/g, String(numericLevel)) .replace(/{file-name}/g, fileName) @@ -167,4 +168,4 @@ function parsePattern(ctx: PatternContext): string { return replaceColorTokens(final, level, config); } -export { parsePattern, getCallerInfo }; +export { parsePattern, getCallerInfo, getTimestamp, generateShortId }; diff --git a/src/lib/config.ts b/src/lib/config.ts index 6f5887c..c372a51 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -54,7 +54,7 @@ const defaultConfig: Required = { silent: false, pattern: - "{color:gray}{timestamp}{reset} {color:levelColor}[{level-name}]{reset} {color:gray}({reset}{file-name}:{line}:{column}{color:gray}){reset} {data}", + "{color:gray}{pretty-timestamp}{reset} {color:levelColor}[{level-name}]{reset} {color:gray}({reset}{file-name}:{line}:{column}{color:gray}){reset} {data}", levelColor: { trace: "cyan", @@ -65,6 +65,10 @@ const defaultConfig: Required = { fatal: "red", }, + customColors: {}, + customPattern: + "{color:gray}{pretty-timestamp}{reset} {color:tagColor}[{tag}]{reset} {color:contextColor}({context}){reset} {data}", + prettyPrint: true, }; @@ -100,6 +104,30 @@ function loadEnvConfig(): LoggerConfig { if (process.env.LOG_PRETTY_PRINT) config.prettyPrint = process.env.LOG_PRETTY_PRINT === "true"; + if (process.env.LOG_LEVEL_COLOR) { + const colors = process.env.LOG_LEVEL_COLOR.split(","); + for (const color of colors) { + const [level, colorName] = color.split(":"); + if (logLevelValues[level as LogLevel] !== undefined) { + config.levelColor = { + ...config.levelColor, + [level as LogLevel]: colorName as keyof typeof ansiColors, + }; + } + } + } + + if (process.env.LOG_CUSTOM_COLORS) { + const colors = process.env.LOG_CUSTOM_COLORS.split(","); + for (const color of colors) { + const [tag, colorName] = color.split(":"); + config.customColors = { + ...config.customColors, + [tag]: colorName as keyof typeof ansiColors, + }; + } + } + return config; } diff --git a/types/index.ts b/types/index.ts index 27e9fb1..0d73707 100644 --- a/types/index.ts +++ b/types/index.ts @@ -22,6 +22,9 @@ type LoggerConfig = { pattern?: string; levelColor?: Partial>; + customPattern?: string; + customColors?: Record; + prettyPrint?: boolean; };