add file logging, readme, so on
All checks were successful
Code quality checks / biome (push) Successful in 8s
All checks were successful
Code quality checks / biome (push) Successful in 8s
This commit is contained in:
parent
542beb82a4
commit
499b8ec46d
6 changed files with 360 additions and 24 deletions
181
README.md
Normal file
181
README.md
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
# @atums/echo
|
||||||
|
|
||||||
|
A minimal, flexible logger for Node with:
|
||||||
|
|
||||||
|
- Colored console output
|
||||||
|
- Daily `.jsonl` file logging
|
||||||
|
- Configurable output patterns
|
||||||
|
- Structured logs with caller metadata
|
||||||
|
- Fully typed config with environment/file/constructor override
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Console and file logging with level-based filtering
|
||||||
|
- Colored output with ANSI formatting
|
||||||
|
- Daily rotated `.jsonl` files
|
||||||
|
- Supports runtime configuration merging
|
||||||
|
- Auto-formatted output using custom patterns
|
||||||
|
- Includes caller file, line, and column
|
||||||
|
- Pretty-prints structured objects if enabled
|
||||||
|
- Flushes open file streams on exit
|
||||||
|
- Uses Biome and EditorConfig for formatting and linting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun add @atums/echo
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { echo } from "@atums/echo";
|
||||||
|
|
||||||
|
echo.info("App started");
|
||||||
|
echo.debug({ state: "init", ok: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw new Error("Something failed");
|
||||||
|
} catch (err) {
|
||||||
|
echo.error(err);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Logger config can be defined in three ways:
|
||||||
|
|
||||||
|
1. JSON file (e.g. `logger.json`)
|
||||||
|
2. Environment variables
|
||||||
|
3. Constructor override
|
||||||
|
|
||||||
|
Priority (highest to lowest):
|
||||||
|
|
||||||
|
```
|
||||||
|
constructor > environment > logger.json > defaults
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### logger.json example
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"directory": "logs",
|
||||||
|
"level": "debug",
|
||||||
|
"console": true,
|
||||||
|
"consoleColor": true,
|
||||||
|
"rotate": true,
|
||||||
|
"maxFiles": 3,
|
||||||
|
"prettyPrint": true,
|
||||||
|
"pattern": "{color:gray}{timestamp}{reset} {color:levelColor}[{level-name}]{reset} ({file-name}:{line}:{column}) {data}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 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 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pattern Tokens
|
||||||
|
|
||||||
|
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 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Examples
|
||||||
|
|
||||||
|
### Console (with colors)
|
||||||
|
|
||||||
|
```
|
||||||
|
2025-05-24 16:15:00.000 [INFO] (index.ts:3:6) Server started
|
||||||
|
```
|
||||||
|
|
||||||
|
### File (`logs/2025-05-24.jsonl`)
|
||||||
|
|
||||||
|
Each line is structured JSON:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"timestamp": 1748115300000,
|
||||||
|
"level": "info",
|
||||||
|
"id": "aB4cD9xZ",
|
||||||
|
"file": "index.ts",
|
||||||
|
"line": "3",
|
||||||
|
"column": "6",
|
||||||
|
"data": "Server started"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If an error is logged:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"timestamp": 1748115301000,
|
||||||
|
"level": "error",
|
||||||
|
"id": "qW3eR7tU",
|
||||||
|
"file": "index.ts",
|
||||||
|
"line": "10",
|
||||||
|
"column": "12",
|
||||||
|
"data": {
|
||||||
|
"name": "Error",
|
||||||
|
"message": "Something failed",
|
||||||
|
"stack": "Error: Something failed\n at index.ts:10:12"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
This project uses:
|
||||||
|
|
||||||
|
- TypeScript
|
||||||
|
- Bun runtime
|
||||||
|
- Biome for formatting/linting
|
||||||
|
- JSONL for structured file output
|
||||||
|
- `date-fns-tz` for timezone support
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
BSD 3-Clause [License](License)
|
|
@ -7,13 +7,14 @@ import {
|
||||||
statSync,
|
statSync,
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
import { parsePattern } from "@lib/char";
|
import { getCallerInfo, parsePattern } from "@lib/char";
|
||||||
import {
|
import {
|
||||||
defaultConfig,
|
defaultConfig,
|
||||||
loadEnvConfig,
|
loadEnvConfig,
|
||||||
loadLoggerConfig,
|
loadLoggerConfig,
|
||||||
logLevelValues,
|
logLevelValues,
|
||||||
} from "@lib/config";
|
} from "@lib/config";
|
||||||
|
import { writeLogJson } from "@lib/file";
|
||||||
import type { LogLevel, LoggerConfig } from "@types";
|
import type { LogLevel, LoggerConfig } from "@types";
|
||||||
|
|
||||||
class Echo {
|
class Echo {
|
||||||
|
@ -73,6 +74,7 @@ class Echo {
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
const meta = getCallerInfo(this.config);
|
||||||
const line = parsePattern({ level, data, config: this.config });
|
const line = parsePattern({ level, data, config: this.config });
|
||||||
|
|
||||||
if (this.config.console) {
|
if (this.config.console) {
|
||||||
|
@ -80,6 +82,10 @@ class Echo {
|
||||||
line,
|
line,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.config.disableFile) {
|
||||||
|
writeLogJson(level, data, meta, this.config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public debug(data: unknown): void {
|
public debug(data: unknown): void {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { basename } from "node:path";
|
import { basename } from "node:path";
|
||||||
import { format } from "node:util";
|
import { format, inspect } from "node:util";
|
||||||
import { format as formatDate } from "date-fns-tz";
|
import { format as formatDate } from "date-fns-tz";
|
||||||
|
|
||||||
import { ansiColors, logLevelValues } from "@lib/config";
|
import { ansiColors, defaultLevelColor, logLevelValues } from "@lib/config";
|
||||||
import type {
|
import type {
|
||||||
LogLevel,
|
LogLevel,
|
||||||
LogLevelValue,
|
LogLevelValue,
|
||||||
|
@ -10,28 +10,43 @@ import type {
|
||||||
PatternContext,
|
PatternContext,
|
||||||
} from "@types";
|
} from "@types";
|
||||||
|
|
||||||
function getTimestamp(config: Required<LoggerConfig>): string {
|
function getTimestamp(config: Required<LoggerConfig>): {
|
||||||
|
prettyTimestamp: string;
|
||||||
|
timestamp: string;
|
||||||
|
} {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
if (config.timezone === "local") {
|
if (config.timezone === "local") {
|
||||||
return formatDate(now, config.dateFormat);
|
return {
|
||||||
|
prettyTimestamp: formatDate(now, config.dateFormat),
|
||||||
|
timestamp: now.toISOString(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return formatDate(now, config.dateFormat, {
|
return {
|
||||||
timeZone: config.timezone,
|
prettyTimestamp: formatDate(now, config.dateFormat, {
|
||||||
});
|
timeZone: config.timezone,
|
||||||
|
}),
|
||||||
|
timestamp: now.toISOString(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCallerInfo(config: Required<LoggerConfig>): {
|
function getCallerInfo(config: Required<LoggerConfig>): {
|
||||||
|
id: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
line: string;
|
line: string;
|
||||||
column: string;
|
column: string;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
|
prettyTimestamp: string;
|
||||||
} {
|
} {
|
||||||
|
const id = generateShortId();
|
||||||
|
|
||||||
const fallback = {
|
const fallback = {
|
||||||
|
id: id,
|
||||||
fileName: "unknown",
|
fileName: "unknown",
|
||||||
line: "0",
|
line: "0",
|
||||||
timestamp: getTimestamp(config),
|
timestamp: getTimestamp(config).timestamp,
|
||||||
|
prettyTimestamp: getTimestamp(config).prettyTimestamp,
|
||||||
column: "0",
|
column: "0",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,10 +69,12 @@ function getCallerInfo(config: Required<LoggerConfig>): {
|
||||||
const columnNumber = fileURLMatch[3];
|
const columnNumber = fileURLMatch[3];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: id,
|
||||||
fileName: basename(fullPath),
|
fileName: basename(fullPath),
|
||||||
line: lineNumber,
|
line: lineNumber,
|
||||||
column: columnNumber,
|
column: columnNumber,
|
||||||
timestamp: getTimestamp(config),
|
timestamp: getTimestamp(config).timestamp,
|
||||||
|
prettyTimestamp: getTimestamp(config).prettyTimestamp,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,10 +93,12 @@ function getCallerInfo(config: Required<LoggerConfig>): {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: id,
|
||||||
fileName: basename(fullPath),
|
fileName: basename(fullPath),
|
||||||
line: lineNumber,
|
line: lineNumber,
|
||||||
column: columnNumber,
|
column: columnNumber,
|
||||||
timestamp: getTimestamp(config),
|
timestamp: getTimestamp(config).timestamp,
|
||||||
|
prettyTimestamp: getTimestamp(config).prettyTimestamp,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,33 +126,45 @@ function replaceColorTokens(
|
||||||
): string {
|
): string {
|
||||||
return input
|
return input
|
||||||
.replace(/{color:(\w+)}/g, (_, colorKey) => {
|
.replace(/{color:(\w+)}/g, (_, colorKey) => {
|
||||||
|
if (!config.consoleColor) return "";
|
||||||
if (colorKey === "levelColor") {
|
if (colorKey === "levelColor") {
|
||||||
const colorForLevel = config.levelColor?.[level];
|
const colorForLevel =
|
||||||
|
config.levelColor?.[level] ?? defaultLevelColor[level];
|
||||||
return ansiColors[colorForLevel ?? ""] ?? "";
|
return ansiColors[colorForLevel ?? ""] ?? "";
|
||||||
}
|
}
|
||||||
return ansiColors[colorKey] ?? "";
|
return ansiColors[colorKey] ?? "";
|
||||||
})
|
})
|
||||||
.replace(/{reset}/g, ansiColors.reset);
|
.replace(/{reset}/g, config.consoleColor ? ansiColors.reset : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function parsePattern(ctx: PatternContext): string {
|
function parsePattern(ctx: PatternContext): string {
|
||||||
const { level, data, config } = ctx;
|
const { level, data, config } = ctx;
|
||||||
|
|
||||||
const { fileName, line, column, timestamp } = getCallerInfo(config);
|
const { id, fileName, line, column, timestamp, prettyTimestamp } =
|
||||||
const resolvedData: string = format(data);
|
getCallerInfo(config);
|
||||||
|
const resolvedData: string =
|
||||||
|
config.prettyPrint && typeof data === "object" && data !== null
|
||||||
|
? inspect(data, {
|
||||||
|
depth: null,
|
||||||
|
colors: false,
|
||||||
|
breakLength: 1,
|
||||||
|
compact: false,
|
||||||
|
})
|
||||||
|
: format(data);
|
||||||
|
|
||||||
const numericLevel: LogLevelValue = logLevelValues[level];
|
const numericLevel: LogLevelValue = logLevelValues[level];
|
||||||
|
|
||||||
const final: string = config.pattern
|
const final = config.pattern
|
||||||
.replace(/{timestamp}/g, timestamp)
|
.replace(/{timestamp}/g, config.prettyPrint ? prettyTimestamp : timestamp)
|
||||||
.replace(/{level-name}/g, level.toUpperCase())
|
.replace(/{level-name}/g, level.toUpperCase())
|
||||||
.replace(/{level}/g, String(numericLevel))
|
.replace(/{level}/g, String(numericLevel))
|
||||||
.replace(/{file-name}/g, fileName)
|
.replace(/{file-name}/g, fileName)
|
||||||
.replace(/{line}/g, line)
|
.replace(/{line}/g, line)
|
||||||
.replace(/{data}/g, resolvedData)
|
.replace(/{data}/g, resolvedData)
|
||||||
.replace(/{id}/g, generateShortId())
|
.replace(/{id}/g, id)
|
||||||
.replace(/{column}/g, column);
|
.replace(/{column}/g, column);
|
||||||
|
|
||||||
return config.consoleColor ? replaceColorTokens(final, level, config) : final;
|
return replaceColorTokens(final, level, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { parsePattern };
|
export { parsePattern, getCallerInfo };
|
||||||
|
|
|
@ -12,6 +12,16 @@ const logLevelValues = {
|
||||||
silent: 70,
|
silent: 70,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultLevelColor: Record<LogLevel, keyof typeof ansiColors> = {
|
||||||
|
trace: "cyan",
|
||||||
|
debug: "blue",
|
||||||
|
info: "green",
|
||||||
|
warn: "yellow",
|
||||||
|
error: "red",
|
||||||
|
fatal: "red",
|
||||||
|
silent: "gray",
|
||||||
|
};
|
||||||
|
|
||||||
const ansiColors: Record<string, string> = {
|
const ansiColors: Record<string, string> = {
|
||||||
reset: "\x1b[0m",
|
reset: "\x1b[0m",
|
||||||
dim: "\x1b[2m",
|
dim: "\x1b[2m",
|
||||||
|
@ -33,7 +43,6 @@ const defaultConfig: Required<LoggerConfig> = {
|
||||||
disableFile: false,
|
disableFile: false,
|
||||||
|
|
||||||
rotate: true,
|
rotate: true,
|
||||||
maxSizeMB: 5,
|
|
||||||
maxFiles: 3,
|
maxFiles: 3,
|
||||||
|
|
||||||
console: true,
|
console: true,
|
||||||
|
@ -55,6 +64,8 @@ const defaultConfig: Required<LoggerConfig> = {
|
||||||
error: "red",
|
error: "red",
|
||||||
fatal: "red",
|
fatal: "red",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
prettyPrint: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
function loadLoggerConfig(configPath = "logger.json"): LoggerConfig {
|
function loadLoggerConfig(configPath = "logger.json"): LoggerConfig {
|
||||||
|
@ -75,8 +86,6 @@ function loadEnvConfig(): LoggerConfig {
|
||||||
if (process.env.LOG_DISABLE_FILE)
|
if (process.env.LOG_DISABLE_FILE)
|
||||||
config.disableFile = process.env.LOG_DISABLE_FILE === "true";
|
config.disableFile = process.env.LOG_DISABLE_FILE === "true";
|
||||||
if (process.env.LOG_ROTATE) config.rotate = process.env.LOG_ROTATE === "true";
|
if (process.env.LOG_ROTATE) config.rotate = process.env.LOG_ROTATE === "true";
|
||||||
if (process.env.LOG_MAX_SIZE_MB)
|
|
||||||
config.maxSizeMB = Number.parseInt(process.env.LOG_MAX_SIZE_MB, 10);
|
|
||||||
if (process.env.LOG_MAX_FILES)
|
if (process.env.LOG_MAX_FILES)
|
||||||
config.maxFiles = Number.parseInt(process.env.LOG_MAX_FILES, 10);
|
config.maxFiles = Number.parseInt(process.env.LOG_MAX_FILES, 10);
|
||||||
if (process.env.LOG_CONSOLE)
|
if (process.env.LOG_CONSOLE)
|
||||||
|
@ -88,12 +97,15 @@ function loadEnvConfig(): LoggerConfig {
|
||||||
if (process.env.LOG_TIMEZONE) config.timezone = process.env.LOG_TIMEZONE;
|
if (process.env.LOG_TIMEZONE) config.timezone = process.env.LOG_TIMEZONE;
|
||||||
if (process.env.LOG_SILENT) config.silent = process.env.LOG_SILENT === "true";
|
if (process.env.LOG_SILENT) config.silent = process.env.LOG_SILENT === "true";
|
||||||
if (process.env.LOG_PATTERN) config.pattern = process.env.LOG_PATTERN;
|
if (process.env.LOG_PATTERN) config.pattern = process.env.LOG_PATTERN;
|
||||||
|
if (process.env.LOG_PRETTY_PRINT)
|
||||||
|
config.prettyPrint = process.env.LOG_PRETTY_PRINT === "true";
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
defaultConfig,
|
defaultConfig,
|
||||||
|
defaultLevelColor,
|
||||||
loadLoggerConfig,
|
loadLoggerConfig,
|
||||||
loadEnvConfig,
|
loadEnvConfig,
|
||||||
logLevelValues,
|
logLevelValues,
|
||||||
|
|
105
src/lib/file.ts
Normal file
105
src/lib/file.ts
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import {
|
||||||
|
type WriteStream,
|
||||||
|
createWriteStream,
|
||||||
|
existsSync,
|
||||||
|
mkdirSync,
|
||||||
|
} from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import type { LogLevel, LoggerConfig } from "@types";
|
||||||
|
import { format } from "date-fns-tz";
|
||||||
|
|
||||||
|
let currentStream: WriteStream | null = null;
|
||||||
|
let currentFilePath = "";
|
||||||
|
let currentDate = "";
|
||||||
|
|
||||||
|
function getLogFilePath(
|
||||||
|
config: Required<LoggerConfig>,
|
||||||
|
dateStr: string,
|
||||||
|
): string {
|
||||||
|
return join(config.directory, `${dateStr}.jsonl`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetStream(path: string): void {
|
||||||
|
currentStream?.end();
|
||||||
|
currentStream = createWriteStream(path, { flags: "a", encoding: "utf-8" });
|
||||||
|
currentFilePath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writeLogJson(
|
||||||
|
level: LogLevel,
|
||||||
|
data: unknown,
|
||||||
|
meta: {
|
||||||
|
id: string;
|
||||||
|
fileName: string;
|
||||||
|
line: string;
|
||||||
|
column: string;
|
||||||
|
timestamp: string;
|
||||||
|
prettyTimestamp: string;
|
||||||
|
},
|
||||||
|
config: Required<LoggerConfig>,
|
||||||
|
): void {
|
||||||
|
if (config.disableFile) return;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
if (!existsSync(config.directory)) {
|
||||||
|
mkdirSync(config.directory, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
let filePath: string;
|
||||||
|
|
||||||
|
if (config.rotate) {
|
||||||
|
const dateStr = format(now, "yyyy-MM-dd", {
|
||||||
|
timeZone: config.timezone,
|
||||||
|
});
|
||||||
|
filePath = getLogFilePath(config, dateStr);
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentStream === null ||
|
||||||
|
currentFilePath !== filePath ||
|
||||||
|
currentDate !== dateStr
|
||||||
|
) {
|
||||||
|
currentDate = dateStr;
|
||||||
|
resetStream(filePath);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filePath = join(config.directory, "log.jsonl");
|
||||||
|
|
||||||
|
if (currentStream === null || currentFilePath !== filePath) {
|
||||||
|
resetStream(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const line = `${JSON.stringify({
|
||||||
|
timestamp: new Date(meta.timestamp).getTime(),
|
||||||
|
level,
|
||||||
|
id: meta.id,
|
||||||
|
file: meta.fileName,
|
||||||
|
line: meta.line,
|
||||||
|
column: meta.column,
|
||||||
|
data:
|
||||||
|
data instanceof Error
|
||||||
|
? {
|
||||||
|
name: data.name,
|
||||||
|
message: data.message,
|
||||||
|
stack: data.stack,
|
||||||
|
}
|
||||||
|
: typeof data === "string" || typeof data === "number"
|
||||||
|
? data
|
||||||
|
: data,
|
||||||
|
})}\n`;
|
||||||
|
|
||||||
|
if (currentStream === null) {
|
||||||
|
throw new Error("Logger stream is not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
currentStream.write(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on("exit", () => {
|
||||||
|
currentStream?.end();
|
||||||
|
});
|
||||||
|
process.on("SIGINT", () => {
|
||||||
|
currentStream?.end();
|
||||||
|
process.exit();
|
||||||
|
});
|
|
@ -9,7 +9,6 @@ type LoggerConfig = {
|
||||||
disableFile?: boolean;
|
disableFile?: boolean;
|
||||||
|
|
||||||
rotate?: boolean;
|
rotate?: boolean;
|
||||||
maxSizeMB?: number;
|
|
||||||
maxFiles?: number;
|
maxFiles?: number;
|
||||||
|
|
||||||
console?: boolean;
|
console?: boolean;
|
||||||
|
@ -22,6 +21,8 @@ type LoggerConfig = {
|
||||||
|
|
||||||
pattern?: string;
|
pattern?: string;
|
||||||
levelColor?: Partial<Record<LogLevel, keyof typeof ansiColors>>;
|
levelColor?: Partial<Record<LogLevel, keyof typeof ansiColors>>;
|
||||||
|
|
||||||
|
prettyPrint?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface PatternContext {
|
interface PatternContext {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue