Skip to content

Commit f0f4e36

Browse files
committed
feat: refactor logger implementation, enhance test coverage, and update configuration files
1 parent 9391a54 commit f0f4e36

10 files changed

Lines changed: 551 additions & 110 deletions

File tree

jest.config.js

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,17 @@
1-
export default {
2-
preset: "ts-jest/presets/default-esm",
3-
1+
/** @type {import('jest').Config} */
2+
module.exports = {
43
testEnvironment: "node",
5-
6-
extensionsToTreatAsEsm: [".ts"],
7-
4+
roots: ["<rootDir>/test"],
5+
testMatch: ["**/*.test.ts"],
86
transform: {
97
"^.+\\.tsx?$": [
108
"ts-jest",
119
{
12-
useESM: true,
13-
tsconfig: "tsconfig.test.json",
10+
tsconfig: {
11+
strict: true,
12+
esModuleInterop: true,
13+
},
1414
},
1515
],
1616
},
17-
18-
moduleNameMapper: {
19-
"^(\\.{1,2}/.*)\\.js$": "$1",
20-
},
21-
22-
testMatch: ["**/test/**/*.test.ts"],
23-
24-
collectCoverageFrom: ["src/**/*.ts", "!src/**/*.d.ts"],
2517
};

package.json

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,39 @@
11
{
2-
"name": "@syntaxsentinel/pretty-log",
2+
"name": "pretty-log",
33
"version": "1.0.0",
4-
"description": "A simple pretty logging utility for Node.js",
5-
"type": "module",
4+
"description": "Make console logs readable with clean, structured, tag-based output.",
65
"main": "dist/index.js",
6+
"module": "dist/index.mjs",
77
"types": "dist/index.d.ts",
88
"exports": {
99
".": {
10-
"import": "./dist/index.js",
10+
"import": "./dist/index.mjs",
11+
"require": "./dist/index.js",
1112
"types": "./dist/index.d.ts"
1213
}
1314
},
1415
"scripts": {
1516
"build": "tsc",
16-
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
17-
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
18-
"test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
19-
"prepublishOnly": "npm run build && npm test"
17+
"test": "jest",
18+
"test:watch": "jest --watch",
19+
"lint": "tsc --noEmit"
2020
},
2121
"keywords": [
22-
"pretty-log",
23-
"console-log",
24-
"logging",
25-
"nodejs"
22+
"logger",
23+
"pretty",
24+
"console",
25+
"log",
26+
"typescript",
27+
"tag",
28+
"structured"
2629
],
27-
"author": "John Mark Pulmano",
30+
"author": "jayemscript",
2831
"license": "MIT",
2932
"devDependencies": {
30-
"@types/jest": "^29.5.14",
31-
"@types/node": "^20.19.39",
33+
"@types/jest": "^29.5.12",
34+
"@types/node": "^20.12.12",
3235
"jest": "^29.7.0",
33-
"ts-jest": "^29.4.9",
34-
"typescript": "^5.0.0"
35-
},
36-
"files": [
37-
"dist",
38-
"README.md"
39-
]
36+
"ts-jest": "^29.1.4",
37+
"typescript": "^5.4.5"
38+
}
4039
}

src/index.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,25 @@
1-
export * from "./logger";
1+
/**
2+
* pretty-log
3+
* Make console logs readable with clean, structured, tag-based output.
4+
*
5+
* @example
6+
* // Named import — use the default `clog` instance
7+
* import { clog } from 'pretty-log';
8+
* clog.user({ id: 1, name: 'Jay' });
9+
* clog.error('Something went wrong');
10+
*
11+
* @example
12+
* // Create a custom logger with extra tags
13+
* import { createLogger } from 'pretty-log';
14+
* const log = createLogger({
15+
* tags: {
16+
* stripe: { fg: 'black', bg: 'brightGreen', level: 'log' },
17+
* }
18+
* });
19+
* log.stripe({ amount: 9.99 });
20+
*/
21+
22+
export { clog, createLogger } from "./logger";
23+
export type { PrettyLogger, LogFn, LoggerOptions } from "./logger";
24+
export type { TagConfig, BuiltinTag } from "./lib/tags";
25+
export { DEFAULT_TAGS } from "./lib/tags";

src/lib/tags.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { FgColor, BgColor } from "../utils/colors";
2+
3+
export interface TagConfig {
4+
fg: FgColor;
5+
bg: BgColor;
6+
/** Which console method to use under the hood */
7+
level: "log" | "info" | "warn" | "error" | "debug";
8+
}
9+
10+
/**
11+
* Built-in tag definitions.
12+
* You can extend these via `createLogger({ tags: { ... } })`.
13+
*/
14+
export const DEFAULT_TAGS: Record<string, TagConfig> = {
15+
// Generic
16+
info: { fg: "white", bg: "blue", level: "info" },
17+
success: { fg: "black", bg: "brightGreen", level: "log" },
18+
warn: { fg: "black", bg: "brightYellow", level: "warn" },
19+
error: { fg: "brightWhite", bg: "brightRed", level: "error" },
20+
debug: { fg: "black", bg: "gray", level: "debug" },
21+
22+
// Domain-specific
23+
user: { fg: "black", bg: "brightCyan", level: "log" },
24+
auth: { fg: "brightWhite", bg: "magenta", level: "log" },
25+
db: { fg: "black", bg: "brightMagenta", level: "log" },
26+
api: { fg: "black", bg: "brightBlue", level: "log" },
27+
server: { fg: "black", bg: "green", level: "log" },
28+
request: { fg: "black", bg: "cyan", level: "log" },
29+
response: { fg: "black", bg: "brightGreen", level: "log" },
30+
cache: { fg: "black", bg: "yellow", level: "log" },
31+
job: { fg: "black", bg: "brightMagenta", level: "log" },
32+
event: { fg: "black", bg: "brightYellow", level: "log" },
33+
mail: { fg: "black", bg: "brightCyan", level: "log" },
34+
payment: { fg: "brightWhite", bg: "brightGreen", level: "log" },
35+
socket: { fg: "black", bg: "blue", level: "log" },
36+
test: { fg: "black", bg: "brightWhite", level: "log" },
37+
};
38+
39+
export type BuiltinTag = keyof typeof DEFAULT_TAGS;

src/logger.ts

Lines changed: 128 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,134 @@
1-
type LogValue = unknown;
1+
import { DEFAULT_TAGS, type TagConfig, type BuiltinTag } from "./lib/tags";
2+
import { buildPrefix, formatData } from "./utils/formatter";
3+
import type { FgColor, BgColor } from "./utils/colors";
24

3-
type Logger = Record<string, (data: LogValue) => void>;
5+
// ─── Types ────────────────────────────────────────────────────────────────────
46

5-
const clog: Logger = new Proxy(
6-
{},
7-
{
8-
get(_, key: string) {
9-
return (data: LogValue) => {
10-
const label = key.toUpperCase();
7+
export type LogFn = (...args: unknown[]) => void;
118

12-
console.log(`\n[${label}]`);
9+
/**
10+
* The shape of a pretty-log instance.
11+
*
12+
* Built-in tags are fully typed; custom tags are accessible via string indexing.
13+
*
14+
* @example
15+
* clog.user({ id: 1, name: 'Jay' });
16+
* clog.payment({ amount: 99.99 });
17+
* clog.myCustomTag('hello!');
18+
*/
19+
export type PrettyLogger = {
20+
[K in BuiltinTag]: LogFn;
21+
} & {
22+
/** Log under any arbitrary tag name */
23+
[tag: string]: LogFn;
24+
};
1325

14-
if (typeof data === "object" && data !== null) {
15-
console.dir(data, { depth: null });
16-
} else {
17-
console.log(data);
18-
}
19-
};
26+
export interface LoggerOptions {
27+
/**
28+
* Show ISO timestamp before each log.
29+
* @default true
30+
*/
31+
timestamp?: boolean;
32+
33+
/**
34+
* Suppress all output (useful for test environments).
35+
* @default false
36+
*/
37+
silent?: boolean;
38+
39+
/**
40+
* Extend or override built-in tag definitions.
41+
*
42+
* @example
43+
* createLogger({
44+
* tags: {
45+
* stripe: { fg: 'black', bg: 'brightGreen', level: 'log' },
46+
* }
47+
* });
48+
*/
49+
tags?: Record<string, TagConfig>;
50+
}
51+
52+
// ─── Factory ──────────────────────────────────────────────────────────────────
53+
54+
/**
55+
* Creates a pretty-log instance with optional configuration.
56+
*
57+
* All tag methods are auto-generated via a Proxy, so any dot-access
58+
* becomes a scoped logger: `logger.user(data)`, `logger.payment(data)`, etc.
59+
*
60+
* @example
61+
* const clog = createLogger();
62+
* clog.user({ id: 1 }); // [timestamp] USER { id: 1 }
63+
* clog.error('Something broke'); // [timestamp] ERROR Something broke
64+
* clog.myTag('custom stuff'); // [timestamp] MYTAG custom stuff
65+
*/
66+
export function createLogger(options: LoggerOptions = {}): PrettyLogger {
67+
const { timestamp = true, silent = false, tags: customTags = {} } = options;
68+
69+
const tagMap: Record<string, TagConfig> = {
70+
...DEFAULT_TAGS,
71+
...customTags,
72+
};
73+
74+
/**
75+
* Core log dispatch — called by every tag method.
76+
*/
77+
function dispatch(tag: string, args: unknown[]): void {
78+
if (silent) return;
79+
80+
const config: TagConfig = tagMap[tag] ?? {
81+
fg: "white" as FgColor,
82+
bg: "gray" as BgColor,
83+
level: "log",
84+
};
85+
86+
const prefix = timestamp
87+
? buildPrefix(tag, config.fg, config.bg)
88+
: buildPrefix(tag, config.fg, config.bg).replace(/^\S+\s/, "");
89+
90+
const formatted = args.map((a) => formatData(a)).join(" ");
91+
const line = `${prefix} ${formatted}`;
92+
93+
// Route to the right console method
94+
switch (config.level) {
95+
case "info":
96+
console.info(line);
97+
break;
98+
case "warn":
99+
console.warn(line);
100+
break;
101+
case "error":
102+
console.error(line);
103+
break;
104+
case "debug":
105+
console.debug(line);
106+
break;
107+
default:
108+
console.log(line);
109+
break;
110+
}
111+
}
112+
113+
/**
114+
* Proxy intercepts any property access and returns a log function
115+
* bound to that property name as the tag.
116+
*/
117+
return new Proxy({} as PrettyLogger, {
118+
get(_target, prop: string) {
119+
return (...args: unknown[]) => dispatch(prop, args);
20120
},
21-
},
22-
);
121+
});
122+
}
123+
124+
// ─── Default instance ─────────────────────────────────────────────────────────
23125

24-
export default clog;
126+
/**
127+
* Ready-to-use logger instance with default settings.
128+
*
129+
* @example
130+
* import { clog } from 'pretty-log';
131+
* clog.user({ id: 1 });
132+
* clog.error('Oops');
133+
*/
134+
export const clog = createLogger();

src/utils/colors.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* ANSI color codes for terminal output
3+
*/
4+
export const RESET = "\x1b[0m";
5+
export const BOLD = "\x1b[1m";
6+
export const DIM = "\x1b[2m";
7+
8+
export const FG = {
9+
black: "\x1b[30m",
10+
red: "\x1b[31m",
11+
green: "\x1b[32m",
12+
yellow: "\x1b[33m",
13+
blue: "\x1b[34m",
14+
magenta: "\x1b[35m",
15+
cyan: "\x1b[36m",
16+
white: "\x1b[37m",
17+
gray: "\x1b[90m",
18+
brightRed: "\x1b[91m",
19+
brightGreen: "\x1b[92m",
20+
brightYellow: "\x1b[93m",
21+
brightBlue: "\x1b[94m",
22+
brightMagenta: "\x1b[95m",
23+
brightCyan: "\x1b[96m",
24+
brightWhite: "\x1b[97m",
25+
} as const;
26+
27+
export const BG = {
28+
black: "\x1b[40m",
29+
red: "\x1b[41m",
30+
green: "\x1b[42m",
31+
yellow: "\x1b[43m",
32+
blue: "\x1b[44m",
33+
magenta: "\x1b[45m",
34+
cyan: "\x1b[46m",
35+
white: "\x1b[47m",
36+
gray: "\x1b[100m",
37+
brightRed: "\x1b[101m",
38+
brightGreen: "\x1b[102m",
39+
brightYellow: "\x1b[103m",
40+
brightBlue: "\x1b[104m",
41+
brightMagenta: "\x1b[105m",
42+
brightCyan: "\x1b[106m",
43+
brightWhite: "\x1b[107m",
44+
} as const;
45+
46+
export type FgColor = keyof typeof FG;
47+
export type BgColor = keyof typeof BG;
48+
49+
export function colorize(
50+
text: string,
51+
fg?: FgColor,
52+
bg?: BgColor,
53+
bold = false,
54+
): string {
55+
let result = "";
56+
if (bold) result += BOLD;
57+
if (bg) result += BG[bg];
58+
if (fg) result += FG[fg];
59+
result += text;
60+
result += RESET;
61+
return result;
62+
}

0 commit comments

Comments
 (0)