@meadown/logger 1.8.10 → 1.8.11
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/dist/cjs/core/writeLog/{formatLocation.d.ts → helpers/formatLocation.d.ts} +1 -1
- package/dist/cjs/core/writeLog/{formatLocation.js → helpers/formatLocation.js} +3 -2
- package/dist/cjs/core/writeLog/helpers/index.d.ts +3 -0
- package/dist/cjs/core/writeLog/helpers/index.js +17 -0
- package/dist/cjs/core/writeLog/{renderMessage.js → helpers/renderMessage.js} +2 -2
- package/dist/cjs/core/writeLog/{visibleLines.js → helpers/visibleLines.js} +1 -1
- package/dist/cjs/core/writeLog/index.d.ts +2 -15
- package/dist/cjs/core/writeLog/index.js +6 -40
- package/dist/cjs/core/writeLog/writeLog.d.ts +14 -0
- package/dist/cjs/core/writeLog/writeLog.js +43 -0
- package/dist/cjs/index.d.ts +46 -17
- package/dist/cjs/index.js +17 -7
- package/dist/cjs/tap/createTap.js +4 -4
- package/dist/cjs/tap/tapAsync/helpers/buildBlock.d.ts +19 -0
- package/dist/cjs/tap/tapAsync/helpers/buildBlock.js +56 -0
- package/dist/cjs/tap/tapAsync/helpers/format.d.ts +4 -0
- package/dist/cjs/tap/tapAsync/helpers/format.js +30 -0
- package/dist/cjs/tap/tapAsync/helpers/index.d.ts +4 -0
- package/dist/cjs/tap/tapAsync/helpers/index.js +20 -0
- package/dist/cjs/tap/tapAsync/helpers/isThenable.d.ts +2 -0
- package/dist/cjs/tap/tapAsync/helpers/isThenable.js +15 -0
- package/dist/cjs/tap/tapAsync/helpers/response.d.ts +21 -0
- package/dist/cjs/tap/tapAsync/helpers/response.js +62 -0
- package/dist/cjs/tap/tapAsync/index.d.ts +2 -0
- package/dist/cjs/tap/tapAsync/index.js +13 -0
- package/dist/{tap → cjs/tap/tapAsync}/tapAsync.d.ts +1 -3
- package/dist/cjs/tap/tapAsync/tapAsync.js +92 -0
- package/dist/core/writeLog/{formatLocation.d.ts → helpers/formatLocation.d.ts} +1 -1
- package/dist/core/writeLog/{formatLocation.js → helpers/formatLocation.js} +3 -2
- package/dist/core/writeLog/helpers/index.d.ts +3 -0
- package/dist/core/writeLog/helpers/index.js +9 -0
- package/dist/core/writeLog/{renderMessage.js → helpers/renderMessage.js} +2 -2
- package/dist/core/writeLog/{visibleLines.js → helpers/visibleLines.js} +1 -1
- package/dist/core/writeLog/index.d.ts +2 -15
- package/dist/core/writeLog/index.js +2 -33
- package/dist/core/writeLog/writeLog.d.ts +14 -0
- package/dist/core/writeLog/writeLog.js +37 -0
- package/dist/index.d.ts +46 -17
- package/dist/index.js +17 -7
- package/dist/tap/createTap.js +2 -2
- package/dist/tap/tapAsync/helpers/buildBlock.d.ts +19 -0
- package/dist/tap/tapAsync/helpers/buildBlock.js +53 -0
- package/dist/tap/tapAsync/helpers/format.d.ts +4 -0
- package/dist/tap/tapAsync/helpers/format.js +26 -0
- package/dist/tap/tapAsync/helpers/index.d.ts +4 -0
- package/dist/tap/tapAsync/helpers/index.js +10 -0
- package/dist/tap/tapAsync/helpers/isThenable.d.ts +2 -0
- package/dist/tap/tapAsync/helpers/isThenable.js +12 -0
- package/dist/tap/tapAsync/helpers/response.d.ts +21 -0
- package/dist/tap/tapAsync/helpers/response.js +57 -0
- package/dist/tap/tapAsync/index.d.ts +2 -0
- package/dist/tap/tapAsync/index.js +8 -0
- package/dist/{cjs/tap → tap/tapAsync}/tapAsync.d.ts +1 -3
- package/dist/tap/tapAsync/tapAsync.js +89 -0
- package/package.json +7 -4
- package/dist/cjs/tap/tapAsync.js +0 -192
- package/dist/tap/tapAsync.js +0 -188
- /package/dist/cjs/core/writeLog/{renderMessage.d.ts → helpers/renderMessage.d.ts} +0 -0
- /package/dist/cjs/core/writeLog/{visibleLines.d.ts → helpers/visibleLines.d.ts} +0 -0
- /package/dist/core/writeLog/{renderMessage.d.ts → helpers/renderMessage.d.ts} +0 -0
- /package/dist/core/writeLog/{visibleLines.d.ts → helpers/visibleLines.d.ts} +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* tapAsync.ts
|
|
4
|
+
* Created by Dewan Mobashirul
|
|
5
|
+
* Copyright (c) 2026 dewan-meadown
|
|
6
|
+
* All rights reserved
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.tapAsync = tapAsync;
|
|
10
|
+
const node_perf_hooks_1 = require("node:perf_hooks");
|
|
11
|
+
const index_js_1 = require("./helpers/index.js");
|
|
12
|
+
const isTTY_js_1 = require("../../terminal/isTTY.js");
|
|
13
|
+
const index_js_2 = require("../../core/writeLog/index.js");
|
|
14
|
+
/**
|
|
15
|
+
* The async tap. Fire-and-forget: returns `promise` immediately (unchanged),
|
|
16
|
+
* and logs a rich block once it resolves. For a `Response`, reads the body from
|
|
17
|
+
* a clone so the caller's original stays consumable. A rejection logs an error
|
|
18
|
+
* to stderr. `caller` MUST be resolved by `tap` (the user-facing function) so
|
|
19
|
+
* the logged location points at the user's file.
|
|
20
|
+
*/
|
|
21
|
+
function tapAsync(promise, label, caller) {
|
|
22
|
+
const useColor = (0, isTTY_js_1.isTTY)("stdout");
|
|
23
|
+
const start = node_perf_hooks_1.performance.now();
|
|
24
|
+
void promise.then((resolved) => {
|
|
25
|
+
const ms = Math.round(node_perf_hooks_1.performance.now() - start);
|
|
26
|
+
if ((0, index_js_1.isResponse)(resolved)) {
|
|
27
|
+
let clone = null;
|
|
28
|
+
try {
|
|
29
|
+
clone = resolved.clone();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
clone = null;
|
|
33
|
+
}
|
|
34
|
+
if (clone === null) {
|
|
35
|
+
(0, index_js_2.writeLog)({
|
|
36
|
+
channel: "log",
|
|
37
|
+
tag: "[TAP]",
|
|
38
|
+
args: (0, index_js_1.buildBlock)(label, ms, resolved, { data: undefined, size: "unknown" }, useColor),
|
|
39
|
+
caller,
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const cl = resolved.headers?.get?.("content-length");
|
|
44
|
+
const tooLarge = cl != null && cl !== "" && Number(cl) > 512 * 1024;
|
|
45
|
+
if (tooLarge) {
|
|
46
|
+
(0, index_js_2.writeLog)({
|
|
47
|
+
channel: "log",
|
|
48
|
+
tag: "[TAP]",
|
|
49
|
+
args: (0, index_js_1.buildBlock)(label, ms, resolved, {
|
|
50
|
+
data: "(body too large to display)",
|
|
51
|
+
size: (0, index_js_1.formatBytes)(Number(cl)),
|
|
52
|
+
}, useColor),
|
|
53
|
+
caller,
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
void (0, index_js_1.readBody)(clone).then((body) => {
|
|
58
|
+
(0, index_js_2.writeLog)({
|
|
59
|
+
channel: "log",
|
|
60
|
+
tag: "[TAP]",
|
|
61
|
+
args: (0, index_js_1.buildBlock)(label, ms, resolved, body, useColor),
|
|
62
|
+
caller,
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Non-Response promise — plain value with elapsed time.
|
|
68
|
+
const elapsed = (0, index_js_1.formatDuration)(ms, useColor);
|
|
69
|
+
(0, index_js_2.writeLog)({
|
|
70
|
+
channel: "log",
|
|
71
|
+
tag: "[TAP]",
|
|
72
|
+
args: label === undefined
|
|
73
|
+
? [elapsed, resolved]
|
|
74
|
+
: [`${label} ${elapsed}`, resolved],
|
|
75
|
+
caller,
|
|
76
|
+
});
|
|
77
|
+
}, (err) => {
|
|
78
|
+
const ms = Math.round(node_perf_hooks_1.performance.now() - start);
|
|
79
|
+
const elapsed = (0, index_js_1.formatDuration)(ms, useColor);
|
|
80
|
+
(0, index_js_2.writeLog)({
|
|
81
|
+
channel: "error",
|
|
82
|
+
tag: "[ERROR]",
|
|
83
|
+
args: [
|
|
84
|
+
label === undefined
|
|
85
|
+
? `rejected after ${elapsed}`
|
|
86
|
+
: `${label} rejected after ${elapsed}`,
|
|
87
|
+
err,
|
|
88
|
+
],
|
|
89
|
+
caller,
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Caller } from "
|
|
1
|
+
import { type Caller } from "../../../caller/getCaller.js";
|
|
2
2
|
/**
|
|
3
3
|
* Renders a caller as a `(file:line)` location — a clickable OSC-8 link on a
|
|
4
4
|
* supporting terminal, plain text otherwise. Pure (no stack access).
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
* Copyright (c) 2026 dewan-meadown
|
|
5
5
|
* All rights reserved
|
|
6
6
|
*/
|
|
7
|
-
import { fileUrl, hyperlink } from "
|
|
7
|
+
import { fileUrl, hyperlink } from "../../../decorations/link.js";
|
|
8
8
|
/**
|
|
9
9
|
* Renders a caller as a `(file:line)` location — a clickable OSC-8 link on a
|
|
10
10
|
* supporting terminal, plain text otherwise. Pure (no stack access).
|
|
11
11
|
*/
|
|
12
12
|
export function formatLocation(caller, interactive) {
|
|
13
|
-
if (caller.file !== null && caller.line !== null && interactive)
|
|
13
|
+
if (caller.file !== null && caller.line !== null && interactive) {
|
|
14
14
|
return hyperlink(caller.label, fileUrl(caller.file));
|
|
15
|
+
}
|
|
15
16
|
return caller.label;
|
|
16
17
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* index.ts
|
|
3
|
+
* Created by Dewan Mobashirul
|
|
4
|
+
* Copyright (c) 2026 dewan-meadown
|
|
5
|
+
* All rights reserved
|
|
6
|
+
*/
|
|
7
|
+
export { visibleLines, getVisibleLines, setVisibleLines, } from "./visibleLines.js";
|
|
8
|
+
export { renderMessage } from "./renderMessage.js";
|
|
9
|
+
export { formatLocation } from "./formatLocation.js";
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* All rights reserved
|
|
6
6
|
*/
|
|
7
7
|
import { formatWithOptions } from "node:util";
|
|
8
|
-
import { MESSAGE_INDENT } from "../../constants.js";
|
|
9
|
-
import { colorize } from "../../colors/color.js";
|
|
10
8
|
import { visibleLines } from "./visibleLines.js";
|
|
9
|
+
import { colorize } from "../../../colors/color.js";
|
|
10
|
+
import { MESSAGE_INDENT } from "../../../constants.js";
|
|
11
11
|
/**
|
|
12
12
|
* Collapses a long multi-line message to {@link visibleLines} lines, replacing
|
|
13
13
|
* the rest with a dimmed `… N more lines` summary. When `visibleLines` is 0
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Copyright (c) 2026 dewan-meadown
|
|
5
5
|
* All rights reserved
|
|
6
6
|
*/
|
|
7
|
-
import { DEFAULT_MAX_LINES } from "
|
|
7
|
+
import { DEFAULT_MAX_LINES } from "../../../constants.js";
|
|
8
8
|
/** Max message lines to show before collapsing the rest; 0 (default) shows all. */
|
|
9
9
|
export let visibleLines = DEFAULT_MAX_LINES;
|
|
10
10
|
/** How many lines a long message shows before collapsing (0 = all). */
|
|
@@ -1,15 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export { getVisibleLines, setVisibleLines } from "./visibleLines.js";
|
|
4
|
-
/**
|
|
5
|
-
* Renders and writes one log entry. The `caller` is resolved by the *caller* of
|
|
6
|
-
* this function (the log closure or `tap`) and passed in, so this helper never
|
|
7
|
-
* touches the stack — keeping {@link getCaller}'s frame depth correct no matter
|
|
8
|
-
* which user-facing function delegates here.
|
|
9
|
-
*/
|
|
10
|
-
export declare function writeLog(opts: {
|
|
11
|
-
channel: LogChannel;
|
|
12
|
-
tag: string;
|
|
13
|
-
args: unknown[];
|
|
14
|
-
caller: Caller;
|
|
15
|
-
}): void;
|
|
1
|
+
export { writeLog } from "./writeLog.js";
|
|
2
|
+
export { getVisibleLines, setVisibleLines } from "./helpers/index.js";
|
|
@@ -4,36 +4,5 @@
|
|
|
4
4
|
* Copyright (c) 2026 dewan-meadown
|
|
5
5
|
* All rights reserved
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import { colorize } from "../../colors/color.js";
|
|
10
|
-
import { isTTY } from "../../terminal/isTTY.js";
|
|
11
|
-
import { renderMessage } from "./renderMessage.js";
|
|
12
|
-
import { formatLocation } from "./formatLocation.js";
|
|
13
|
-
export { getVisibleLines, setVisibleLines } from "./visibleLines.js";
|
|
14
|
-
/**
|
|
15
|
-
* Renders and writes one log entry. The `caller` is resolved by the *caller* of
|
|
16
|
-
* this function (the log closure or `tap`) and passed in, so this helper never
|
|
17
|
-
* touches the stack — keeping {@link getCaller}'s frame depth correct no matter
|
|
18
|
-
* which user-facing function delegates here.
|
|
19
|
-
*/
|
|
20
|
-
export function writeLog(opts) {
|
|
21
|
-
const { channel, tag, args, caller } = opts;
|
|
22
|
-
const streamName = channel === "log" ? "stdout" : "stderr";
|
|
23
|
-
// One terminal check drives both color and clickable links — `isTTY` is the
|
|
24
|
-
// single source of truth (DRY). Off when output is piped/redirected.
|
|
25
|
-
const useColor = isTTY(streamName);
|
|
26
|
-
const paint = (s, c) => useColor ? colorize(s, c) : s;
|
|
27
|
-
const location = formatLocation(caller, useColor);
|
|
28
|
-
const tagOut = paint(tag, TAG_COLOR[channel]);
|
|
29
|
-
const timeStamp = paint(getTimeStamp(), "teal");
|
|
30
|
-
const locOut = paint(`(${location})`, "dimTeal");
|
|
31
|
-
const connector = paint(BRANCH, "gray");
|
|
32
|
-
const connectorBottom = paint(BRANCH_END, "gray");
|
|
33
|
-
const separator = paint(SEPARATOR, "gray");
|
|
34
|
-
// Layout: the tag, the message hanging off a `├──` branch, then the timestamp
|
|
35
|
-
// and location on a `└──` branch below. Leading `\n` spaces entries apart.
|
|
36
|
-
const message = renderMessage(args, useColor);
|
|
37
|
-
const meta = `\n${connectorBottom} ${timeStamp} ${separator} ${locOut}`;
|
|
38
|
-
console[channel](`\n${tagOut}`, `\n${connector}`, message, meta);
|
|
39
|
-
}
|
|
7
|
+
export { writeLog } from "./writeLog.js";
|
|
8
|
+
export { getVisibleLines, setVisibleLines } from "./helpers/index.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type LogChannel } from "../../constants.js";
|
|
2
|
+
import { type Caller } from "../../caller/getCaller.js";
|
|
3
|
+
/**
|
|
4
|
+
* Renders and writes one log entry. The `caller` is resolved by the *caller* of
|
|
5
|
+
* this function (the log closure or `tap`) and passed in, so this helper never
|
|
6
|
+
* touches the stack — keeping {@link getCaller}'s frame depth correct no matter
|
|
7
|
+
* which user-facing function delegates here.
|
|
8
|
+
*/
|
|
9
|
+
export declare function writeLog(opts: {
|
|
10
|
+
channel: LogChannel;
|
|
11
|
+
tag: string;
|
|
12
|
+
args: unknown[];
|
|
13
|
+
caller: Caller;
|
|
14
|
+
}): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* writeLog.ts
|
|
3
|
+
* Created by Dewan Mobashirul
|
|
4
|
+
* Copyright (c) 2026 dewan-meadown
|
|
5
|
+
* All rights reserved
|
|
6
|
+
*/
|
|
7
|
+
import { TAG_COLOR, BRANCH, BRANCH_END, SEPARATOR, } from "../../constants.js";
|
|
8
|
+
import { isTTY } from "../../terminal/isTTY.js";
|
|
9
|
+
import getTimeStamp from "../../time/getTimeStamp.js";
|
|
10
|
+
import { colorize } from "../../colors/color.js";
|
|
11
|
+
import { renderMessage, formatLocation } from "./helpers/index.js";
|
|
12
|
+
/**
|
|
13
|
+
* Renders and writes one log entry. The `caller` is resolved by the *caller* of
|
|
14
|
+
* this function (the log closure or `tap`) and passed in, so this helper never
|
|
15
|
+
* touches the stack — keeping {@link getCaller}'s frame depth correct no matter
|
|
16
|
+
* which user-facing function delegates here.
|
|
17
|
+
*/
|
|
18
|
+
export function writeLog(opts) {
|
|
19
|
+
const { channel, tag, args, caller } = opts;
|
|
20
|
+
const streamName = channel === "log" ? "stdout" : "stderr";
|
|
21
|
+
// One terminal check drives both color and clickable links — `isTTY` is the
|
|
22
|
+
// single source of truth (DRY). Off when output is piped/redirected.
|
|
23
|
+
const useColor = isTTY(streamName);
|
|
24
|
+
const paint = (s, c) => (useColor ? colorize(s, c) : s);
|
|
25
|
+
const location = formatLocation(caller, useColor);
|
|
26
|
+
const tagOut = paint(tag, TAG_COLOR[channel]);
|
|
27
|
+
const timeStamp = paint(getTimeStamp(), "teal");
|
|
28
|
+
const locOut = paint(`(${location})`, "dimTeal");
|
|
29
|
+
const connector = paint(BRANCH, "gray");
|
|
30
|
+
const connectorBottom = paint(BRANCH_END, "gray");
|
|
31
|
+
const separator = paint(SEPARATOR, "gray");
|
|
32
|
+
// Layout: the tag, the message hanging off a `├──` branch, then the timestamp
|
|
33
|
+
// and location on a `└──` branch below. Leading `\n` spaces entries apart.
|
|
34
|
+
const message = renderMessage(args, useColor);
|
|
35
|
+
const meta = `\n${connectorBottom} ${timeStamp} ${separator} ${locOut}`;
|
|
36
|
+
console[channel](`\n${tagOut}`, `\n${connector}`, message, meta);
|
|
37
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,32 +1,61 @@
|
|
|
1
|
-
/**
|
|
2
|
-
export interface
|
|
1
|
+
/** Type of the logger — use this to annotate variables or parameters that accept it. */
|
|
2
|
+
export interface Logger {
|
|
3
|
+
/** Log at info level — `stdout`, cyan `[INFO]` tag. */
|
|
3
4
|
(...args: unknown[]): void;
|
|
5
|
+
/** Log at error level — `stderr`, red `[ERROR]` tag. */
|
|
4
6
|
error(...args: unknown[]): void;
|
|
7
|
+
/** Log at warn level — `stderr`, yellow `[WARN]` tag. */
|
|
5
8
|
warn(...args: unknown[]): void;
|
|
6
9
|
/**
|
|
7
|
-
* Logs `value`
|
|
8
|
-
* so it
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Logs `value` with an optional `label` and gives it straight back unchanged
|
|
11
|
+
* — so you can drop it into any expression without adding an extra line.
|
|
12
|
+
*
|
|
13
|
+
* **Sync value** — logged immediately, returned as-is:
|
|
14
|
+
* ```ts
|
|
15
|
+
* const port = logger.tap(3000, "port") // logs it, port is still 3000
|
|
16
|
+
* const user = logger.tap(getUser(), "user") // logs the user object, returns it
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* **Promise** — the same promise comes back; timing and HTTP status are logged
|
|
20
|
+
* in the background once it settles, without blocking your code:
|
|
21
|
+
* ```ts
|
|
22
|
+
* const res = await logger.tap(fetch(url), "GET /users") // logs status + ms
|
|
23
|
+
* const data = await logger.tap(loadConfig(), "config") // logs value + ms
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @param value Any value or promise — always returned as-is.
|
|
27
|
+
* @param label Optional label shown next to the value in the log line.
|
|
12
28
|
*/
|
|
13
29
|
tap<T>(value: T, label?: string): T;
|
|
14
30
|
/**
|
|
15
|
-
* How many lines
|
|
16
|
-
*
|
|
31
|
+
* How many lines to show before the rest collapses into a
|
|
32
|
+
* `… N more lines` summary. `0` (default) shows everything.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* logger.maxLines = 5
|
|
17
36
|
*/
|
|
18
37
|
maxLines: number;
|
|
19
38
|
}
|
|
20
39
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
40
|
+
* A logger built for development — colored tags, a local timestamp, and a
|
|
41
|
+
* clickable `(file:line)` link on every line so you always know where a
|
|
42
|
+
* message came from. Works like `console.log`: pass anything and it prints.
|
|
43
|
+
*
|
|
44
|
+
* Logs only when `NODE_ENV !== "production"`.
|
|
45
|
+
*
|
|
24
46
|
* @example
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* logger.
|
|
47
|
+
* ```ts
|
|
48
|
+
* logger("server started", { port: 3000 })
|
|
49
|
+
* logger.error("db failed", new Error("ECONNREFUSED"))
|
|
50
|
+
* logger.warn("disk above 80%")
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @example Tap — keep the value flowing, log it on the side
|
|
54
|
+
* ```ts
|
|
55
|
+
* const user = logger.tap(await getUser(id), "user")
|
|
56
|
+
* const res = await logger.tap(fetch(url), "GET /users")
|
|
57
|
+
* ```
|
|
29
58
|
*/
|
|
30
|
-
declare const logger:
|
|
59
|
+
declare const logger: Logger;
|
|
31
60
|
export { logger };
|
|
32
61
|
export default logger;
|
package/dist/index.js
CHANGED
|
@@ -8,14 +8,24 @@ import createLog from "./core/createLog.js";
|
|
|
8
8
|
import createTap from "./tap/createTap.js";
|
|
9
9
|
import { getVisibleLines, setVisibleLines } from "./core/writeLog/index.js";
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* A logger built for development — colored tags, a local timestamp, and a
|
|
12
|
+
* clickable `(file:line)` link on every line so you always know where a
|
|
13
|
+
* message came from. Works like `console.log`: pass anything and it prints.
|
|
14
|
+
*
|
|
15
|
+
* Logs only when `NODE_ENV !== "production"`.
|
|
16
|
+
*
|
|
14
17
|
* @example
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* logger.
|
|
18
|
+
* ```ts
|
|
19
|
+
* logger("server started", { port: 3000 })
|
|
20
|
+
* logger.error("db failed", new Error("ECONNREFUSED"))
|
|
21
|
+
* logger.warn("disk above 80%")
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example Tap — keep the value flowing, log it on the side
|
|
25
|
+
* ```ts
|
|
26
|
+
* const user = logger.tap(await getUser(id), "user")
|
|
27
|
+
* const res = await logger.tap(fetch(url), "GET /users")
|
|
28
|
+
* ```
|
|
19
29
|
*/
|
|
20
30
|
const logger = Object.assign(createLog("log", "[INFO]"), {
|
|
21
31
|
error: createLog("error", "[ERROR]"),
|
package/dist/tap/createTap.js
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* Copyright (c) 2026 dewan-meadown
|
|
5
5
|
* All rights reserved
|
|
6
6
|
*/
|
|
7
|
+
import { isLogAllowed } from "../config.js";
|
|
7
8
|
import getCaller from "../caller/getCaller.js";
|
|
8
9
|
import { writeLog } from "../core/writeLog/index.js";
|
|
9
|
-
import {
|
|
10
|
-
import { isThenable, tapAsync } from "./tapAsync.js";
|
|
10
|
+
import { isThenable, tapAsync } from "./tapAsync/index.js";
|
|
11
11
|
/**
|
|
12
12
|
* Builds `tap` — logs a value and returns it **unchanged**, so it drops into any
|
|
13
13
|
* expression (`const u = logger.tap(getUser(), "user")`). The consumer always
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ResponseLike } from "./response.js";
|
|
2
|
+
/**
|
|
3
|
+
* Renders the nested tree block:
|
|
4
|
+
*
|
|
5
|
+
* GET /users/1
|
|
6
|
+
* │
|
|
7
|
+
* │ response:
|
|
8
|
+
* │ ├── time: 65ms
|
|
9
|
+
* │ ├── status: 200 OK
|
|
10
|
+
* │ └── size: 848 B
|
|
11
|
+
* │
|
|
12
|
+
* │ body:
|
|
13
|
+
* │ ├── id: 1
|
|
14
|
+
* │ └── name: Leanne Graham
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildBlock(label: string | undefined, ms: number, res: ResponseLike, body: {
|
|
17
|
+
data: unknown;
|
|
18
|
+
size: string;
|
|
19
|
+
}, useColor: boolean): unknown[];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* buildBlock.ts
|
|
3
|
+
* Created by Dewan Mobashirul
|
|
4
|
+
* Copyright (c) 2026 dewan-meadown
|
|
5
|
+
* All rights reserved
|
|
6
|
+
*/
|
|
7
|
+
import { formatWithOptions } from "node:util";
|
|
8
|
+
import { formatDuration } from "./format.js";
|
|
9
|
+
import { colorize } from "../../../colors/color.js";
|
|
10
|
+
import { formatStatus } from "./response.js";
|
|
11
|
+
/**
|
|
12
|
+
* Renders the nested tree block:
|
|
13
|
+
*
|
|
14
|
+
* GET /users/1
|
|
15
|
+
* │
|
|
16
|
+
* │ response:
|
|
17
|
+
* │ ├── time: 65ms
|
|
18
|
+
* │ ├── status: 200 OK
|
|
19
|
+
* │ └── size: 848 B
|
|
20
|
+
* │
|
|
21
|
+
* │ body:
|
|
22
|
+
* │ ├── id: 1
|
|
23
|
+
* │ └── name: Leanne Graham
|
|
24
|
+
*/
|
|
25
|
+
export function buildBlock(label, ms, res, body, useColor) {
|
|
26
|
+
const paint = (s, c) => useColor ? colorize(s, c) : s;
|
|
27
|
+
const pipe = paint("│", "gray");
|
|
28
|
+
const branch = paint("├──", "gray");
|
|
29
|
+
const last = paint("└──", "gray");
|
|
30
|
+
const indent = `${pipe} `;
|
|
31
|
+
const timeLine = `${indent}${branch} time: ${formatDuration(ms, useColor)}`;
|
|
32
|
+
const statusLine = `${indent}${branch} status: ${formatStatus(res, useColor)}`;
|
|
33
|
+
const sizeLine = `${indent}${last} size: ${body.size}`;
|
|
34
|
+
const responseLines = [
|
|
35
|
+
`${pipe}`,
|
|
36
|
+
`${indent}response:`,
|
|
37
|
+
timeLine,
|
|
38
|
+
statusLine,
|
|
39
|
+
sizeLine,
|
|
40
|
+
];
|
|
41
|
+
const head = label === undefined ? "" : `${label}\n`;
|
|
42
|
+
if (body.data === undefined) {
|
|
43
|
+
return [`${head}${responseLines.join("\n")}`];
|
|
44
|
+
}
|
|
45
|
+
const bodyText = formatWithOptions({ colors: useColor }, body.data);
|
|
46
|
+
const bodyLines = bodyText.split("\n");
|
|
47
|
+
const lastIdx = bodyLines.length - 1;
|
|
48
|
+
const bodyBlock = bodyLines
|
|
49
|
+
.map((line, i) => `${indent}${i === lastIdx ? last : branch} ${line}`)
|
|
50
|
+
.join("\n");
|
|
51
|
+
const full = [...responseLines, `${pipe}`, `${indent}body:`, bodyBlock].join("\n");
|
|
52
|
+
return [`${head}${full}`];
|
|
53
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* format.ts
|
|
3
|
+
* Created by Dewan Mobashirul
|
|
4
|
+
* Copyright (c) 2026 dewan-meadown
|
|
5
|
+
* All rights reserved
|
|
6
|
+
*/
|
|
7
|
+
import { colorize } from "../../../colors/color.js";
|
|
8
|
+
/** `65ms` (green) · `1.2s` (yellow) · `5.8s` (red). */
|
|
9
|
+
export function formatDuration(ms, useColor) {
|
|
10
|
+
const text = ms >= 1000 ? `${(ms / 1000).toFixed(2)}s` : `${ms}ms`;
|
|
11
|
+
if (!useColor)
|
|
12
|
+
return text;
|
|
13
|
+
if (ms >= 2000)
|
|
14
|
+
return colorize(text, "red");
|
|
15
|
+
if (ms >= 500)
|
|
16
|
+
return colorize(text, "yellow");
|
|
17
|
+
return colorize(text, "green");
|
|
18
|
+
}
|
|
19
|
+
/** `848 B` / `1.84 KB` / `2.10 MB`. */
|
|
20
|
+
export function formatBytes(bytes) {
|
|
21
|
+
if (bytes < 1024)
|
|
22
|
+
return `${bytes} B`;
|
|
23
|
+
if (bytes < 1024 * 1024)
|
|
24
|
+
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
25
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
26
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* index.ts
|
|
3
|
+
* Created by Dewan Mobashirul
|
|
4
|
+
* Copyright (c) 2026 dewan-meadown
|
|
5
|
+
* All rights reserved
|
|
6
|
+
*/
|
|
7
|
+
export { readBody, isResponse, formatStatus, } from "./response.js";
|
|
8
|
+
export { buildBlock } from "./buildBlock.js";
|
|
9
|
+
export { isThenable } from "./isThenable.js";
|
|
10
|
+
export { formatDuration, formatBytes } from "./format.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* isThenable.ts
|
|
3
|
+
* Created by Dewan Mobashirul
|
|
4
|
+
* Copyright (c) 2026 dewan-meadown
|
|
5
|
+
* All rights reserved
|
|
6
|
+
*/
|
|
7
|
+
/** Whether `value` is thenable (a promise we can await + time). */
|
|
8
|
+
export function isThenable(value) {
|
|
9
|
+
return (typeof value === "object" &&
|
|
10
|
+
value !== null &&
|
|
11
|
+
typeof value.then === "function");
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type ResponseLike = {
|
|
2
|
+
status: number;
|
|
3
|
+
statusText?: unknown;
|
|
4
|
+
headers?: {
|
|
5
|
+
get?: (name: string) => string | null;
|
|
6
|
+
};
|
|
7
|
+
clone: () => ResponseLike;
|
|
8
|
+
text: () => Promise<string>;
|
|
9
|
+
};
|
|
10
|
+
/** Whether the resolved value is a fetch `Response` we can read. */
|
|
11
|
+
export declare function isResponse(value: unknown): value is ResponseLike;
|
|
12
|
+
export declare function formatStatus(res: ResponseLike, useColor: boolean): string;
|
|
13
|
+
/**
|
|
14
|
+
* Reads a (cloned) response body and returns both the parsed value and the
|
|
15
|
+
* actual byte size. Size is calculated from the body text — not `Content-Length`,
|
|
16
|
+
* which is absent on compressed responses — so size is always shown.
|
|
17
|
+
*/
|
|
18
|
+
export declare function readBody(res: ResponseLike): Promise<{
|
|
19
|
+
data: unknown;
|
|
20
|
+
size: string;
|
|
21
|
+
}>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* response.ts
|
|
3
|
+
* Created by Dewan Mobashirul
|
|
4
|
+
* Copyright (c) 2026 dewan-meadown
|
|
5
|
+
* All rights reserved
|
|
6
|
+
*/
|
|
7
|
+
import { formatBytes } from "./format.js";
|
|
8
|
+
import { colorize } from "../../../colors/color.js";
|
|
9
|
+
/** Whether the resolved value is a fetch `Response` we can read. */
|
|
10
|
+
export function isResponse(value) {
|
|
11
|
+
const v = value;
|
|
12
|
+
return (typeof v?.status === "number" &&
|
|
13
|
+
typeof v.clone === "function" &&
|
|
14
|
+
typeof v.text === "function");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Status badge color:
|
|
18
|
+
* 2xx → green · 3xx → cyan · 4xx → yellow · 5xx → red
|
|
19
|
+
*/
|
|
20
|
+
function statusColor(status) {
|
|
21
|
+
if (status >= 500)
|
|
22
|
+
return "red";
|
|
23
|
+
if (status >= 400)
|
|
24
|
+
return "yellow";
|
|
25
|
+
if (status >= 300)
|
|
26
|
+
return "cyan";
|
|
27
|
+
return "green";
|
|
28
|
+
}
|
|
29
|
+
export function formatStatus(res, useColor) {
|
|
30
|
+
const text = typeof res.statusText === "string" && res.statusText
|
|
31
|
+
? `${res.status} ${res.statusText}`
|
|
32
|
+
: `${res.status}`;
|
|
33
|
+
return useColor ? colorize(text, statusColor(res.status)) : text;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Reads a (cloned) response body and returns both the parsed value and the
|
|
37
|
+
* actual byte size. Size is calculated from the body text — not `Content-Length`,
|
|
38
|
+
* which is absent on compressed responses — so size is always shown.
|
|
39
|
+
*/
|
|
40
|
+
export async function readBody(res) {
|
|
41
|
+
try {
|
|
42
|
+
const text = await res.text();
|
|
43
|
+
const bytes = new TextEncoder().encode(text).length;
|
|
44
|
+
const size = formatBytes(bytes);
|
|
45
|
+
if (text === "")
|
|
46
|
+
return { data: undefined, size };
|
|
47
|
+
try {
|
|
48
|
+
return { data: JSON.parse(text), size };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return { data: text, size };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return { data: undefined, size: "unknown" };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { type Caller } from "
|
|
2
|
-
/** Whether `value` is thenable (a promise we can await + time). */
|
|
3
|
-
export declare function isThenable(value: unknown): value is PromiseLike<unknown>;
|
|
1
|
+
import { type Caller } from "../../caller/getCaller.js";
|
|
4
2
|
/**
|
|
5
3
|
* The async tap. Fire-and-forget: returns `promise` immediately (unchanged),
|
|
6
4
|
* and logs a rich block once it resolves. For a `Response`, reads the body from
|