fix build problems, add all actual log funcs, move types
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
d554599768
commit
542beb82a4
8 changed files with 305 additions and 53 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
/node_modules
|
||||
bun.lock
|
||||
.vscode
|
||||
logger.json
|
||||
dist
|
27
package.json
27
package.json
|
@ -7,14 +7,28 @@
|
|||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"lib/char": ["./dist/lib/char.d.ts"],
|
||||
"lib/config": ["./dist/lib/config.d.ts"]
|
||||
}
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
"require": "./dist/index.js"
|
||||
},
|
||||
"./lib/char": {
|
||||
"import": "./dist/lib/char.js",
|
||||
"require": "./dist/lib/char.js"
|
||||
},
|
||||
"./lib/config": {
|
||||
"import": "./dist/lib/config.js",
|
||||
"require": "./dist/lib/config.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && tsup src/index.ts --dts --out-dir dist --format esm,cjs",
|
||||
"lint": "bunx biome check",
|
||||
"lint:fix": "bunx biome check --fix",
|
||||
"cleanup": "rm -rf logs node_modules bun.lock"
|
||||
|
@ -22,11 +36,16 @@
|
|||
"license": "BSD-3-Clause",
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@types/bun": "^1.2.13"
|
||||
"@types/bun": "^1.2.13",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"files": ["dist", "README.md", "LICENSE"],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.creations.works/creations/logger.git"
|
||||
"url": "https://git.creations.works/atums/echo"
|
||||
},
|
||||
"dependencies": {
|
||||
"date-fns-tz": "^3.2.0"
|
||||
}
|
||||
}
|
||||
|
|
62
src/index.ts
62
src/index.ts
|
@ -7,20 +7,27 @@ import {
|
|||
statSync,
|
||||
} from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { defaultConfig, loadEnvConfig, loadLoggerConfig } from "@lib/config";
|
||||
import { parsePattern } from "@lib/char";
|
||||
import {
|
||||
defaultConfig,
|
||||
loadEnvConfig,
|
||||
loadLoggerConfig,
|
||||
logLevelValues,
|
||||
} from "@lib/config";
|
||||
import type { LogLevel, LoggerConfig } from "@types";
|
||||
|
||||
export class Echo {
|
||||
class Echo {
|
||||
private readonly directory: string;
|
||||
private readonly config: Required<LoggerConfig>;
|
||||
|
||||
constructor(configOrPath?: string | LoggerConfig) {
|
||||
constructor(config?: string | LoggerConfig) {
|
||||
const fileConfig: LoggerConfig =
|
||||
typeof configOrPath === "string"
|
||||
? loadLoggerConfig(configOrPath)
|
||||
typeof config === "string"
|
||||
? loadLoggerConfig(config)
|
||||
: loadLoggerConfig();
|
||||
|
||||
const overrideConfig: LoggerConfig =
|
||||
typeof configOrPath === "object" ? configOrPath : {};
|
||||
typeof config === "object" ? config : {};
|
||||
|
||||
const envConfig: LoggerConfig = loadEnvConfig();
|
||||
|
||||
|
@ -58,4 +65,47 @@ export class Echo {
|
|||
public getConfig(): Required<LoggerConfig> {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
private log(level: LogLevel, data: unknown): void {
|
||||
if (
|
||||
this.config.silent ||
|
||||
logLevelValues[this.config.level] > logLevelValues[level]
|
||||
)
|
||||
return;
|
||||
|
||||
const line = parsePattern({ level, data, config: this.config });
|
||||
|
||||
if (this.config.console) {
|
||||
console[level === "error" ? "error" : level === "warn" ? "warn" : "log"](
|
||||
line,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public debug(data: unknown): void {
|
||||
this.log("debug", data);
|
||||
}
|
||||
|
||||
public info(data: unknown): void {
|
||||
this.log("info", data);
|
||||
}
|
||||
|
||||
public warn(data: unknown): void {
|
||||
this.log("warn", data);
|
||||
}
|
||||
|
||||
public error(data: unknown): void {
|
||||
this.log("error", data);
|
||||
}
|
||||
|
||||
public fatal(data: unknown): void {
|
||||
this.log("fatal", data);
|
||||
}
|
||||
|
||||
public trace(data: unknown): void {
|
||||
this.log("trace", data);
|
||||
}
|
||||
}
|
||||
|
||||
const echo = new Echo();
|
||||
export { echo, Echo };
|
||||
|
|
143
src/lib/char.ts
143
src/lib/char.ts
|
@ -1,8 +1,139 @@
|
|||
function timestampToReadable(timestamp?: number): string {
|
||||
const date: Date =
|
||||
timestamp && !Number.isNaN(timestamp) ? new Date(timestamp) : new Date();
|
||||
if (Number.isNaN(date.getTime())) return "Invalid Date";
|
||||
return date.toISOString().replace("T", " ").replace("Z", "");
|
||||
import { basename } from "node:path";
|
||||
import { format } from "node:util";
|
||||
import { format as formatDate } from "date-fns-tz";
|
||||
|
||||
import { ansiColors, logLevelValues } from "@lib/config";
|
||||
import type {
|
||||
LogLevel,
|
||||
LogLevelValue,
|
||||
LoggerConfig,
|
||||
PatternContext,
|
||||
} from "@types";
|
||||
|
||||
function getTimestamp(config: Required<LoggerConfig>): string {
|
||||
const now = new Date();
|
||||
|
||||
if (config.timezone === "local") {
|
||||
return formatDate(now, config.dateFormat);
|
||||
}
|
||||
|
||||
return formatDate(now, config.dateFormat, {
|
||||
timeZone: config.timezone,
|
||||
});
|
||||
}
|
||||
|
||||
export { timestampToReadable };
|
||||
function getCallerInfo(config: Required<LoggerConfig>): {
|
||||
fileName: string;
|
||||
line: string;
|
||||
column: string;
|
||||
timestamp: string;
|
||||
} {
|
||||
const fallback = {
|
||||
fileName: "unknown",
|
||||
line: "0",
|
||||
timestamp: getTimestamp(config),
|
||||
column: "0",
|
||||
};
|
||||
|
||||
const stack = new Error().stack;
|
||||
if (!stack) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const lines = stack.split("\n");
|
||||
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
|
||||
const fileURLMatch = line.match(
|
||||
/at\s+(?:.*\()?file:\/\/(.*):(\d+):(\d+)\)?/,
|
||||
);
|
||||
if (fileURLMatch) {
|
||||
const fullPath = fileURLMatch[1];
|
||||
const lineNumber = fileURLMatch[2];
|
||||
const columnNumber = fileURLMatch[3];
|
||||
|
||||
return {
|
||||
fileName: basename(fullPath),
|
||||
line: lineNumber,
|
||||
column: columnNumber,
|
||||
timestamp: getTimestamp(config),
|
||||
};
|
||||
}
|
||||
|
||||
const rawMatch = line.match(/at\s+(\/.*):(\d+):(\d+)/);
|
||||
if (rawMatch) {
|
||||
const fullPath = rawMatch[1];
|
||||
const lineNumber = rawMatch[2];
|
||||
const columnNumber = rawMatch[3];
|
||||
|
||||
if (
|
||||
fullPath.includes("/logger/") ||
|
||||
fullPath.includes("/src/index.ts") ||
|
||||
fullPath.includes("/src/lib/")
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return {
|
||||
fileName: basename(fullPath),
|
||||
line: lineNumber,
|
||||
column: columnNumber,
|
||||
timestamp: getTimestamp(config),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function generateShortId(length = 8): string {
|
||||
const chars =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
let id = "";
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const rand = Math.floor(Math.random() * chars.length);
|
||||
id += chars[rand];
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
function replaceColorTokens(
|
||||
input: string,
|
||||
level: LogLevel,
|
||||
config: Required<LoggerConfig>,
|
||||
): string {
|
||||
return input
|
||||
.replace(/{color:(\w+)}/g, (_, colorKey) => {
|
||||
if (colorKey === "levelColor") {
|
||||
const colorForLevel = config.levelColor?.[level];
|
||||
return ansiColors[colorForLevel ?? ""] ?? "";
|
||||
}
|
||||
return ansiColors[colorKey] ?? "";
|
||||
})
|
||||
.replace(/{reset}/g, ansiColors.reset);
|
||||
}
|
||||
|
||||
function parsePattern(ctx: PatternContext): string {
|
||||
const { level, data, config } = ctx;
|
||||
|
||||
const { fileName, line, column, timestamp } = getCallerInfo(config);
|
||||
const resolvedData: string = format(data);
|
||||
const numericLevel: LogLevelValue = logLevelValues[level];
|
||||
|
||||
const final: string = config.pattern
|
||||
.replace(/{timestamp}/g, timestamp)
|
||||
.replace(/{level-name}/g, level.toUpperCase())
|
||||
.replace(/{level}/g, String(numericLevel))
|
||||
.replace(/{file-name}/g, fileName)
|
||||
.replace(/{line}/g, line)
|
||||
.replace(/{data}/g, resolvedData)
|
||||
.replace(/{id}/g, generateShortId())
|
||||
.replace(/{column}/g, column);
|
||||
|
||||
return config.consoleColor ? replaceColorTokens(final, level, config) : final;
|
||||
}
|
||||
|
||||
export { parsePattern };
|
||||
|
|
|
@ -1,5 +1,31 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import type { LogLevel, LoggerConfig } from "@types";
|
||||
|
||||
const logLevelValues = {
|
||||
trace: 10,
|
||||
debug: 20,
|
||||
info: 30,
|
||||
warn: 40,
|
||||
error: 50,
|
||||
fatal: 60,
|
||||
silent: 70,
|
||||
};
|
||||
|
||||
const ansiColors: Record<string, string> = {
|
||||
reset: "\x1b[0m",
|
||||
dim: "\x1b[2m",
|
||||
bright: "\x1b[1m",
|
||||
black: "\x1b[30m",
|
||||
red: "\x1b[31m",
|
||||
green: "\x1b[32m",
|
||||
yellow: "\x1b[33m",
|
||||
blue: "\x1b[34m",
|
||||
magenta: "\x1b[35m",
|
||||
cyan: "\x1b[36m",
|
||||
white: "\x1b[37m",
|
||||
gray: "\x1b[90m",
|
||||
};
|
||||
|
||||
const defaultConfig: Required<LoggerConfig> = {
|
||||
directory: "logs",
|
||||
|
@ -13,12 +39,22 @@ const defaultConfig: Required<LoggerConfig> = {
|
|||
console: true,
|
||||
consoleColor: true,
|
||||
|
||||
dateFormat: "YYYY-MM-DD HH:mm:ss",
|
||||
timezone: "UTC",
|
||||
dateFormat: "yyyy-MM-dd HH:mm:ss.SSS",
|
||||
timezone: "local",
|
||||
|
||||
silent: false,
|
||||
|
||||
pattern: "{timestamp} [{level-name}] ({file-name}:{line}){message}",
|
||||
pattern:
|
||||
"{color:gray}{timestamp}{reset} {color:levelColor}[{level-name}]{reset} {color:gray}({reset}{file-name}:{line}:{column}{color:gray}){reset} {data}",
|
||||
|
||||
levelColor: {
|
||||
trace: "cyan",
|
||||
debug: "blue",
|
||||
info: "green",
|
||||
warn: "yellow",
|
||||
error: "red",
|
||||
fatal: "red",
|
||||
},
|
||||
};
|
||||
|
||||
function loadLoggerConfig(configPath = "logger.json"): LoggerConfig {
|
||||
|
@ -56,4 +92,10 @@ function loadEnvConfig(): LoggerConfig {
|
|||
return config;
|
||||
}
|
||||
|
||||
export { defaultConfig, loadLoggerConfig, loadEnvConfig };
|
||||
export {
|
||||
defaultConfig,
|
||||
loadLoggerConfig,
|
||||
loadEnvConfig,
|
||||
logLevelValues,
|
||||
ansiColors,
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["src/*"],
|
||||
"@types": ["types/index.ts"],
|
||||
"@types/*": ["types/*"],
|
||||
"@lib/*": ["src/lib/*"]
|
||||
},
|
||||
|
@ -15,7 +16,10 @@
|
|||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
"noEmit": false,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
|
32
types/index.d.ts
vendored
32
types/index.d.ts
vendored
|
@ -1,32 +0,0 @@
|
|||
const LogLevelValue = {
|
||||
trace: 10,
|
||||
debug: 20,
|
||||
info: 30,
|
||||
warn: 40,
|
||||
error: 50,
|
||||
fatal: 60,
|
||||
silent: 70,
|
||||
} as const;
|
||||
|
||||
type LogLevelValue = typeof LogLevelValue[keyof typeof LogLevelValue];
|
||||
type LogLevel = keyof typeof LogLevelValue;
|
||||
|
||||
type LoggerConfig = {
|
||||
directory?: string;
|
||||
level?: LogLevel;
|
||||
disableFile?: boolean;
|
||||
|
||||
rotate?: boolean;
|
||||
maxSizeMB?: number;
|
||||
maxFiles?: number;
|
||||
|
||||
console?: boolean;
|
||||
consoleColor?: boolean;
|
||||
|
||||
dateFormat?: string;
|
||||
timezone?: string;
|
||||
|
||||
silent?: boolean;
|
||||
|
||||
pattern?: string;
|
||||
};
|
33
types/index.ts
Normal file
33
types/index.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { ansiColors, logLevelValues } from "@lib/config";
|
||||
|
||||
type LogLevelValue = typeof logLevelValues[keyof typeof logLevelValues];
|
||||
type LogLevel = keyof typeof logLevelValues;
|
||||
|
||||
type LoggerConfig = {
|
||||
directory?: string;
|
||||
level?: LogLevel;
|
||||
disableFile?: boolean;
|
||||
|
||||
rotate?: boolean;
|
||||
maxSizeMB?: number;
|
||||
maxFiles?: number;
|
||||
|
||||
console?: boolean;
|
||||
consoleColor?: boolean;
|
||||
|
||||
dateFormat?: string;
|
||||
timezone?: string;
|
||||
|
||||
silent?: boolean;
|
||||
|
||||
pattern?: string;
|
||||
levelColor?: Partial<Record<LogLevel, keyof typeof ansiColors>>;
|
||||
};
|
||||
|
||||
interface PatternContext {
|
||||
level: LogLevel;
|
||||
data: unknown;
|
||||
config: Required<LoggerConfig>;
|
||||
}
|
||||
|
||||
export type { LogLevel, LogLevelValue, LoggerConfig, PatternContext };
|
Loading…
Add table
Add a link
Reference in a new issue