try fix for windows, change how logging works allows much more flexibility
All checks were successful
Code quality checks / biome (push) Successful in 9s

This commit is contained in:
creations 2025-06-11 18:24:48 -04:00
parent 15d4978ac5
commit f313a8a329
Signed by: creations
GPG key ID: 8F553AA4320FC711
6 changed files with 297 additions and 51 deletions

View file

@ -77,7 +77,7 @@ class Echo {
accessSync(dir, constants.W_OK);
}
private log(level: LogLevel, data: unknown): void {
private log(level: LogLevel, ...args: unknown[]): void {
if (
this.config.silent ||
logLevelValues[this.config.level] > logLevelValues[level]
@ -85,39 +85,38 @@ class Echo {
return;
const meta = getCallerInfo(this.config);
const line = parsePattern({ level, data, config: this.config });
const line = parsePattern({ level, data: args, config: this.config });
if (this.config.console) {
console[getConsoleMethod(level)](line);
}
if (!this.config.disableFile && this.fileLogger) {
this.fileLogger.write(level, data, meta);
this.fileLogger.write(level, args, meta);
}
}
public debug(data: unknown): void {
this.log("debug", data);
public debug(...args: unknown[]): void {
this.log("debug", ...args);
}
public info(data: unknown): void {
this.log("info", data);
public info(...args: unknown[]): void {
this.log("info", ...args);
}
public warn(data: unknown): void {
this.log("warn", data);
public warn(...args: unknown[]): void {
this.log("warn", ...args);
}
public error(data: unknown): void {
this.log("error", data);
public error(...args: unknown[]): void {
this.log("error", ...args);
}
public fatal(data: unknown): void {
this.log("fatal", data);
public fatal(...args: unknown[]): void {
this.log("fatal", ...args);
}
public trace(data: unknown): void {
this.log("trace", data);
public trace(...args: unknown[]): void {
this.log("trace", ...args);
}
public custom(tag: string, context: string, data: unknown): void {

View file

@ -1,8 +1,8 @@
import { basename } from "node:path";
import { format, inspect } from "node:util";
import { ansiColors, defaultLevelColor, logLevelValues } from "@lib/config";
import { format as formatDate } from "date-fns-tz";
import { ansiColors, defaultLevelColor, logLevelValues } from "@lib/config";
import type {
LogLevel,
LogLevelValue,
@ -59,6 +59,7 @@ function getCallerInfo(config: Required<LoggerConfig>): {
for (let i = 1; i < lines.length; i++) {
const line = lines[i].trim();
// try file:// URLs first (works on all platforms)
const fileURLMatch = line.match(
/at\s+(?:.*\()?file:\/\/(.*):(\d+):(\d+)\)?/,
);
@ -79,16 +80,16 @@ function getCallerInfo(config: Required<LoggerConfig>): {
};
}
const rawMatch = line.match(/at\s+(?:.*\()?(.+):(\d+):(\d+)\)?/);
if (rawMatch) {
const [_, fullPath, lineNumber, columnNumber] = rawMatch;
const pathMatch = parseStackTracePath(line);
if (pathMatch) {
const { fullPath, lineNumber, columnNumber } = pathMatch;
const isInternal =
fullPath.includes("atums.echo") || fullPath.includes("@atums/echo");
if (isInternal) continue;
return {
id: id,
id,
fileName: basename(fullPath),
line: lineNumber,
column: columnNumber,
@ -101,6 +102,59 @@ function getCallerInfo(config: Required<LoggerConfig>): {
return fallback;
}
function parseStackTracePath(line: string): {
fullPath: string;
lineNumber: string;
columnNumber: string;
} | null {
// remove "at " prefix and trim
const cleanLine = line.replace(/^\s*at\s+/, "").trim();
let pathPart: string;
// extract path from parentheses if present
const parenMatch = cleanLine.match(/\(([^)]+)\)$/);
if (parenMatch) {
pathPart = parenMatch[1];
} else {
pathPart = cleanLine;
}
let match: RegExpMatchArray | null = null;
if (process.platform === "win32") {
// windows-specific parsing
// handle drive letters (C:) vs line numbers (:10:5)
match = pathPart.match(
/^((?:[a-zA-Z]:|\\\\[^\\]+\\[^\\]+|[^:]+)):(\d+):(\d+)$/,
);
if (!match) {
// try alternative windows format with forward slashes
match = pathPart.match(/^([a-zA-Z]:[^:]+):(\d+):(\d+)$/);
}
if (!match) {
// try UNC path format
match = pathPart.match(/^(\\\\[^:]+):(\d+):(\d+)$/);
}
} else {
// unix-like systems (Linux, macOS)
match = pathPart.match(/^([^:]+):(\d+):(\d+)$/);
}
if (match) {
const [_, fullPath, lineNumber, columnNumber] = match;
return {
fullPath: fullPath.trim(),
lineNumber,
columnNumber,
};
}
return null;
}
function generateShortId(length = 8): string {
const chars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@ -166,13 +220,63 @@ function serializeLogData(data: unknown): unknown {
};
}
if (typeof data === "string" || typeof data === "number") {
if (
typeof data === "string" ||
typeof data === "number" ||
typeof data === "boolean" ||
data === null ||
data === undefined
) {
return data;
}
if (typeof data === "object") {
try {
return JSON.parse(JSON.stringify(data));
} catch (err) {
if (
(err instanceof TypeError && err.message.includes("circular")) ||
(err instanceof Error && err.message.includes("cyclic"))
) {
return createSafeObjectRepresentation(data);
}
return String(data);
}
}
return data;
}
function createSafeObjectRepresentation(
obj: unknown,
seen = new WeakSet(),
): unknown {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (seen.has(obj as object)) {
return "[Circular Reference]";
}
seen.add(obj as object);
if (Array.isArray(obj)) {
return obj.map((item) => createSafeObjectRepresentation(item, seen));
}
const result: Record<string, unknown> = {};
for (const [key, value] of Object.entries(obj)) {
try {
result[key] = createSafeObjectRepresentation(value, seen);
} catch {
result[key] = "[Unserializable]";
}
}
return result;
}
function processPattern(
pattern: string,
tokens: PatternTokens,
@ -231,15 +335,23 @@ function processPattern(
return processed;
}
function formatMultipleData(
dataArray: unknown[],
config: Required<LoggerConfig>,
): string {
return dataArray.map((item) => formatData(item, config)).join(" ");
}
function parsePattern(ctx: PatternContext): string {
const { level, data, config } = ctx;
const { id, fileName, line, column, timestamp, prettyTimestamp } =
getCallerInfo(config);
const resolvedData: string = formatData(data, config);
const numericLevel: LogLevelValue = logLevelValues[level];
const resolvedData: string = Array.isArray(data)
? formatMultipleData(data, config)
: formatData(data, config);
const numericLevel: LogLevelValue = logLevelValues[level];
const tokens: PatternTokens = {
timestamp,
prettyTimestamp,
@ -251,7 +363,6 @@ function parsePattern(ctx: PatternContext): string {
data: resolvedData,
id,
};
return processPattern(config.pattern, tokens, config, level);
}

View file

@ -1,5 +1,6 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import type { LogLevel, LoggerConfig } from "@types";
const logLevelValues = {

View file

@ -8,9 +8,10 @@ import {
} 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";
import type { LogLevel, LoggerConfig } from "@types";
class FileLogger {
private stream: WriteStream | null = null;
private filePath = "";