|
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"; |
2 | 4 |
|
3 | | -type Logger = Record<string, (data: LogValue) => void>; |
| 5 | +// ─── Types ──────────────────────────────────────────────────────────────────── |
4 | 6 |
|
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; |
11 | 8 |
|
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 | +}; |
13 | 25 |
|
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); |
20 | 120 | }, |
21 | | - }, |
22 | | -); |
| 121 | + }); |
| 122 | +} |
| 123 | + |
| 124 | +// ─── Default instance ───────────────────────────────────────────────────────── |
23 | 125 |
|
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(); |
0 commit comments