- added fileNameFormat
All checks were successful
Code quality checks / biome (push) Successful in 11s
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:
parent
5c88ce3e28
commit
f7d2c7084b
8 changed files with 491 additions and 151 deletions
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue