- added fileNameFormat
All checks were successful
Code quality checks / biome (push) Successful in 11s

- fixed up some logic aka duplicate logic
- actually verify config
-add more ansiColors
-default to no max files
This commit is contained in:
creations 2025-06-11 10:13:27 -04:00
parent 5c88ce3e28
commit f7d2c7084b
Signed by: creations
GPG key ID: 8F553AA4320FC711
8 changed files with 491 additions and 151 deletions

View file

@ -7,6 +7,7 @@ import {
unlinkSync,
} from "node:fs";
import { join } from "node:path";
import { serializeLogData } from "@lib/char";
import type { LogLevel, LoggerConfig } from "@types";
import { format } from "date-fns-tz";
@ -14,15 +15,30 @@ class FileLogger {
private stream: WriteStream | null = null;
private filePath = "";
private date = "";
private fileNameFormat = "yyyy-MM-dd";
constructor(private readonly config: Required<LoggerConfig>) {
if (!existsSync(this.config.directory)) {
mkdirSync(this.config.directory, { recursive: true });
}
if (this.config.fileNameFormat) {
try {
format(new Date(), this.config.fileNameFormat, {
timeZone: this.config.timezone,
});
this.fileNameFormat = this.config.fileNameFormat;
} catch (error) {
throw new Error(
`[@atums/echo] Invalid fileNameFormat: ${this.config.fileNameFormat}. Error: ${error instanceof Error ? error.message : "Unknown error"}`,
);
}
}
}
private getLogFilePath(dateStr: string): string {
return join(this.config.directory, `${dateStr}.jsonl`);
const fileName = `${dateStr}.jsonl`;
return join(this.config.directory, fileName);
}
private resetStream(path: string): void {
@ -31,13 +47,32 @@ class FileLogger {
this.filePath = path;
}
private generateFileRegex(): RegExp {
const pattern = this.fileNameFormat
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
.replace(/\\y\\y\\y\\y/g, "\\d{4}")
.replace(/\\M\\M/g, "\\d{2}")
.replace(/\\d\\d/g, "\\d{2}")
.replace(/\\H\\H/g, "\\d{2}")
.replace(/\\m\\m/g, "\\d{2}")
.replace(/\\s\\s/g, "\\d{2}")
.replace(/\\S\\S\\S/g, "\\d{3}");
return new RegExp(`^${pattern}\\.jsonl$`);
}
private pruneOldLogs(): void {
if (this.config.maxFiles && this.config.maxFiles < 1) {
if (this.config.maxFiles === null) {
return;
}
if (this.config.maxFiles < 1) {
throw new Error("[@atums/echo] maxFiles must be >= 1 if set.");
}
const fileRegex = this.generateFileRegex();
const files = readdirSync(this.config.directory)
.filter((file) => /^\d{4}-\d{2}-\d{2}\.jsonl$/.test(file))
.filter((file) => fileRegex.test(file))
.sort();
const excess = files.slice(
@ -52,6 +87,13 @@ class FileLogger {
}
}
private getFilePath(dateStr?: string): string {
if (this.config.rotate && dateStr) {
return this.getLogFilePath(dateStr);
}
return join(this.config.directory, "log.jsonl");
}
public write(
level: LogLevel | string,
data: unknown,
@ -67,27 +109,6 @@ class FileLogger {
if (this.config.disableFile) return;
const now = new Date();
let path: string;
if (this.config.rotate) {
const dateStr = format(now, "yyyy-MM-dd", {
timeZone: this.config.timezone,
});
path = this.getLogFilePath(dateStr);
if (!this.stream || this.filePath !== path || this.date !== dateStr) {
this.date = dateStr;
this.resetStream(path);
this.pruneOldLogs();
}
} else {
path = join(this.config.directory, "log.jsonl");
if (!this.stream || this.filePath !== path) {
this.resetStream(path);
}
}
const line = `${JSON.stringify({
timestamp: new Date(meta.timestamp).getTime(),
level,
@ -95,18 +116,28 @@ class FileLogger {
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,
data: serializeLogData(data),
})}\n`;
let path: string;
const dateStr = this.config.rotate
? format(now, this.fileNameFormat, { timeZone: this.config.timezone })
: undefined;
const needsRotation = this.config.rotate && this.date !== dateStr;
path = this.getFilePath(dateStr);
if (!this.stream || needsRotation || this.filePath !== path) {
if (this.config.rotate && dateStr) {
this.date = dateStr;
}
this.resetStream(path);
if (needsRotation) {
this.pruneOldLogs();
}
}
try {
this.stream?.write(line);
} catch (err) {