crisplogs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +333 -0
- package/dist/index.d.mts +272 -0
- package/dist/index.d.ts +272 -0
- package/dist/index.js +530 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +482 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +49 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
CleanFileHandler: () => CleanFileHandler,
|
|
34
|
+
ConsoleHandler: () => ConsoleHandler,
|
|
35
|
+
DEFAULT_LOG_COLORS: () => DEFAULT_LOG_COLORS,
|
|
36
|
+
LEVEL_VALUES: () => LEVEL_VALUES,
|
|
37
|
+
LogFormatter: () => LogFormatter,
|
|
38
|
+
Logger: () => Logger,
|
|
39
|
+
VERSION: () => VERSION,
|
|
40
|
+
getLogger: () => getLogger,
|
|
41
|
+
removeLogger: () => removeLogger,
|
|
42
|
+
resetLogging: () => resetLogging,
|
|
43
|
+
setupLogging: () => setupLogging,
|
|
44
|
+
stripAnsi: () => stripAnsi
|
|
45
|
+
});
|
|
46
|
+
module.exports = __toCommonJS(index_exports);
|
|
47
|
+
|
|
48
|
+
// src/utils.ts
|
|
49
|
+
var ANSI_ESCAPE = /[\x1b\x9b][\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><~]|\x1b\].*?(?:\x1b\\|\x07)/g;
|
|
50
|
+
function stripAnsi(text) {
|
|
51
|
+
return text.replace(ANSI_ESCAPE, "");
|
|
52
|
+
}
|
|
53
|
+
function strftime(format, date) {
|
|
54
|
+
const pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
55
|
+
return format.replace(/%[YmdHMSIpfjaAbB%]/g, (token) => {
|
|
56
|
+
switch (token) {
|
|
57
|
+
case "%Y":
|
|
58
|
+
return String(date.getFullYear());
|
|
59
|
+
case "%m":
|
|
60
|
+
return pad(date.getMonth() + 1);
|
|
61
|
+
case "%d":
|
|
62
|
+
return pad(date.getDate());
|
|
63
|
+
case "%H":
|
|
64
|
+
return pad(date.getHours());
|
|
65
|
+
case "%M":
|
|
66
|
+
return pad(date.getMinutes());
|
|
67
|
+
case "%S":
|
|
68
|
+
return pad(date.getSeconds());
|
|
69
|
+
case "%I":
|
|
70
|
+
return pad(date.getHours() % 12 || 12);
|
|
71
|
+
case "%p":
|
|
72
|
+
return date.getHours() < 12 ? "AM" : "PM";
|
|
73
|
+
case "%f":
|
|
74
|
+
return pad(date.getMilliseconds() * 1e3, 6);
|
|
75
|
+
case "%j":
|
|
76
|
+
return pad(getDayOfYear(date), 3);
|
|
77
|
+
case "%a":
|
|
78
|
+
return date.toLocaleDateString("en-US", { weekday: "short" });
|
|
79
|
+
case "%A":
|
|
80
|
+
return date.toLocaleDateString("en-US", { weekday: "long" });
|
|
81
|
+
case "%b":
|
|
82
|
+
return date.toLocaleDateString("en-US", { month: "short" });
|
|
83
|
+
case "%B":
|
|
84
|
+
return date.toLocaleDateString("en-US", { month: "long" });
|
|
85
|
+
case "%%":
|
|
86
|
+
return "%";
|
|
87
|
+
default:
|
|
88
|
+
return token;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function getDayOfYear(date) {
|
|
93
|
+
const start = new Date(date.getFullYear(), 0, 0);
|
|
94
|
+
return Math.floor((date.getTime() - start.getTime()) / 864e5);
|
|
95
|
+
}
|
|
96
|
+
function wordWrap(text, width) {
|
|
97
|
+
if (!text) return [""];
|
|
98
|
+
const words = text.split(/\s+/).filter((w) => w.length > 0);
|
|
99
|
+
if (words.length === 0) return [""];
|
|
100
|
+
const lines = [];
|
|
101
|
+
let currentLine = words[0];
|
|
102
|
+
for (let i = 1; i < words.length; i++) {
|
|
103
|
+
const visCurrentLen = stripAnsi(currentLine).length;
|
|
104
|
+
const visWordLen = stripAnsi(words[i]).length;
|
|
105
|
+
if (visCurrentLen + 1 + visWordLen <= width) {
|
|
106
|
+
currentLine += " " + words[i];
|
|
107
|
+
} else {
|
|
108
|
+
lines.push(currentLine);
|
|
109
|
+
currentLine = words[i];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
lines.push(currentLine);
|
|
113
|
+
return lines;
|
|
114
|
+
}
|
|
115
|
+
function getCallerInfo(belowFn) {
|
|
116
|
+
const obj = {};
|
|
117
|
+
if (typeof Error.captureStackTrace === "function" && belowFn) {
|
|
118
|
+
Error.captureStackTrace(obj, belowFn);
|
|
119
|
+
}
|
|
120
|
+
if (!obj.stack) {
|
|
121
|
+
return { pathname: "<anonymous>", lineno: 0 };
|
|
122
|
+
}
|
|
123
|
+
for (const line of obj.stack.split("\n").slice(1)) {
|
|
124
|
+
const match = line.match(/\((.+):(\d+):\d+\)/) || line.match(/at (.+):(\d+):\d+/);
|
|
125
|
+
if (match) {
|
|
126
|
+
return { pathname: match[1], lineno: parseInt(match[2], 10) };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { pathname: "<anonymous>", lineno: 0 };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/types.ts
|
|
133
|
+
var LEVEL_VALUES = {
|
|
134
|
+
DEBUG: 10,
|
|
135
|
+
INFO: 20,
|
|
136
|
+
WARNING: 30,
|
|
137
|
+
ERROR: 40,
|
|
138
|
+
CRITICAL: 50
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// src/logger.ts
|
|
142
|
+
var Logger = class {
|
|
143
|
+
constructor(name, level = LEVEL_VALUES.DEBUG, captureCallerInfo = true) {
|
|
144
|
+
this.name = name;
|
|
145
|
+
this._level = level;
|
|
146
|
+
this._handlers = [];
|
|
147
|
+
this._captureCallerInfo = captureCallerInfo;
|
|
148
|
+
}
|
|
149
|
+
get level() {
|
|
150
|
+
return this._level;
|
|
151
|
+
}
|
|
152
|
+
set level(val) {
|
|
153
|
+
this._level = val;
|
|
154
|
+
}
|
|
155
|
+
get handlers() {
|
|
156
|
+
return this._handlers;
|
|
157
|
+
}
|
|
158
|
+
addHandler(handler) {
|
|
159
|
+
if (!this._handlers.includes(handler)) {
|
|
160
|
+
this._handlers.push(handler);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
removeHandler(handler) {
|
|
164
|
+
const idx = this._handlers.indexOf(handler);
|
|
165
|
+
if (idx !== -1) {
|
|
166
|
+
this._handlers.splice(idx, 1);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
clearHandlers() {
|
|
172
|
+
for (const handler of this._handlers) {
|
|
173
|
+
try {
|
|
174
|
+
handler.close();
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
this._handlers = [];
|
|
179
|
+
}
|
|
180
|
+
isEnabledFor(level) {
|
|
181
|
+
return LEVEL_VALUES[level] >= this._level;
|
|
182
|
+
}
|
|
183
|
+
_log(levelName, message, extra, callerFn) {
|
|
184
|
+
const levelNo = LEVEL_VALUES[levelName];
|
|
185
|
+
if (levelNo < this._level) return;
|
|
186
|
+
const { pathname, lineno } = this._captureCallerInfo ? getCallerInfo(callerFn) : { pathname: "<anonymous>", lineno: 0 };
|
|
187
|
+
const record = {
|
|
188
|
+
levelName,
|
|
189
|
+
levelNo,
|
|
190
|
+
message,
|
|
191
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
192
|
+
name: this.name,
|
|
193
|
+
pathname,
|
|
194
|
+
lineno,
|
|
195
|
+
extra
|
|
196
|
+
};
|
|
197
|
+
for (const handler of this._handlers) {
|
|
198
|
+
if (levelNo >= handler.level) {
|
|
199
|
+
try {
|
|
200
|
+
handler.emit(record);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
try {
|
|
203
|
+
process.stderr.write(
|
|
204
|
+
`crisplogs: handler emit failed: ${err instanceof Error ? err.message : err}
|
|
205
|
+
`
|
|
206
|
+
);
|
|
207
|
+
} catch {
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
debug(message, extra) {
|
|
214
|
+
this._log("DEBUG", message, extra, this.debug);
|
|
215
|
+
}
|
|
216
|
+
info(message, extra) {
|
|
217
|
+
this._log("INFO", message, extra, this.info);
|
|
218
|
+
}
|
|
219
|
+
warning(message, extra) {
|
|
220
|
+
this._log("WARNING", message, extra, this.warning);
|
|
221
|
+
}
|
|
222
|
+
/** Alias for {@link warning} to match Node.js conventions. */
|
|
223
|
+
warn(message, extra) {
|
|
224
|
+
this._log("WARNING", message, extra, this.warn);
|
|
225
|
+
}
|
|
226
|
+
error(message, extra) {
|
|
227
|
+
this._log("ERROR", message, extra, this.error);
|
|
228
|
+
}
|
|
229
|
+
critical(message, extra) {
|
|
230
|
+
this._log("CRITICAL", message, extra, this.critical);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Log with an explicit level.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```ts
|
|
237
|
+
* logger.log("INFO", "Server started");
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
log(level, message, extra) {
|
|
241
|
+
this._log(level, message, extra, this.log);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// src/colors.ts
|
|
246
|
+
var FG_COLORS = {
|
|
247
|
+
black: 30,
|
|
248
|
+
red: 31,
|
|
249
|
+
green: 32,
|
|
250
|
+
yellow: 33,
|
|
251
|
+
blue: 34,
|
|
252
|
+
purple: 35,
|
|
253
|
+
magenta: 35,
|
|
254
|
+
cyan: 36,
|
|
255
|
+
white: 37
|
|
256
|
+
};
|
|
257
|
+
var BG_COLORS = {
|
|
258
|
+
black: 40,
|
|
259
|
+
red: 41,
|
|
260
|
+
green: 42,
|
|
261
|
+
yellow: 43,
|
|
262
|
+
blue: 44,
|
|
263
|
+
purple: 45,
|
|
264
|
+
magenta: 45,
|
|
265
|
+
cyan: 46,
|
|
266
|
+
white: 47
|
|
267
|
+
};
|
|
268
|
+
var MODIFIERS = {
|
|
269
|
+
bold: 1,
|
|
270
|
+
thin: 2,
|
|
271
|
+
dim: 2,
|
|
272
|
+
italic: 3,
|
|
273
|
+
underline: 4
|
|
274
|
+
};
|
|
275
|
+
var RESET = "\x1B[0m";
|
|
276
|
+
function parseColorString(colorStr) {
|
|
277
|
+
const parts = colorStr.split(",");
|
|
278
|
+
const codes = [];
|
|
279
|
+
for (const part of parts) {
|
|
280
|
+
const trimmed = part.trim().toLowerCase();
|
|
281
|
+
if (trimmed === "reset") {
|
|
282
|
+
return RESET;
|
|
283
|
+
}
|
|
284
|
+
if (trimmed.startsWith("bg_")) {
|
|
285
|
+
const color = trimmed.slice(3);
|
|
286
|
+
if (color in BG_COLORS) codes.push(BG_COLORS[color]);
|
|
287
|
+
} else if (trimmed.includes("_")) {
|
|
288
|
+
const idx = trimmed.indexOf("_");
|
|
289
|
+
const modifier = trimmed.slice(0, idx);
|
|
290
|
+
const color = trimmed.slice(idx + 1);
|
|
291
|
+
if (modifier in MODIFIERS) codes.push(MODIFIERS[modifier]);
|
|
292
|
+
if (color in FG_COLORS) codes.push(FG_COLORS[color]);
|
|
293
|
+
} else if (trimmed in MODIFIERS) {
|
|
294
|
+
codes.push(MODIFIERS[trimmed]);
|
|
295
|
+
} else if (trimmed in FG_COLORS) {
|
|
296
|
+
codes.push(FG_COLORS[trimmed]);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return codes.length > 0 ? `\x1B[${codes.join(";")}m` : "";
|
|
300
|
+
}
|
|
301
|
+
var DEFAULT_LOG_COLORS = {
|
|
302
|
+
DEBUG: "cyan",
|
|
303
|
+
INFO: "green",
|
|
304
|
+
WARNING: "yellow",
|
|
305
|
+
ERROR: "red",
|
|
306
|
+
CRITICAL: "bold_red"
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// src/formatters.ts
|
|
310
|
+
function padVisual(text, width) {
|
|
311
|
+
return text + " ".repeat(Math.max(0, width - stripAnsi(text).length));
|
|
312
|
+
}
|
|
313
|
+
function safeStringify(obj, indent) {
|
|
314
|
+
try {
|
|
315
|
+
return JSON.stringify(obj, null, indent);
|
|
316
|
+
} catch {
|
|
317
|
+
return "[Circular]";
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function serializeExtra(extra, format = "inline") {
|
|
321
|
+
if (!extra || Object.keys(extra).length === 0) return "";
|
|
322
|
+
if (format === "json") {
|
|
323
|
+
return " " + safeStringify(extra);
|
|
324
|
+
}
|
|
325
|
+
if (format === "pretty") {
|
|
326
|
+
return "\n" + safeStringify(extra, 2);
|
|
327
|
+
}
|
|
328
|
+
return " [" + Object.entries(extra).map(([k, v]) => {
|
|
329
|
+
if (v === null || v === void 0) return `${k}=${v}`;
|
|
330
|
+
if (typeof v === "object") return `${k}=${safeStringify(v)}`;
|
|
331
|
+
return `${k}=${v}`;
|
|
332
|
+
}).join(" ") + "]";
|
|
333
|
+
}
|
|
334
|
+
function formatBase(record, opts) {
|
|
335
|
+
const { datefmt, logColors, colored } = opts;
|
|
336
|
+
const timestamp = strftime(datefmt, record.timestamp);
|
|
337
|
+
const levelName = record.levelName.padEnd(8);
|
|
338
|
+
const name = record.name || "root";
|
|
339
|
+
if (colored) {
|
|
340
|
+
const levelColor = parseColorString(logColors[record.levelName] || "white");
|
|
341
|
+
const blue = parseColorString("blue");
|
|
342
|
+
const cyan = parseColorString("cyan");
|
|
343
|
+
const msgColor = parseColorString(logColors[record.levelName] || "white");
|
|
344
|
+
return `${levelColor}${levelName}${RESET} ${timestamp} ${blue}[${name}]${RESET} ${cyan}${record.pathname}:${record.lineno}${RESET} - ${msgColor}${record.message}${RESET}`;
|
|
345
|
+
}
|
|
346
|
+
return `${levelName} ${timestamp} [${name}] ${record.pathname}:${record.lineno} - ${record.message}`;
|
|
347
|
+
}
|
|
348
|
+
var LogFormatter = class {
|
|
349
|
+
constructor(opts) {
|
|
350
|
+
this.opts = opts;
|
|
351
|
+
}
|
|
352
|
+
format(record) {
|
|
353
|
+
const {
|
|
354
|
+
box = false,
|
|
355
|
+
fullBorder = false,
|
|
356
|
+
width = 100,
|
|
357
|
+
wordWrap: doWordWrap = false,
|
|
358
|
+
extraFormat
|
|
359
|
+
} = this.opts;
|
|
360
|
+
let message = formatBase(record, this.opts);
|
|
361
|
+
if (!box || doWordWrap) {
|
|
362
|
+
message += serializeExtra(record.extra, extraFormat);
|
|
363
|
+
}
|
|
364
|
+
if (!box) {
|
|
365
|
+
return message;
|
|
366
|
+
}
|
|
367
|
+
const lines = message.split("\n");
|
|
368
|
+
const w = width === "auto" ? lines.reduce((max, l) => Math.max(max, stripAnsi(l).length), 0) : width;
|
|
369
|
+
const contentLines = doWordWrap ? lines.flatMap((line) => wordWrap(line, w)) : lines;
|
|
370
|
+
if (fullBorder) {
|
|
371
|
+
const top2 = "\u250C" + "\u2500".repeat(w + 2) + "\u2510";
|
|
372
|
+
const bottom2 = "\u2514" + "\u2500".repeat(w + 2) + "\u2518";
|
|
373
|
+
return [
|
|
374
|
+
top2,
|
|
375
|
+
...contentLines.map((l) => `\u2502 ${padVisual(l, w)} \u2502`),
|
|
376
|
+
bottom2
|
|
377
|
+
].join("\n");
|
|
378
|
+
}
|
|
379
|
+
const top = "\u250C" + "\u2500".repeat(w + 2);
|
|
380
|
+
const bottom = "\u2514" + "\u2500".repeat(w + 2);
|
|
381
|
+
const rows = doWordWrap ? contentLines.map((l) => `\u2502 ${l}`) : contentLines.map((l) => `\u2502 ${padVisual(l, w)} `);
|
|
382
|
+
return [top, ...rows, bottom].join("\n");
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
// src/handlers.ts
|
|
387
|
+
var fs = __toESM(require("fs"));
|
|
388
|
+
var ConsoleHandler = class {
|
|
389
|
+
constructor(level, formatter) {
|
|
390
|
+
this.level = level;
|
|
391
|
+
this.formatter = formatter;
|
|
392
|
+
}
|
|
393
|
+
emit(record) {
|
|
394
|
+
const output = this.formatter.format(record);
|
|
395
|
+
process.stdout.write(output + "\n");
|
|
396
|
+
}
|
|
397
|
+
close() {
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
var CleanFileHandler = class {
|
|
401
|
+
constructor(filename, level, formatter) {
|
|
402
|
+
this.level = level;
|
|
403
|
+
this.formatter = formatter;
|
|
404
|
+
this.stream = fs.createWriteStream(filename, { flags: "a" });
|
|
405
|
+
this.stream.on("error", (err) => {
|
|
406
|
+
try {
|
|
407
|
+
process.stderr.write(
|
|
408
|
+
`crisplogs: file write failed (${filename}): ${err.message}
|
|
409
|
+
`
|
|
410
|
+
);
|
|
411
|
+
} catch {
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
emit(record) {
|
|
416
|
+
const output = this.formatter.format(record);
|
|
417
|
+
const clean = stripAnsi(output);
|
|
418
|
+
this.stream.write(clean + "\n");
|
|
419
|
+
}
|
|
420
|
+
close() {
|
|
421
|
+
this.stream.end();
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// src/index.ts
|
|
426
|
+
var VERSION = true ? "0.1.0" : "0.0.0-dev";
|
|
427
|
+
var DEFAULT_DATEFMT = "%Y-%m-%d %H:%M:%S";
|
|
428
|
+
var loggers = /* @__PURE__ */ new Map();
|
|
429
|
+
function setupLogging(options) {
|
|
430
|
+
const {
|
|
431
|
+
colored = true,
|
|
432
|
+
style = null,
|
|
433
|
+
level = "DEBUG",
|
|
434
|
+
width = 100,
|
|
435
|
+
datefmt = DEFAULT_DATEFMT,
|
|
436
|
+
logColors: userColors,
|
|
437
|
+
file = null,
|
|
438
|
+
fileLevel = null,
|
|
439
|
+
name = "",
|
|
440
|
+
extraFormat,
|
|
441
|
+
captureCallerInfo = true
|
|
442
|
+
} = options ?? {};
|
|
443
|
+
if (!(level in LEVEL_VALUES)) {
|
|
444
|
+
throw new TypeError(`Invalid log level: "${level}". Expected one of: ${Object.keys(LEVEL_VALUES).join(", ")}`);
|
|
445
|
+
}
|
|
446
|
+
if (fileLevel !== null && !(fileLevel in LEVEL_VALUES)) {
|
|
447
|
+
throw new TypeError(`Invalid fileLevel: "${fileLevel}". Expected one of: ${Object.keys(LEVEL_VALUES).join(", ")}`);
|
|
448
|
+
}
|
|
449
|
+
if (typeof width !== "number" || width <= 0 || !Number.isFinite(width)) {
|
|
450
|
+
throw new TypeError(`Invalid width: ${width}. Must be a positive finite number.`);
|
|
451
|
+
}
|
|
452
|
+
if (file !== null && (typeof file !== "string" || file.length === 0)) {
|
|
453
|
+
throw new TypeError(`Invalid file path: must be a non-empty string.`);
|
|
454
|
+
}
|
|
455
|
+
const colors = { ...DEFAULT_LOG_COLORS, ...userColors ?? {} };
|
|
456
|
+
const fmtOpts = {
|
|
457
|
+
datefmt,
|
|
458
|
+
logColors: colors,
|
|
459
|
+
colored,
|
|
460
|
+
extraFormat,
|
|
461
|
+
box: style !== null,
|
|
462
|
+
fullBorder: style === "short-dynamic",
|
|
463
|
+
width: style === "short-dynamic" ? "auto" : width,
|
|
464
|
+
wordWrap: style === "long-boxed"
|
|
465
|
+
};
|
|
466
|
+
const formatter = new LogFormatter(fmtOpts);
|
|
467
|
+
const consoleHandler = new ConsoleHandler(LEVEL_VALUES[level], formatter);
|
|
468
|
+
if (loggers.has(name)) {
|
|
469
|
+
loggers.get(name).clearHandlers();
|
|
470
|
+
}
|
|
471
|
+
const logger = new Logger(name, LEVEL_VALUES.DEBUG, captureCallerInfo);
|
|
472
|
+
logger.addHandler(consoleHandler);
|
|
473
|
+
if (file) {
|
|
474
|
+
const resolvedFileLevel = fileLevel ?? level;
|
|
475
|
+
const fileHandler = new CleanFileHandler(
|
|
476
|
+
file,
|
|
477
|
+
LEVEL_VALUES[resolvedFileLevel],
|
|
478
|
+
formatter
|
|
479
|
+
);
|
|
480
|
+
logger.addHandler(fileHandler);
|
|
481
|
+
}
|
|
482
|
+
loggers.set(name, logger);
|
|
483
|
+
return logger;
|
|
484
|
+
}
|
|
485
|
+
function resetLogging() {
|
|
486
|
+
for (const logger of loggers.values()) {
|
|
487
|
+
logger.clearHandlers();
|
|
488
|
+
}
|
|
489
|
+
loggers.clear();
|
|
490
|
+
}
|
|
491
|
+
function removeLogger(name) {
|
|
492
|
+
const logger = loggers.get(name);
|
|
493
|
+
if (logger) {
|
|
494
|
+
logger.clearHandlers();
|
|
495
|
+
loggers.delete(name);
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
function getLogger(name = "") {
|
|
501
|
+
if (loggers.has(name)) return loggers.get(name);
|
|
502
|
+
const root = loggers.get("");
|
|
503
|
+
if (root) {
|
|
504
|
+
const logger2 = new Logger(name, root.level);
|
|
505
|
+
for (const handler of root.handlers) {
|
|
506
|
+
logger2.addHandler(handler);
|
|
507
|
+
}
|
|
508
|
+
loggers.set(name, logger2);
|
|
509
|
+
return logger2;
|
|
510
|
+
}
|
|
511
|
+
const logger = new Logger(name);
|
|
512
|
+
loggers.set(name, logger);
|
|
513
|
+
return logger;
|
|
514
|
+
}
|
|
515
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
516
|
+
0 && (module.exports = {
|
|
517
|
+
CleanFileHandler,
|
|
518
|
+
ConsoleHandler,
|
|
519
|
+
DEFAULT_LOG_COLORS,
|
|
520
|
+
LEVEL_VALUES,
|
|
521
|
+
LogFormatter,
|
|
522
|
+
Logger,
|
|
523
|
+
VERSION,
|
|
524
|
+
getLogger,
|
|
525
|
+
removeLogger,
|
|
526
|
+
resetLogging,
|
|
527
|
+
setupLogging,
|
|
528
|
+
stripAnsi
|
|
529
|
+
});
|
|
530
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/types.ts","../src/logger.ts","../src/colors.ts","../src/formatters.ts","../src/handlers.ts"],"sourcesContent":["/**\r\n * crisplogs - Beautiful, colored, and boxed logging for Node.js.\r\n *\r\n * @example\r\n * ```ts\r\n * import { setupLogging } from \"crisplogs\";\r\n *\r\n * const logger = setupLogging();\r\n * logger.info(\"Hello from crisplogs!\");\r\n * ```\r\n *\r\n * @example\r\n * ```ts\r\n * const logger = setupLogging({ style: \"long-boxed\", file: \"app.log\" });\r\n * logger.warning(\"Disk usage high\", { usage: \"85%\" });\r\n * ```\r\n */\r\n\r\nimport { Logger } from \"./logger\";\r\nimport { LogFormatter } from \"./formatters\";\r\nimport type { FormatterOptions } from \"./formatters\";\r\nimport { ConsoleHandler, CleanFileHandler } from \"./handlers\";\r\nimport { DEFAULT_LOG_COLORS } from \"./colors\";\r\nimport type { SetupLoggingOptions, Formatter } from \"./types\";\r\nimport { LEVEL_VALUES } from \"./types\";\r\n\r\ndeclare const __VERSION__: string;\r\nexport const VERSION: string = typeof __VERSION__ !== \"undefined\" ? __VERSION__ : \"0.0.0-dev\";\r\n\r\nconst DEFAULT_DATEFMT = \"%Y-%m-%d %H:%M:%S\";\r\n\r\n/** Global logger registry, keyed by name. */\r\nconst loggers = new Map<string, Logger>();\r\n\r\n/**\r\n * Configure logging with colors and optional box formatting in one call.\r\n *\r\n * This is the main entry point for crisplogs. Call it once at application\r\n * startup to configure the root (or named) logger.\r\n *\r\n * @returns The configured {@link Logger} instance.\r\n */\r\nexport function setupLogging(options?: SetupLoggingOptions): Logger {\r\n const {\r\n colored = true,\r\n style = null,\r\n level = \"DEBUG\",\r\n width = 100,\r\n datefmt = DEFAULT_DATEFMT,\r\n logColors: userColors,\r\n file = null,\r\n fileLevel = null,\r\n name = \"\",\r\n extraFormat,\r\n captureCallerInfo = true,\r\n } = options ?? {};\r\n\r\n // Runtime validation for JS consumers (TypeScript catches these at compile time).\r\n if (!(level in LEVEL_VALUES)) {\r\n throw new TypeError(`Invalid log level: \"${level}\". Expected one of: ${Object.keys(LEVEL_VALUES).join(\", \")}`);\r\n }\r\n if (fileLevel !== null && !(fileLevel in LEVEL_VALUES)) {\r\n throw new TypeError(`Invalid fileLevel: \"${fileLevel}\". Expected one of: ${Object.keys(LEVEL_VALUES).join(\", \")}`);\r\n }\r\n if (typeof width !== \"number\" || width <= 0 || !Number.isFinite(width)) {\r\n throw new TypeError(`Invalid width: ${width}. Must be a positive finite number.`);\r\n }\r\n if (file !== null && (typeof file !== \"string\" || file.length === 0)) {\r\n throw new TypeError(`Invalid file path: must be a non-empty string.`);\r\n }\r\n\r\n const colors = { ...DEFAULT_LOG_COLORS, ...(userColors ?? {}) };\r\n\r\n const fmtOpts: FormatterOptions = {\r\n datefmt,\r\n logColors: colors,\r\n colored,\r\n extraFormat,\r\n box: style !== null,\r\n fullBorder: style === \"short-dynamic\",\r\n width: style === \"short-dynamic\" ? \"auto\" : width,\r\n wordWrap: style === \"long-boxed\",\r\n };\r\n\r\n const formatter: Formatter = new LogFormatter(fmtOpts);\r\n\r\n // Console handler\r\n const consoleHandler = new ConsoleHandler(LEVEL_VALUES[level], formatter);\r\n\r\n // Clear previous logger with the same name to avoid duplicate handlers.\r\n if (loggers.has(name)) {\r\n loggers.get(name)!.clearHandlers();\r\n }\r\n\r\n const logger = new Logger(name, LEVEL_VALUES.DEBUG, captureCallerInfo);\r\n logger.addHandler(consoleHandler);\r\n\r\n // File handler (optional)\r\n if (file) {\r\n const resolvedFileLevel = fileLevel ?? level;\r\n const fileHandler = new CleanFileHandler(\r\n file,\r\n LEVEL_VALUES[resolvedFileLevel],\r\n formatter,\r\n );\r\n logger.addHandler(fileHandler);\r\n }\r\n\r\n loggers.set(name, logger);\r\n return logger;\r\n}\r\n\r\n/**\r\n * Tear down all loggers, closing their handlers and clearing the registry.\r\n * Useful in tests or when reconfiguring logging at runtime.\r\n */\r\nexport function resetLogging(): void {\r\n for (const logger of loggers.values()) {\r\n logger.clearHandlers();\r\n }\r\n loggers.clear();\r\n}\r\n\r\n/**\r\n * Remove a single logger from the registry by name.\r\n * Returns `true` if the logger existed and was removed.\r\n */\r\nexport function removeLogger(name: string): boolean {\r\n const logger = loggers.get(name);\r\n if (logger) {\r\n logger.clearHandlers();\r\n loggers.delete(name);\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\n/**\r\n * Retrieve a previously configured logger by name, or create one\r\n * that inherits the root logger's handlers.\r\n *\r\n * @example\r\n * ```ts\r\n * setupLogging(); // configure root\r\n * const logger = getLogger(\"myapp\"); // inherits root handlers\r\n * logger.info(\"works\");\r\n * ```\r\n */\r\nexport function getLogger(name: string = \"\"): Logger {\r\n if (loggers.has(name)) return loggers.get(name)!;\r\n\r\n // Inherit handlers from root logger if available.\r\n const root = loggers.get(\"\");\r\n if (root) {\r\n const logger = new Logger(name, root.level);\r\n for (const handler of root.handlers) {\r\n logger.addHandler(handler);\r\n }\r\n loggers.set(name, logger);\r\n return logger;\r\n }\r\n\r\n // No root configured - return a bare logger (no output until configured).\r\n const logger = new Logger(name);\r\n loggers.set(name, logger);\r\n return logger;\r\n}\r\n\r\n// Re-exports\r\nexport { Logger } from \"./logger\";\r\nexport { LogFormatter } from \"./formatters\";\r\nexport type { FormatterOptions } from \"./formatters\";\r\nexport { ConsoleHandler, CleanFileHandler } from \"./handlers\";\r\nexport { stripAnsi } from \"./utils\";\r\nexport { DEFAULT_LOG_COLORS } from \"./colors\";\r\nexport type {\r\n Style,\r\n Level,\r\n ExtraFormat,\r\n SetupLoggingOptions,\r\n LogRecord,\r\n Formatter,\r\n Handler,\r\n} from \"./types\";\r\nexport { LEVEL_VALUES } from \"./types\";\r\n","/**\n * Internal utilities for crisplogs.\n */\n\n/** Regex matching ANSI escape sequences (SGR, CSI, OSC, etc.). */\nconst ANSI_ESCAPE =\n /[\\x1b\\x9b][\\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><~]|\\x1b\\].*?(?:\\x1b\\\\|\\x07)/g;\n\n/**\n * Remove all ANSI escape sequences from a string.\n *\n * @example\n * stripAnsi(\"\\x1b[32mHello\\x1b[0m\") // \"Hello\"\n */\nexport function stripAnsi(text: string): string {\n return text.replace(ANSI_ESCAPE, \"\");\n}\n\n/**\n * Format a Date using Python-compatible strftime tokens.\n *\n * Supported tokens: `%Y`, `%m`, `%d`, `%H`, `%M`, `%S`, `%I`, `%p`,\n * `%f`, `%j`, `%a`, `%A`, `%b`, `%B`, `%%`.\n */\nexport function strftime(format: string, date: Date): string {\n const pad = (n: number, w = 2) => String(n).padStart(w, \"0\");\n\n return format.replace(/%[YmdHMSIpfjaAbB%]/g, (token) => {\n switch (token) {\n case \"%Y\": return String(date.getFullYear());\n case \"%m\": return pad(date.getMonth() + 1);\n case \"%d\": return pad(date.getDate());\n case \"%H\": return pad(date.getHours());\n case \"%M\": return pad(date.getMinutes());\n case \"%S\": return pad(date.getSeconds());\n case \"%I\": return pad(date.getHours() % 12 || 12);\n case \"%p\": return date.getHours() < 12 ? \"AM\" : \"PM\";\n case \"%f\": return pad(date.getMilliseconds() * 1000, 6);\n case \"%j\": return pad(getDayOfYear(date), 3);\n case \"%a\": return date.toLocaleDateString(\"en-US\", { weekday: \"short\" });\n case \"%A\": return date.toLocaleDateString(\"en-US\", { weekday: \"long\" });\n case \"%b\": return date.toLocaleDateString(\"en-US\", { month: \"short\" });\n case \"%B\": return date.toLocaleDateString(\"en-US\", { month: \"long\" });\n case \"%%\": return \"%\";\n default: return token;\n }\n });\n}\n\nfunction getDayOfYear(date: Date): number {\n const start = new Date(date.getFullYear(), 0, 0);\n return Math.floor((date.getTime() - start.getTime()) / 86_400_000);\n}\n\n/**\n * Word-wrap text at word boundaries, matching Python's\n * `textwrap.wrap(text, width, break_long_words=False, break_on_hyphens=False)`.\n *\n * ANSI escape sequences are excluded from width calculations so colored text\n * wraps at the correct visible column.\n */\nexport function wordWrap(text: string, width: number): string[] {\n if (!text) return [\"\"];\n\n const words = text.split(/\\s+/).filter((w) => w.length > 0);\n if (words.length === 0) return [\"\"];\n\n const lines: string[] = [];\n let currentLine = words[0];\n\n for (let i = 1; i < words.length; i++) {\n const visCurrentLen = stripAnsi(currentLine).length;\n const visWordLen = stripAnsi(words[i]).length;\n if (visCurrentLen + 1 + visWordLen <= width) {\n currentLine += \" \" + words[i];\n } else {\n lines.push(currentLine);\n currentLine = words[i];\n }\n }\n\n lines.push(currentLine);\n return lines;\n}\n\n/**\n * Capture the file path and line number of the caller.\n *\n * Uses V8's `Error.captureStackTrace` when available to skip internal\n * frames and return the user's call site directly.\n *\n * @param belowFn - The public method to skip past in the stack (e.g. `logger.info`).\n */\nexport function getCallerInfo(belowFn?: (...args: any[]) => any): {\n pathname: string;\n lineno: number;\n} {\n const obj: { stack?: string } = {};\n\n if (typeof Error.captureStackTrace === \"function\" && belowFn) {\n Error.captureStackTrace(obj, belowFn);\n }\n\n if (!obj.stack) {\n return { pathname: \"<anonymous>\", lineno: 0 };\n }\n\n for (const line of obj.stack.split(\"\\n\").slice(1)) {\n const match =\n line.match(/\\((.+):(\\d+):\\d+\\)/) ||\n line.match(/at (.+):(\\d+):\\d+/);\n if (match) {\n return { pathname: match[1], lineno: parseInt(match[2], 10) };\n }\n }\n\n return { pathname: \"<anonymous>\", lineno: 0 };\n}\n","/** Box decoration style for console output. */\r\nexport type Style = \"short-fixed\" | \"short-dynamic\" | \"long-boxed\";\r\n\r\n/**\r\n * How extra fields are serialized in the log output.\r\n * - `\"inline\"` (default): `[key=value key2=value2]`\r\n * - `\"json\"`: compact single-line JSON — `{\"key\":\"value\",\"key2\":\"value2\"}`\r\n * - `\"pretty\"`: formatted multi-line JSON (best paired with `\"long-boxed\"` style)\r\n */\r\nexport type ExtraFormat = \"inline\" | \"json\" | \"pretty\";\r\n\r\n/** Supported log levels, matching Python's logging module hierarchy. */\r\nexport type Level = \"DEBUG\" | \"INFO\" | \"WARNING\" | \"ERROR\" | \"CRITICAL\";\r\n\r\n/** Numeric values for each log level (matches Python logging). */\r\nexport const LEVEL_VALUES: Record<Level, number> = {\r\n DEBUG: 10,\r\n INFO: 20,\r\n WARNING: 30,\r\n ERROR: 40,\r\n CRITICAL: 50,\r\n};\r\n\r\n/** A single log record containing all metadata for one log event. */\r\nexport interface LogRecord {\r\n levelName: Level;\r\n levelNo: number;\r\n message: string;\r\n timestamp: Date;\r\n name: string;\r\n pathname: string;\r\n lineno: number;\r\n extra?: Record<string, unknown>;\r\n}\r\n\r\n/** Options accepted by {@link setupLogging}. */\r\nexport interface SetupLoggingOptions {\r\n /** Enable colored output on the console. Default: `true`. */\r\n colored?: boolean;\r\n /** Box style for console output. Default: `null` (no box). */\r\n style?: Style | null;\r\n /** Minimum log level for the console handler. Default: `\"DEBUG\"`. */\r\n level?: Level;\r\n /** Box width in characters (for `\"short-fixed\"` and `\"long-boxed\"`). Default: `100`. */\r\n width?: number;\r\n /** Date/time format string using `strftime` tokens. Default: `\"%Y-%m-%d %H:%M:%S\"`. */\r\n datefmt?: string;\r\n /** Override the default color for each log level. */\r\n logColors?: Partial<Record<Level, string>>;\r\n /** Path to a log file. ANSI codes are stripped automatically. Default: `null`. */\r\n file?: string | null;\r\n /** Minimum log level for the file handler. Defaults to the console `level`. */\r\n fileLevel?: Level | null;\r\n /** Logger name. `\"\"` for root logger. Default: `\"\"`. */\r\n name?: string;\r\n /**\r\n * Capture file path and line number of the caller on each log call.\r\n * Disable for higher throughput in production. Default: `true`.\r\n */\r\n captureCallerInfo?: boolean;\r\n /**\r\n * How `extra` fields are rendered in the log output.\r\n * Only applies to no-style (plain/colored) and `\"long-boxed\"` outputs.\r\n * Default: `\"inline\"`.\r\n */\r\n extraFormat?: ExtraFormat;\r\n}\r\n\r\n/** Interface that all formatters implement. */\r\nexport interface Formatter {\r\n format(record: LogRecord): string;\r\n}\r\n\r\n/** Interface that all handlers implement. */\r\nexport interface Handler {\r\n readonly level: number;\r\n formatter: Formatter;\r\n emit(record: LogRecord): void;\r\n /** Release any resources held by this handler (e.g. file streams). */\r\n close(): void;\r\n}\r\n","/**\n * Logger class for crisplogs.\n *\n * Mirrors Python's `logging.Logger` API with level-specific methods\n * and automatic caller-info capture.\n */\n\nimport { getCallerInfo } from \"./utils\";\nimport type { Handler, LogRecord } from \"./types\";\nimport type { Level } from \"./types\";\nimport { LEVEL_VALUES } from \"./types\";\n\nexport class Logger {\n readonly name: string;\n private _level: number;\n private _handlers: Handler[];\n private _captureCallerInfo: boolean;\n\n constructor(\n name: string,\n level: number = LEVEL_VALUES.DEBUG,\n captureCallerInfo: boolean = true,\n ) {\n this.name = name;\n this._level = level;\n this._handlers = [];\n this._captureCallerInfo = captureCallerInfo;\n }\n\n get level(): number {\n return this._level;\n }\n\n set level(val: number) {\n this._level = val;\n }\n\n get handlers(): readonly Handler[] {\n return this._handlers;\n }\n\n addHandler(handler: Handler): void {\n if (!this._handlers.includes(handler)) {\n this._handlers.push(handler);\n }\n }\n\n removeHandler(handler: Handler): boolean {\n const idx = this._handlers.indexOf(handler);\n if (idx !== -1) {\n this._handlers.splice(idx, 1);\n return true;\n }\n return false;\n }\n\n clearHandlers(): void {\n for (const handler of this._handlers) {\n try {\n handler.close();\n } catch {\n // Swallow close errors — cleanup must not throw.\n }\n }\n this._handlers = [];\n }\n\n isEnabledFor(level: Level): boolean {\n return LEVEL_VALUES[level] >= this._level;\n }\n\n private _log(\n levelName: Level,\n message: string,\n extra: Record<string, unknown> | undefined,\n callerFn?: ((...args: any[]) => any),\n ): void {\n const levelNo = LEVEL_VALUES[levelName];\n if (levelNo < this._level) return;\n\n const { pathname, lineno } = this._captureCallerInfo\n ? getCallerInfo(callerFn)\n : { pathname: \"<anonymous>\", lineno: 0 };\n\n const record: LogRecord = {\n levelName,\n levelNo,\n message,\n timestamp: new Date(),\n name: this.name,\n pathname,\n lineno,\n extra,\n };\n\n for (const handler of this._handlers) {\n if (levelNo >= handler.level) {\n try {\n handler.emit(record);\n } catch (err) {\n try {\n process.stderr.write(\n `crisplogs: handler emit failed: ${err instanceof Error ? err.message : err}\\n`,\n );\n } catch {\n // Last resort: stderr itself failed, silently swallow.\n }\n }\n }\n }\n }\n\n debug(message: string, extra?: Record<string, unknown>): void {\n this._log(\"DEBUG\", message, extra, this.debug);\n }\n\n info(message: string, extra?: Record<string, unknown>): void {\n this._log(\"INFO\", message, extra, this.info);\n }\n\n warning(message: string, extra?: Record<string, unknown>): void {\n this._log(\"WARNING\", message, extra, this.warning);\n }\n\n /** Alias for {@link warning} to match Node.js conventions. */\n warn(message: string, extra?: Record<string, unknown>): void {\n this._log(\"WARNING\", message, extra, this.warn);\n }\n\n error(message: string, extra?: Record<string, unknown>): void {\n this._log(\"ERROR\", message, extra, this.error);\n }\n\n critical(message: string, extra?: Record<string, unknown>): void {\n this._log(\"CRITICAL\", message, extra, this.critical);\n }\n\n /**\n * Log with an explicit level.\n *\n * @example\n * ```ts\n * logger.log(\"INFO\", \"Server started\");\n * ```\n */\n log(level: Level, message: string, extra?: Record<string, unknown>): void {\n this._log(level, message, extra, this.log);\n }\n}\n","/**\n * ANSI color code mapping and parsing.\n *\n * Supports the same color strings as Python's `colorlog`:\n * basic colors, bold/dim modifiers, background colors, and\n * comma-separated combinations like `\"bold_red,bg_white\"`.\n */\n\nconst FG_COLORS: Record<string, number> = {\n black: 30,\n red: 31,\n green: 32,\n yellow: 33,\n blue: 34,\n purple: 35,\n magenta: 35,\n cyan: 36,\n white: 37,\n};\n\nconst BG_COLORS: Record<string, number> = {\n black: 40,\n red: 41,\n green: 42,\n yellow: 43,\n blue: 44,\n purple: 45,\n magenta: 45,\n cyan: 46,\n white: 47,\n};\n\nconst MODIFIERS: Record<string, number> = {\n bold: 1,\n thin: 2,\n dim: 2,\n italic: 3,\n underline: 4,\n};\n\n/** ANSI reset sequence. */\nexport const RESET = \"\\x1b[0m\";\n\n/**\n * Parse a colorlog-compatible color string into an ANSI escape sequence.\n *\n * @example\n * parseColorString(\"red\") // \"\\x1b[31m\"\n * parseColorString(\"bold_red\") // \"\\x1b[1;31m\"\n * parseColorString(\"bold_red,bg_white\") // \"\\x1b[1;31;47m\"\n */\nexport function parseColorString(colorStr: string): string {\n const parts = colorStr.split(\",\");\n const codes: number[] = [];\n\n for (const part of parts) {\n const trimmed = part.trim().toLowerCase();\n\n if (trimmed === \"reset\") {\n return RESET;\n }\n\n if (trimmed.startsWith(\"bg_\")) {\n const color = trimmed.slice(3);\n if (color in BG_COLORS) codes.push(BG_COLORS[color]);\n } else if (trimmed.includes(\"_\")) {\n const idx = trimmed.indexOf(\"_\");\n const modifier = trimmed.slice(0, idx);\n const color = trimmed.slice(idx + 1);\n if (modifier in MODIFIERS) codes.push(MODIFIERS[modifier]);\n if (color in FG_COLORS) codes.push(FG_COLORS[color]);\n } else if (trimmed in MODIFIERS) {\n codes.push(MODIFIERS[trimmed]);\n } else if (trimmed in FG_COLORS) {\n codes.push(FG_COLORS[trimmed]);\n }\n }\n\n return codes.length > 0 ? `\\x1b[${codes.join(\";\")}m` : \"\";\n}\n\nimport type { Level } from \"./types\";\n\n/** Default color scheme applied to each log level. */\nexport const DEFAULT_LOG_COLORS: Record<Level, string> = {\n DEBUG: \"cyan\",\n INFO: \"green\",\n WARNING: \"yellow\",\n ERROR: \"red\",\n CRITICAL: \"bold_red\",\n};\n","/**\r\n * Formatter for crisplogs.\r\n *\r\n * A single LogFormatter class covers all output styles via options.\r\n */\r\n\r\nimport { parseColorString, RESET } from \"./colors\";\r\nimport { strftime, wordWrap, stripAnsi } from \"./utils\";\r\nimport type { ExtraFormat, Formatter, LogRecord } from \"./types\";\r\n\r\n/** Options accepted by {@link LogFormatter}. */\r\nexport interface FormatterOptions {\r\n datefmt: string;\r\n logColors: Record<string, string>; // string keys to allow custom level extensions\r\n colored: boolean;\r\n /** How extra fields are rendered. Only applies when `box` is false or `wordWrap` is true. */\r\n extraFormat?: ExtraFormat;\r\n /** Draw a box around each log entry. Default: `false`. */\r\n box?: boolean;\r\n /**\r\n * Full border (`┌─┐ │ └─┘`) instead of left-border only (`┌─ │ └─`).\r\n * Only applies when `box` is `true`. Default: `false`.\r\n */\r\n fullBorder?: boolean;\r\n /**\r\n * Box width in characters, or `\"auto\"` to size to the longest line.\r\n * Only applies when `box` is `true`. Default: `100`.\r\n */\r\n width?: number | \"auto\";\r\n /**\r\n * Word-wrap long lines within the box.\r\n * Only applies when `box` is `true`. Default: `false`.\r\n */\r\n wordWrap?: boolean;\r\n}\r\n\r\n/**\r\n * Pad text to a visual width, ignoring ANSI escape codes.\r\n */\r\nfunction padVisual(text: string, width: number): string {\r\n return text + \" \".repeat(Math.max(0, width - stripAnsi(text).length));\r\n}\r\n\r\n/**\r\n * Safely stringify an object, handling circular references.\r\n */\r\nfunction safeStringify(obj: unknown, indent?: number): string {\r\n try {\r\n return JSON.stringify(obj, null, indent);\r\n } catch {\r\n return \"[Circular]\";\r\n }\r\n}\r\n\r\n/**\r\n * Serialize extra fields according to the chosen format.\r\n * Returns an empty string when there are no extra fields.\r\n */\r\nfunction serializeExtra(\r\n extra: Record<string, unknown> | undefined,\r\n format: ExtraFormat = \"inline\",\r\n): string {\r\n if (!extra || Object.keys(extra).length === 0) return \"\";\r\n\r\n if (format === \"json\") {\r\n return \" \" + safeStringify(extra);\r\n }\r\n\r\n if (format === \"pretty\") {\r\n return \"\\n\" + safeStringify(extra, 2);\r\n }\r\n\r\n // inline: [key=value key2=value2]\r\n return (\r\n \" [\" +\r\n Object.entries(extra)\r\n .map(([k, v]) => {\r\n if (v === null || v === undefined) return `${k}=${v}`;\r\n if (typeof v === \"object\") return `${k}=${safeStringify(v)}`;\r\n return `${k}=${v}`;\r\n })\r\n .join(\" \") +\r\n \"]\"\r\n );\r\n}\r\n\r\n/**\r\n * Build the base formatted line for a log record (no box, no extras).\r\n *\r\n * Colored: `{levelColor}LEVEL {reset} timestamp {blue}[name]{reset} {cyan}path:line{reset} - {msgColor}message{reset}`\r\n * Plain: `LEVEL timestamp [name] path:line - message`\r\n */\r\nfunction formatBase(record: LogRecord, opts: FormatterOptions): string {\r\n const { datefmt, logColors, colored } = opts;\r\n const timestamp = strftime(datefmt, record.timestamp);\r\n const levelName = record.levelName.padEnd(8);\r\n const name = record.name || \"root\";\r\n\r\n if (colored) {\r\n const levelColor = parseColorString(logColors[record.levelName] || \"white\");\r\n const blue = parseColorString(\"blue\");\r\n const cyan = parseColorString(\"cyan\");\r\n const msgColor = parseColorString(logColors[record.levelName] || \"white\");\r\n\r\n return (\r\n `${levelColor}${levelName}${RESET} ` +\r\n `${timestamp} ` +\r\n `${blue}[${name}]${RESET} ` +\r\n `${cyan}${record.pathname}:${record.lineno}${RESET} - ` +\r\n `${msgColor}${record.message}${RESET}`\r\n );\r\n }\r\n\r\n return (\r\n `${levelName} ${timestamp} ` +\r\n `[${name}] ` +\r\n `${record.pathname}:${record.lineno} - ` +\r\n `${record.message}`\r\n );\r\n}\r\n\r\n/**\r\n * Single configurable log formatter.\r\n *\r\n * Covers all output styles through constructor options:\r\n *\r\n * | Equivalent old class | Options |\r\n * |-----------------------------|--------------------------------------------------|\r\n * | `ColoredLogFormatter` | `{ box: false }` |\r\n * | `ShortFixedBoxFormatter` | `{ box: true, width: N }` |\r\n * | `ShortDynamicBoxFormatter` | `{ box: true, fullBorder: true, width: \"auto\" }` |\r\n * | `LongBoxedFormatter` | `{ box: true, wordWrap: true, width: N }` |\r\n *\r\n * @example\r\n * ```ts\r\n * // Colored, no box (default style)\r\n * new LogFormatter({ datefmt, logColors, colored: true });\r\n *\r\n * // Full border, auto width\r\n * new LogFormatter({ datefmt, logColors, colored: true, box: true, fullBorder: true, width: \"auto\" });\r\n *\r\n * // Word-wrapped box with JSON extras\r\n * new LogFormatter({ datefmt, logColors, colored: true, box: true, wordWrap: true, width: 100, extraFormat: \"json\" });\r\n * ```\r\n */\r\nexport class LogFormatter implements Formatter {\r\n private opts: FormatterOptions;\r\n\r\n constructor(opts: FormatterOptions) {\r\n this.opts = opts;\r\n }\r\n\r\n format(record: LogRecord): string {\r\n const {\r\n box = false,\r\n fullBorder = false,\r\n width = 100,\r\n wordWrap: doWordWrap = false,\r\n extraFormat,\r\n } = this.opts;\r\n\r\n // Extras are supported for plain output and word-wrapped boxes only.\r\n let message = formatBase(record, this.opts);\r\n if (!box || doWordWrap) {\r\n message += serializeExtra(record.extra, extraFormat);\r\n }\r\n\r\n if (!box) {\r\n return message;\r\n }\r\n\r\n const lines = message.split(\"\\n\");\r\n\r\n // Determine effective box width.\r\n const w: number =\r\n width === \"auto\"\r\n ? lines.reduce((max, l) => Math.max(max, stripAnsi(l).length), 0)\r\n : (width as number);\r\n\r\n // Word-wrap if requested.\r\n const contentLines = doWordWrap\r\n ? lines.flatMap((line) => wordWrap(line, w))\r\n : lines;\r\n\r\n if (fullBorder) {\r\n const top = \"\\u250c\" + \"\\u2500\".repeat(w + 2) + \"\\u2510\";\r\n const bottom = \"\\u2514\" + \"\\u2500\".repeat(w + 2) + \"\\u2518\";\r\n return [\r\n top,\r\n ...contentLines.map((l) => `\\u2502 ${padVisual(l, w)} \\u2502`),\r\n bottom,\r\n ].join(\"\\n\");\r\n }\r\n\r\n // Left-border only.\r\n const top = \"\\u250c\" + \"\\u2500\".repeat(w + 2);\r\n const bottom = \"\\u2514\" + \"\\u2500\".repeat(w + 2);\r\n const rows = doWordWrap\r\n ? contentLines.map((l) => `\\u2502 ${l}`)\r\n : contentLines.map((l) => `\\u2502 ${padVisual(l, w)} `);\r\n\r\n return [top, ...rows, bottom].join(\"\\n\");\r\n }\r\n}\r\n","/**\n * Custom log handlers for crisplogs.\n *\n * Provides handlers that produce clean output for both console and file destinations.\n */\n\nimport * as fs from \"fs\";\nimport { stripAnsi } from \"./utils\";\nimport type { Handler, Formatter, LogRecord } from \"./types\";\n\n/**\n * Console handler that writes formatted log records to stdout.\n */\nexport class ConsoleHandler implements Handler {\n readonly level: number;\n formatter: Formatter;\n\n constructor(level: number, formatter: Formatter) {\n this.level = level;\n this.formatter = formatter;\n }\n\n emit(record: LogRecord): void {\n const output = this.formatter.format(record);\n process.stdout.write(output + \"\\n\");\n }\n\n close(): void {\n // No-op for console output.\n }\n}\n\n/**\n * File handler that strips ANSI color codes before writing.\n *\n * Logs written to files should be plain text for readability in editors,\n * log aggregators, and CI systems. This handler removes all ANSI escape\n * sequences before writing each record.\n *\n * @example\n * ```ts\n * const handler = new CleanFileHandler(\"app.log\", LEVEL_VALUES.WARNING, formatter);\n * ```\n */\nexport class CleanFileHandler implements Handler {\n readonly level: number;\n formatter: Formatter;\n private stream: fs.WriteStream;\n\n constructor(filename: string, level: number, formatter: Formatter) {\n this.level = level;\n this.formatter = formatter;\n this.stream = fs.createWriteStream(filename, { flags: \"a\" });\n this.stream.on(\"error\", (err) => {\n try {\n process.stderr.write(\n `crisplogs: file write failed (${filename}): ${err.message}\\n`,\n );\n } catch {\n // stderr itself failed, silently swallow.\n }\n });\n }\n\n emit(record: LogRecord): void {\n const output = this.formatter.format(record);\n const clean = stripAnsi(output);\n this.stream.write(clean + \"\\n\");\n }\n\n close(): void {\n this.stream.end();\n }\n}\n"],"mappings":"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","names":["top","bottom","logger"]}
|