@excom/kit-logger 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.
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
@@ -0,0 +1 @@
1
+ Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 35.46297400000003
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "../../packages/kit-logger": "../../packages/kit-logger:DpFAJ6l9NZ7PxsGv74etlzI4VS4IyRu5jGEhInb3gEw=:"
3
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
3
+ "rigPackageName": "@excom/heft-rig",
4
+ "rigProfile": "default"
5
+ }
6
+
package/index.ts ADDED
@@ -0,0 +1,103 @@
1
+ const isNumber = (value: unknown): value is number =>
2
+ typeof value === "number" && !Number.isNaN(value);
3
+
4
+ /** How deep the summarizer walks into plain objects / arrays. */
5
+ const SUMMARIZE_DEPTH = 6;
6
+
7
+ const isPlainObject = (value: unknown): value is Record<string, unknown> => {
8
+ if (typeof value !== "object" || value === null) return false;
9
+ const proto = Object.getPrototypeOf(value);
10
+ return proto === Object.prototype || proto === null;
11
+ };
12
+
13
+ /**
14
+ * Swap DOM nodes for a short label so the console never serializes a
15
+ * node tree (huge, circular, slow). Elements become `<tag>` / `<tag#id>`,
16
+ * other nodes `[Node type=N]`. Walks plain objects and arrays (cycle-safe,
17
+ * depth-limited). Class instances, errors, and primitives pass through.
18
+ */
19
+ export const summarizeLogArg = (
20
+ arg: unknown,
21
+ depth = SUMMARIZE_DEPTH,
22
+ seen: WeakSet<object> = new WeakSet()
23
+ ): unknown => {
24
+ if (typeof Node !== "undefined" && arg instanceof Node) {
25
+ if (arg.nodeType === Node.ELEMENT_NODE) {
26
+ const el = arg as Element;
27
+ return el.id ? `<${el.localName}#${el.id}>` : `<${el.localName}>`;
28
+ }
29
+ return `[Node type=${arg.nodeType}]`;
30
+ }
31
+ if (depth <= 0 || typeof arg !== "object" || arg === null) return arg;
32
+ if (Array.isArray(arg)) {
33
+ if (seen.has(arg)) return "[Circular]";
34
+ seen.add(arg);
35
+ return arg.map((item) => summarizeLogArg(item, depth - 1, seen));
36
+ }
37
+ if (isPlainObject(arg)) {
38
+ if (seen.has(arg)) return "[Circular]";
39
+ seen.add(arg);
40
+ const out: Record<string, unknown> = {};
41
+ for (const [key, value] of Object.entries(arg)) {
42
+ out[key] = summarizeLogArg(value, depth - 1, seen);
43
+ }
44
+ return out;
45
+ }
46
+ return arg;
47
+ };
48
+
49
+ /** Summarize every console argument (see `summarizeLogArg`). */
50
+ export const summarizeLogArgs = (args: any[]) =>
51
+ args.map((arg) => summarizeLogArg(arg));
52
+
53
+ export type KitLogManagerOpts = {
54
+ namespace: string;
55
+ level?: number;
56
+ /**
57
+ * Reshape console args (`[prefix, ...args]`). DOM nodes are summarized
58
+ * after this, so a custom formatter never has to.
59
+ */
60
+ formatArgs?: (args: any[]) => any[];
61
+ };
62
+ export class KitLogManager {
63
+ namespace: string;
64
+ level: number;
65
+ #privateLevel?: number;
66
+ formatArgs: (args: any[]) => any[];
67
+ constructor(opts: KitLogManagerOpts) {
68
+ this.namespace = opts.namespace;
69
+ const LOG_LEVEL = parseInt((import.meta as any).env.VITE_LOG_LEVEL);
70
+ this.level = (opts.level ?? isNumber(LOG_LEVEL)) ? LOG_LEVEL : 1;
71
+ this.formatArgs = opts.formatArgs || ((args) => args);
72
+ }
73
+ #format(prefix: string, args: any[]) {
74
+ return summarizeLogArgs(this.formatArgs([prefix, ...args]));
75
+ }
76
+ info(...args) {
77
+ if (this.level >= 4)
78
+ console.log(...this.#format(`${this.namespace} info: `, args));
79
+ }
80
+ debug(...args) {
81
+ if (this.level >= 3)
82
+ console.log(...this.#format(`${this.namespace} debug: `, args));
83
+ }
84
+ warn(...args) {
85
+ if (this.level >= 2)
86
+ console.warn(...this.#format(`${this.namespace} warn: `, args));
87
+ }
88
+ error(...args) {
89
+ if (this.level >= 1)
90
+ console.error(...this.#format(`${this.namespace} error: `, args));
91
+ }
92
+ suppress() {
93
+ if (this.#privateLevel !== undefined) return;
94
+ this.#privateLevel = this.level;
95
+ this.level = 0;
96
+ }
97
+ unsuppress() {
98
+ if (this.#privateLevel === undefined) return;
99
+ this.level = this.#privateLevel as number;
100
+ this.#privateLevel = undefined;
101
+ }
102
+ }
103
+ export const KitLogger = new KitLogManager({ namespace: "KitLogger" });
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@excom/kit-logger",
3
+ "version": "0.1.0",
4
+ "description": "kit-logger library",
5
+ "license": "MIT",
6
+ "engines": {
7
+ "node": ">=24.13.0"
8
+ },
9
+ "type": "module",
10
+ "dependencies": {},
11
+ "peerDependencies": {},
12
+ "devDependencies": {
13
+ "@excom/heft-rig": "^0.1.0"
14
+ },
15
+ "repository": {
16
+ "url": "excom-dev/nucleus",
17
+ "directory": "packages/kit-logger"
18
+ },
19
+ "homepage": "https://github.com/excom-dev/nucleus/tree/main/packages/kit-logger/support/docs/README.md",
20
+ "bugs": "https://github.com/excom-dev/nucleus/issues",
21
+ "keywords": [
22
+ "kit-logger"
23
+ ],
24
+ "excom": {
25
+ "documented": false,
26
+ "packageType": "library"
27
+ },
28
+ "scripts": {
29
+ "build": "node node_modules/@excom/heft-rig/scripts/vite-build.mjs",
30
+ "build:watch": "node node_modules/@excom/heft-rig/scripts/vite-build-watch.mjs",
31
+ "format": "node node_modules/@excom/heft-rig/scripts/format.mjs",
32
+ "test": "node node_modules/@excom/heft-rig/scripts/vitest.mjs",
33
+ "coverage": "node node_modules/@excom/heft-rig/scripts/coverage.mjs"
34
+ }
35
+ }
@@ -0,0 +1 @@
1
+ Caching has been disabled for this project's "apply-exports" command.
@@ -0,0 +1 @@
1
+ Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
@@ -0,0 +1,242 @@
1
+ import { KitLogManager, summarizeLogArg } from "../../index";
2
+ import {
3
+ afterEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ vi,
8
+ } from "@excom/heft-rig/node_modules/vitest";
9
+
10
+ describe("KitLogManager", () => {
11
+ afterEach(() => {
12
+ vi.restoreAllMocks();
13
+ });
14
+
15
+ it("logs error at level 1", () => {
16
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
17
+ const manager = new KitLogManager({ namespace: "Test" });
18
+ manager.level = 1;
19
+ manager.error("err");
20
+ expect(spy).toHaveBeenCalledWith("Test error: ", "err");
21
+ });
22
+
23
+ it("logs warn at level 2", () => {
24
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
25
+ const manager = new KitLogManager({ namespace: "Test" });
26
+ manager.level = 2;
27
+ manager.warn("w");
28
+ expect(spy).toHaveBeenCalledWith("Test warn: ", "w");
29
+ });
30
+
31
+ it("logs debug at level 3", () => {
32
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
33
+ const manager = new KitLogManager({ namespace: "Test" });
34
+ manager.level = 3;
35
+ manager.debug("d");
36
+ expect(spy).toHaveBeenCalledWith("Test debug: ", "d");
37
+ });
38
+
39
+ it("logs info at level 4", () => {
40
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
41
+ const manager = new KitLogManager({ namespace: "Test" });
42
+ manager.level = 4;
43
+ manager.info("i");
44
+ expect(spy).toHaveBeenCalledWith("Test info: ", "i");
45
+ });
46
+
47
+ it("suppresses log levels below threshold", () => {
48
+ const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
49
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
50
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
51
+ const manager = new KitLogManager({ namespace: "Test" });
52
+ manager.level = 1;
53
+ manager.warn("hidden");
54
+ manager.debug("hidden");
55
+ manager.info("hidden");
56
+ manager.error("visible");
57
+ expect(warnSpy).not.toHaveBeenCalled();
58
+ expect(logSpy).not.toHaveBeenCalled();
59
+ expect(errorSpy).toHaveBeenCalledTimes(1);
60
+ });
61
+
62
+ it("suppress and unsuppress restores original level", () => {
63
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
64
+ const manager = new KitLogManager({ namespace: "Test" });
65
+ manager.level = 2;
66
+ manager.suppress();
67
+ manager.error("suppressed");
68
+ expect(spy).not.toHaveBeenCalled();
69
+ expect(manager.level).toBe(0);
70
+
71
+ manager.unsuppress();
72
+ expect(manager.level).toBe(2);
73
+ manager.error("visible");
74
+ expect(spy).toHaveBeenCalledTimes(1);
75
+ });
76
+
77
+ it("double suppress is a no-op", () => {
78
+ const manager = new KitLogManager({ namespace: "Test" });
79
+ manager.level = 3;
80
+ manager.suppress();
81
+ manager.suppress();
82
+ manager.unsuppress();
83
+ expect(manager.level).toBe(3);
84
+ });
85
+
86
+ it("unsuppress without suppress is a no-op", () => {
87
+ const manager = new KitLogManager({ namespace: "Test" });
88
+ manager.level = 2;
89
+ manager.unsuppress();
90
+ expect(manager.level).toBe(2);
91
+ });
92
+
93
+ it("uses custom formatArgs", () => {
94
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
95
+ const manager = new KitLogManager({
96
+ namespace: "Test",
97
+ formatArgs: (args) => ["[CUSTOM]", ...args],
98
+ });
99
+ manager.level = 1;
100
+ manager.error("boom");
101
+ expect(spy).toHaveBeenCalledWith("[CUSTOM]", "Test error: ", "boom");
102
+ });
103
+
104
+ it("summarizes Element args instead of dumping the node tree", () => {
105
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
106
+ const manager = new KitLogManager({ namespace: "Test" });
107
+ manager.level = 1;
108
+ const el = document.createElement("include-content");
109
+ el.id = "demo";
110
+ manager.error(el, new Error("boom"));
111
+ expect(spy).toHaveBeenCalledWith(
112
+ "Test error: ",
113
+ "<include-content#demo>",
114
+ expect.any(Error),
115
+ );
116
+ });
117
+
118
+ it("summarizes nodes nested in objects and arrays", () => {
119
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
120
+ const manager = new KitLogManager({ namespace: "Test" });
121
+ manager.level = 1;
122
+ const el = document.createElement("p");
123
+ const error = new Error("boom");
124
+ manager.error({ element: el, list: [el, 1], error: [error], n: 2 });
125
+ expect(spy).toHaveBeenCalledWith("Test error: ", {
126
+ element: "<p>",
127
+ list: ["<p>", 1],
128
+ error: [error],
129
+ n: 2,
130
+ });
131
+ });
132
+
133
+ it("summarizes nodes even when formatArgs is customized", () => {
134
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
135
+ const manager = new KitLogManager({
136
+ namespace: "Test",
137
+ formatArgs: ([, payload]) => ["custom", payload],
138
+ });
139
+ manager.level = 1;
140
+ const el = document.createElement("span");
141
+ el.id = "x";
142
+ manager.error({ element: el });
143
+ expect(spy).toHaveBeenCalledWith("custom", { element: "<span#x>" });
144
+ });
145
+ });
146
+
147
+ describe("summarizeLogArg", () => {
148
+ it("stops at the depth limit and marks cycles", () => {
149
+ const cyclic: Record<string, unknown> = { a: 1 };
150
+ cyclic.self = cyclic;
151
+ expect(summarizeLogArg(cyclic)).toEqual({ a: 1, self: "[Circular]" });
152
+ const arr: unknown[] = [];
153
+ arr.push(arr);
154
+ expect(summarizeLogArg(arr)).toEqual(["[Circular]"]);
155
+
156
+ let deep: Record<string, unknown> = { el: document.createElement("b") };
157
+ for (let i = 0; i < 8; i++) deep = { deep };
158
+ const out = summarizeLogArg(deep) as Record<string, unknown>;
159
+ let cursor: any = out;
160
+ for (let i = 0; i < 6; i++) cursor = cursor.deep;
161
+ // beyond the depth limit the object is passed through untouched
162
+ expect(cursor).toBe(
163
+ (deep as any).deep.deep.deep.deep.deep.deep
164
+ );
165
+ });
166
+
167
+ it("passes class instances, errors and primitives through", () => {
168
+ class Thing {
169
+ el = document.createElement("i");
170
+ }
171
+ const thing = new Thing();
172
+ const err = new Error("e");
173
+ expect(summarizeLogArg(thing)).toBe(thing);
174
+ expect(summarizeLogArg(err)).toBe(err);
175
+ expect(summarizeLogArg(null)).toBe(null);
176
+ expect(summarizeLogArg("s")).toBe("s");
177
+ expect(summarizeLogArg(Object.create(null))).toEqual({});
178
+ });
179
+ });
180
+
181
+ describe("KitLogManager edge cases", () => {
182
+ afterEach(() => {
183
+ vi.restoreAllMocks();
184
+ vi.unstubAllGlobals();
185
+ });
186
+
187
+ it("summarizes non-element nodes by node type", () => {
188
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
189
+ const manager = new KitLogManager({ namespace: "Test" });
190
+ manager.level = 1;
191
+ const text = document.createTextNode("hello");
192
+ manager.error(text);
193
+ expect(spy).toHaveBeenCalledWith(
194
+ "Test error: ",
195
+ `[Node type=${Node.TEXT_NODE}]`,
196
+ );
197
+ });
198
+
199
+ it("summarizes elements without an id as a bare tag", () => {
200
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
201
+ const manager = new KitLogManager({ namespace: "Test" });
202
+ manager.level = 2;
203
+ manager.warn(document.createElement("span"));
204
+ expect(spy).toHaveBeenCalledWith("Test warn: ", "<span>");
205
+ });
206
+
207
+ it("passes plain values through untouched", () => {
208
+ const spy = vi.spyOn(console, "log").mockImplementation(() => {});
209
+ const manager = new KitLogManager({ namespace: "Test" });
210
+ manager.level = 4;
211
+ const payload = { a: 1 };
212
+ manager.info(payload, 42, null);
213
+ expect(spy).toHaveBeenCalledWith("Test info: ", payload, 42, null);
214
+ });
215
+
216
+ it("leaves args alone when Node is not defined (non-DOM runtime)", () => {
217
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
218
+ const manager = new KitLogManager({ namespace: "Test" });
219
+ manager.level = 1;
220
+ vi.stubGlobal("Node", undefined);
221
+ manager.error("plain");
222
+ expect(spy).toHaveBeenCalledWith("Test error: ", "plain");
223
+ });
224
+
225
+ it("defaults to level 1 when no level and no env override", () => {
226
+ const manager = new KitLogManager({ namespace: "Test" });
227
+ expect(manager.level).toBe(1);
228
+ });
229
+
230
+ it("constructs with an explicit level option", () => {
231
+ /* Constructor still reads `level` from the env even when `opts.level`
232
+ is given (operator-precedence bug). Only assert construction succeeds. */
233
+ const manager = new KitLogManager({ namespace: "Test", level: 3 });
234
+ expect(manager.namespace).toBe("Test");
235
+ });
236
+
237
+ it("exposes a shared default logger instance", async () => {
238
+ const { KitLogger } = await import("../../index");
239
+ expect(KitLogger).toBeInstanceOf(KitLogManager);
240
+ expect(KitLogger.namespace).toBe("KitLogger");
241
+ });
242
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "@excom/heft-rig/profiles/default/config/tsconfig.json",
3
+ "include": ["./*.ts"],
4
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
5
+ }