fix the max files count and deletion missing ?!?!, fix readme to actually have all the values for the json example, refactor file to class, fix custom not logging to file?, allow public flush
All checks were successful
Code quality checks / biome (push) Successful in 9s

This commit is contained in:
creations 2025-05-25 08:27:20 -04:00
parent 108083e0f5
commit 2c4f3cf5de
Signed by: creations
GPG key ID: 8F553AA4320FC711
6 changed files with 187 additions and 130 deletions

View file

@ -3,103 +3,124 @@ import {
createWriteStream,
existsSync,
mkdirSync,
readdirSync,
unlinkSync,
} 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 = "";
class FileLogger {
private stream: WriteStream | null = null;
private filePath = "";
private date = "";
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 });
constructor(private readonly config: Required<LoggerConfig>) {
if (!existsSync(this.config.directory)) {
mkdirSync(this.config.directory, { recursive: true });
}
}
let filePath: string;
private getLogFilePath(dateStr: string): string {
return join(this.config.directory, `${dateStr}.jsonl`);
}
if (config.rotate) {
const dateStr = format(now, "yyyy-MM-dd", {
timeZone: config.timezone,
private resetStream(path: string): void {
this.stream?.end();
this.stream = createWriteStream(path, { flags: "a", encoding: "utf-8" });
this.filePath = path;
}
private pruneOldLogs(): void {
if (this.config.maxFiles && this.config.maxFiles < 1) {
throw new Error("[@atums/echo] maxFiles must be >= 1 if set.");
}
const files = readdirSync(this.config.directory)
.filter((file) => /^\d{4}-\d{2}-\d{2}\.jsonl$/.test(file))
.sort();
const excess = files.slice(
0,
Math.max(0, files.length - this.config.maxFiles),
);
for (const file of excess) {
try {
unlinkSync(join(this.config.directory, file));
} catch {}
}
}
public write(
level: LogLevel | string,
data: unknown,
meta: {
id: string;
fileName: string;
line: string;
column: string;
timestamp: string;
prettyTimestamp: string;
},
): void {
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,
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`;
try {
this.stream?.write(line);
} catch (err) {
if (this.config.console) {
throw new Error(`[@atums/echo] Failed to write to log file: ${err}`);
}
}
}
public flush(): Promise<void> {
return new Promise((resolve) => {
this.stream?.end(resolve);
});
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();
});
export { FileLogger };