loggily 0.3.0 → 0.4.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/README.md +67 -22
- package/package.json +24 -11
- package/src/context.ts +26 -11
- package/src/core.ts +118 -72
- package/src/file-writer.ts +12 -6
- package/src/index.browser.ts +9 -1
- package/src/index.ts +9 -1
- package/src/tracing.ts +11 -3
- package/src/worker.ts +119 -132
- package/.github/workflows/docs.yml +0 -58
- package/.github/workflows/release.yml +0 -31
- package/.github/workflows/test.yml +0 -20
- package/CHANGELOG.md +0 -45
- package/CLAUDE.md +0 -299
- package/CONTRIBUTING.md +0 -58
- package/benchmarks/overhead.ts +0 -267
- package/bun.lock +0 -479
- package/docs/api-reference.md +0 -400
- package/docs/benchmarks.md +0 -106
- package/docs/comparison.md +0 -315
- package/docs/conditional-logging-research.md +0 -159
- package/docs/guide.md +0 -205
- package/docs/migration-from-debug.md +0 -310
- package/docs/migration-from-pino.md +0 -178
- package/docs/migration-from-winston.md +0 -179
- package/docs/site/.vitepress/config.ts +0 -67
- package/docs/site/api/configuration.md +0 -94
- package/docs/site/api/index.md +0 -61
- package/docs/site/api/logger.md +0 -99
- package/docs/site/api/worker.md +0 -120
- package/docs/site/api/writers.md +0 -69
- package/docs/site/guide/getting-started.md +0 -143
- package/docs/site/guide/journey.md +0 -203
- package/docs/site/guide/migration-from-debug.md +0 -24
- package/docs/site/guide/spans.md +0 -139
- package/docs/site/guide/why.md +0 -55
- package/docs/site/guide/workers.md +0 -113
- package/docs/site/guide/zero-overhead.md +0 -87
- package/docs/site/index.md +0 -54
- package/tests/features.test.ts +0 -552
- package/tests/logger.test.ts +0 -944
- package/tests/tracing.test.ts +0 -618
- package/tests/universal.test.ts +0 -107
- package/tests/worker.test.ts +0 -590
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -10
package/tests/universal.test.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Universal runtime compatibility tests for loggily.
|
|
3
|
-
*
|
|
4
|
-
* Verifies that the core logger works when Node.js globals (process, fs) are unavailable,
|
|
5
|
-
* as they would be in browser/edge/Deno environments.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, test, expect, beforeEach, afterEach, vi } from "vitest"
|
|
9
|
-
|
|
10
|
-
describe("universal runtime compatibility", () => {
|
|
11
|
-
describe("core module (no fs dependency)", () => {
|
|
12
|
-
test("core.ts does not import node:fs", async () => {
|
|
13
|
-
const coreSource = await Bun.file(new URL("../src/core.ts", import.meta.url).pathname).text()
|
|
14
|
-
expect(coreSource).not.toContain('from "fs"')
|
|
15
|
-
expect(coreSource).not.toContain('from "node:fs"')
|
|
16
|
-
expect(coreSource).not.toContain("require(")
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
test("index.browser.ts does not import node:fs or file-writer", async () => {
|
|
20
|
-
const browserSource = await Bun.file(new URL("../src/index.browser.ts", import.meta.url).pathname).text()
|
|
21
|
-
expect(browserSource).not.toContain('from "node:fs"')
|
|
22
|
-
// Type-only imports from file-writer are fine (erased at compile time)
|
|
23
|
-
// But no runtime imports from file-writer
|
|
24
|
-
const runtimeImportLines = browserSource
|
|
25
|
-
.split("\n")
|
|
26
|
-
.filter(
|
|
27
|
-
(line: string) =>
|
|
28
|
-
line.includes("file-writer") &&
|
|
29
|
-
!line.includes("type {") &&
|
|
30
|
-
!line.includes("type{") &&
|
|
31
|
-
!line.trim().startsWith("//"),
|
|
32
|
-
)
|
|
33
|
-
expect(runtimeImportLines).toEqual([])
|
|
34
|
-
// But it should re-export from core
|
|
35
|
-
expect(browserSource).toContain("./core.js")
|
|
36
|
-
})
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
describe("browser entry createFileWriter stub", () => {
|
|
40
|
-
test("throws with helpful message", async () => {
|
|
41
|
-
const { createFileWriter } = await import("../src/index.browser.ts")
|
|
42
|
-
expect(() => createFileWriter()).toThrow("not available in browser")
|
|
43
|
-
})
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
describe("file-writer separation", () => {
|
|
47
|
-
test("file-writer.ts imports from node:fs", async () => {
|
|
48
|
-
const fwSource = await Bun.file(new URL("../src/file-writer.ts", import.meta.url).pathname).text()
|
|
49
|
-
expect(fwSource).toContain('from "node:fs"')
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
test("index.ts re-exports createFileWriter from file-writer", async () => {
|
|
53
|
-
const indexSource = await Bun.file(new URL("../src/index.ts", import.meta.url).pathname).text()
|
|
54
|
-
expect(indexSource).toContain("./file-writer.js")
|
|
55
|
-
expect(indexSource).toContain("createFileWriter")
|
|
56
|
-
})
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
describe("getEnv guard", () => {
|
|
60
|
-
test("process.env reads use getEnv helper (no bare process.env)", async () => {
|
|
61
|
-
const coreSource = await Bun.file(new URL("../src/core.ts", import.meta.url).pathname).text()
|
|
62
|
-
// Should not have bare process.env reads (except in the getEnv function itself and _process init)
|
|
63
|
-
const lines = coreSource.split("\n")
|
|
64
|
-
const bareProcessEnvLines = lines.filter(
|
|
65
|
-
(line, i) =>
|
|
66
|
-
line.includes("process.env") &&
|
|
67
|
-
!line.includes("_process") &&
|
|
68
|
-
!line.includes("getEnv") &&
|
|
69
|
-
!line.trim().startsWith("//") &&
|
|
70
|
-
!line.trim().startsWith("*"),
|
|
71
|
-
)
|
|
72
|
-
expect(bareProcessEnvLines).toEqual([])
|
|
73
|
-
})
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
describe("writeStderr guard", () => {
|
|
77
|
-
test("no bare process.stderr.write calls in core", async () => {
|
|
78
|
-
const coreSource = await Bun.file(new URL("../src/core.ts", import.meta.url).pathname).text()
|
|
79
|
-
const lines = coreSource.split("\n")
|
|
80
|
-
const bareStderrLines = lines.filter(
|
|
81
|
-
(line) =>
|
|
82
|
-
line.includes("process.stderr") &&
|
|
83
|
-
!line.includes("_process") &&
|
|
84
|
-
!line.trim().startsWith("//") &&
|
|
85
|
-
!line.trim().startsWith("*"),
|
|
86
|
-
)
|
|
87
|
-
expect(bareStderrLines).toEqual([])
|
|
88
|
-
})
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
describe("no bare process references in core", () => {
|
|
92
|
-
test("all process usage goes through _process guard", async () => {
|
|
93
|
-
const coreSource = await Bun.file(new URL("../src/core.ts", import.meta.url).pathname).text()
|
|
94
|
-
const lines = coreSource.split("\n")
|
|
95
|
-
const bareProcessLines = lines.filter(
|
|
96
|
-
(line) =>
|
|
97
|
-
// Match bare `process.` but not `_process.` or `typeof process`
|
|
98
|
-
/(?<![_\w])process\./.test(line) &&
|
|
99
|
-
!line.includes("_process") &&
|
|
100
|
-
!line.includes("typeof process") &&
|
|
101
|
-
!line.trim().startsWith("//") &&
|
|
102
|
-
!line.trim().startsWith("*"),
|
|
103
|
-
)
|
|
104
|
-
expect(bareProcessLines).toEqual([])
|
|
105
|
-
})
|
|
106
|
-
})
|
|
107
|
-
})
|
package/tests/worker.test.ts
DELETED
|
@@ -1,590 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Worker Console Forwarding Tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, test, expect, beforeEach, afterEach, vi } from "vitest"
|
|
6
|
-
import {
|
|
7
|
-
forwardConsole,
|
|
8
|
-
restoreConsole,
|
|
9
|
-
createWorkerConsoleHandler,
|
|
10
|
-
createWorkerLogger,
|
|
11
|
-
createWorkerLogHandler,
|
|
12
|
-
resetWorkerIds,
|
|
13
|
-
isWorkerConsoleMessage,
|
|
14
|
-
isWorkerLogMessage,
|
|
15
|
-
isWorkerSpanMessage,
|
|
16
|
-
isWorkerMessage,
|
|
17
|
-
type WorkerConsoleMessage,
|
|
18
|
-
type WorkerLogMessage,
|
|
19
|
-
type WorkerSpanMessage,
|
|
20
|
-
type WorkerMessage,
|
|
21
|
-
} from "../src/worker.ts"
|
|
22
|
-
import { setLogLevel, resetIds, disableSpans, enableSpans } from "../src/index.ts"
|
|
23
|
-
|
|
24
|
-
// Capture console output from main thread handler
|
|
25
|
-
let consoleOutput: { level: string; message: string }[] = []
|
|
26
|
-
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
consoleOutput = []
|
|
29
|
-
resetIds()
|
|
30
|
-
setLogLevel("trace")
|
|
31
|
-
disableSpans()
|
|
32
|
-
|
|
33
|
-
// Mock console methods for main thread
|
|
34
|
-
vi.spyOn(console, "log").mockImplementation((msg) => {
|
|
35
|
-
consoleOutput.push({ level: "log", message: String(msg) })
|
|
36
|
-
})
|
|
37
|
-
vi.spyOn(console, "debug").mockImplementation((msg) => {
|
|
38
|
-
consoleOutput.push({ level: "debug", message: String(msg) })
|
|
39
|
-
})
|
|
40
|
-
vi.spyOn(console, "info").mockImplementation((msg) => {
|
|
41
|
-
consoleOutput.push({ level: "info", message: String(msg) })
|
|
42
|
-
})
|
|
43
|
-
vi.spyOn(console, "warn").mockImplementation((msg) => {
|
|
44
|
-
consoleOutput.push({ level: "warn", message: String(msg) })
|
|
45
|
-
})
|
|
46
|
-
vi.spyOn(console, "error").mockImplementation((msg) => {
|
|
47
|
-
consoleOutput.push({ level: "error", message: String(msg) })
|
|
48
|
-
})
|
|
49
|
-
vi.spyOn(console, "trace").mockImplementation((msg) => {
|
|
50
|
-
consoleOutput.push({ level: "trace", message: String(msg) })
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
// Spans use process.stderr.write to bypass Ink's patchConsole
|
|
54
|
-
vi.spyOn(process.stderr, "write").mockImplementation(((chunk: string | Uint8Array) => {
|
|
55
|
-
consoleOutput.push({ level: "stderr", message: String(chunk) })
|
|
56
|
-
return true
|
|
57
|
-
}) as typeof process.stderr.write)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
afterEach(() => {
|
|
61
|
-
vi.restoreAllMocks()
|
|
62
|
-
restoreConsole()
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
describe("isWorkerConsoleMessage", () => {
|
|
66
|
-
test("returns true for valid message", () => {
|
|
67
|
-
const msg: WorkerConsoleMessage = {
|
|
68
|
-
type: "console",
|
|
69
|
-
level: "log",
|
|
70
|
-
args: ["test"],
|
|
71
|
-
timestamp: Date.now(),
|
|
72
|
-
}
|
|
73
|
-
expect(isWorkerConsoleMessage(msg)).toBe(true)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
test("returns false for invalid messages", () => {
|
|
77
|
-
expect(isWorkerConsoleMessage(null)).toBe(false)
|
|
78
|
-
expect(isWorkerConsoleMessage(undefined)).toBe(false)
|
|
79
|
-
expect(isWorkerConsoleMessage({})).toBe(false)
|
|
80
|
-
expect(isWorkerConsoleMessage({ type: "other" })).toBe(false)
|
|
81
|
-
expect(isWorkerConsoleMessage({ type: "console" })).toBe(false)
|
|
82
|
-
expect(isWorkerConsoleMessage({ type: "console", level: "log" })).toBe(false)
|
|
83
|
-
})
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
describe("forwardConsole", () => {
|
|
87
|
-
test("intercepts console.log", () => {
|
|
88
|
-
const messages: WorkerConsoleMessage[] = []
|
|
89
|
-
const mockPostMessage = (msg: WorkerConsoleMessage) => messages.push(msg)
|
|
90
|
-
|
|
91
|
-
forwardConsole(mockPostMessage)
|
|
92
|
-
console.log("test message")
|
|
93
|
-
|
|
94
|
-
expect(messages).toHaveLength(1)
|
|
95
|
-
expect(messages[0]!.type).toBe("console")
|
|
96
|
-
expect(messages[0]!.level).toBe("log")
|
|
97
|
-
expect(messages[0]!.args).toEqual(["test message"])
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
test("intercepts all console levels", () => {
|
|
101
|
-
const messages: WorkerConsoleMessage[] = []
|
|
102
|
-
const mockPostMessage = (msg: WorkerConsoleMessage) => messages.push(msg)
|
|
103
|
-
|
|
104
|
-
forwardConsole(mockPostMessage)
|
|
105
|
-
|
|
106
|
-
console.log("log")
|
|
107
|
-
console.debug("debug")
|
|
108
|
-
console.info("info")
|
|
109
|
-
console.warn("warn")
|
|
110
|
-
console.error("error")
|
|
111
|
-
console.trace("trace")
|
|
112
|
-
|
|
113
|
-
expect(messages).toHaveLength(6)
|
|
114
|
-
expect(messages.map((m) => m.level)).toEqual(["log", "debug", "info", "warn", "error", "trace"])
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
test("includes namespace if provided", () => {
|
|
118
|
-
const messages: WorkerConsoleMessage[] = []
|
|
119
|
-
const mockPostMessage = (msg: WorkerConsoleMessage) => messages.push(msg)
|
|
120
|
-
|
|
121
|
-
forwardConsole(mockPostMessage, "km:worker:test")
|
|
122
|
-
console.log("message")
|
|
123
|
-
|
|
124
|
-
expect(messages[0]!.namespace).toBe("km:worker:test")
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
test("serializes multiple arguments", () => {
|
|
128
|
-
const messages: WorkerConsoleMessage[] = []
|
|
129
|
-
const mockPostMessage = (msg: WorkerConsoleMessage) => messages.push(msg)
|
|
130
|
-
|
|
131
|
-
forwardConsole(mockPostMessage)
|
|
132
|
-
console.log("message", 123, { key: "value" })
|
|
133
|
-
|
|
134
|
-
expect(messages[0]!.args).toEqual(["message", 123, { key: "value" }])
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
test("serializes Error objects", () => {
|
|
138
|
-
const messages: WorkerConsoleMessage[] = []
|
|
139
|
-
const mockPostMessage = (msg: WorkerConsoleMessage) => messages.push(msg)
|
|
140
|
-
|
|
141
|
-
forwardConsole(mockPostMessage)
|
|
142
|
-
console.error(new Error("test error"))
|
|
143
|
-
|
|
144
|
-
const serializedError = messages[0]!.args[0] as {
|
|
145
|
-
name: string
|
|
146
|
-
message: string
|
|
147
|
-
stack: string
|
|
148
|
-
}
|
|
149
|
-
expect(serializedError.name).toBe("Error")
|
|
150
|
-
expect(serializedError.message).toBe("test error")
|
|
151
|
-
expect(serializedError.stack).toContain("Error: test error")
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
test("handles non-serializable values", () => {
|
|
155
|
-
const messages: WorkerConsoleMessage[] = []
|
|
156
|
-
const mockPostMessage = (msg: WorkerConsoleMessage) => messages.push(msg)
|
|
157
|
-
|
|
158
|
-
forwardConsole(mockPostMessage)
|
|
159
|
-
console.log(function namedFn() {})
|
|
160
|
-
console.log(Symbol("test"))
|
|
161
|
-
|
|
162
|
-
expect(messages[0]!.args[0]).toBe("[Function: namedFn]")
|
|
163
|
-
expect(messages[1]!.args[0]).toBe("Symbol(test)")
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
test("includes timestamp", () => {
|
|
167
|
-
const messages: WorkerConsoleMessage[] = []
|
|
168
|
-
const mockPostMessage = (msg: WorkerConsoleMessage) => messages.push(msg)
|
|
169
|
-
|
|
170
|
-
const before = Date.now()
|
|
171
|
-
forwardConsole(mockPostMessage)
|
|
172
|
-
console.log("message")
|
|
173
|
-
const after = Date.now()
|
|
174
|
-
|
|
175
|
-
expect(messages[0]!.timestamp).toBeGreaterThanOrEqual(before)
|
|
176
|
-
expect(messages[0]!.timestamp).toBeLessThanOrEqual(after)
|
|
177
|
-
})
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
describe("restoreConsole", () => {
|
|
181
|
-
test("restores original console methods", () => {
|
|
182
|
-
const messages: WorkerConsoleMessage[] = []
|
|
183
|
-
const mockPostMessage = (msg: WorkerConsoleMessage) => messages.push(msg)
|
|
184
|
-
|
|
185
|
-
forwardConsole(mockPostMessage)
|
|
186
|
-
console.log("forwarded")
|
|
187
|
-
expect(messages).toHaveLength(1)
|
|
188
|
-
|
|
189
|
-
restoreConsole()
|
|
190
|
-
console.log("not forwarded")
|
|
191
|
-
expect(messages).toHaveLength(1) // Still only 1
|
|
192
|
-
})
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
describe("createWorkerConsoleHandler", () => {
|
|
196
|
-
test("outputs log messages through logger", () => {
|
|
197
|
-
const handler = createWorkerConsoleHandler({ defaultNamespace: "test" })
|
|
198
|
-
|
|
199
|
-
handler({
|
|
200
|
-
type: "console",
|
|
201
|
-
level: "info",
|
|
202
|
-
args: ["test message"],
|
|
203
|
-
timestamp: Date.now(),
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
expect(consoleOutput).toHaveLength(1)
|
|
207
|
-
expect(consoleOutput[0]!.message).toContain("test message")
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
test("respects message namespace over default", () => {
|
|
211
|
-
const handler = createWorkerConsoleHandler({ defaultNamespace: "default" })
|
|
212
|
-
|
|
213
|
-
handler({
|
|
214
|
-
type: "console",
|
|
215
|
-
level: "info",
|
|
216
|
-
namespace: "specific",
|
|
217
|
-
args: ["message"],
|
|
218
|
-
timestamp: Date.now(),
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
expect(consoleOutput[0]!.message).toContain("specific")
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
test("maps console levels to logger levels", () => {
|
|
225
|
-
const handler = createWorkerConsoleHandler({ defaultNamespace: "test" })
|
|
226
|
-
|
|
227
|
-
handler({
|
|
228
|
-
type: "console",
|
|
229
|
-
level: "log",
|
|
230
|
-
args: ["l"],
|
|
231
|
-
timestamp: Date.now(),
|
|
232
|
-
})
|
|
233
|
-
handler({
|
|
234
|
-
type: "console",
|
|
235
|
-
level: "debug",
|
|
236
|
-
args: ["d"],
|
|
237
|
-
timestamp: Date.now(),
|
|
238
|
-
})
|
|
239
|
-
handler({
|
|
240
|
-
type: "console",
|
|
241
|
-
level: "info",
|
|
242
|
-
args: ["i"],
|
|
243
|
-
timestamp: Date.now(),
|
|
244
|
-
})
|
|
245
|
-
handler({
|
|
246
|
-
type: "console",
|
|
247
|
-
level: "warn",
|
|
248
|
-
args: ["w"],
|
|
249
|
-
timestamp: Date.now(),
|
|
250
|
-
})
|
|
251
|
-
handler({
|
|
252
|
-
type: "console",
|
|
253
|
-
level: "error",
|
|
254
|
-
args: ["e"],
|
|
255
|
-
timestamp: Date.now(),
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
expect(consoleOutput).toHaveLength(5)
|
|
259
|
-
// log -> info, debug -> debug, info -> info, warn -> warn, error -> error
|
|
260
|
-
expect(consoleOutput[0]!.level).toBe("info")
|
|
261
|
-
expect(consoleOutput[1]!.level).toBe("debug")
|
|
262
|
-
expect(consoleOutput[2]!.level).toBe("info")
|
|
263
|
-
expect(consoleOutput[3]!.level).toBe("warn")
|
|
264
|
-
expect(consoleOutput[4]!.level).toBe("error")
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
test("formats multiple args as message", () => {
|
|
268
|
-
const handler = createWorkerConsoleHandler({ defaultNamespace: "test" })
|
|
269
|
-
|
|
270
|
-
handler({
|
|
271
|
-
type: "console",
|
|
272
|
-
level: "info",
|
|
273
|
-
args: ["value:", 42, { key: "val" }],
|
|
274
|
-
timestamp: Date.now(),
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
expect(consoleOutput[0]!.message).toContain("value:")
|
|
278
|
-
expect(consoleOutput[0]!.message).toContain("42")
|
|
279
|
-
expect(consoleOutput[0]!.message).toContain("key")
|
|
280
|
-
})
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
describe("end-to-end forwarding", () => {
|
|
284
|
-
test("worker -> main thread flow", () => {
|
|
285
|
-
// Simulate worker side
|
|
286
|
-
const messages: WorkerConsoleMessage[] = []
|
|
287
|
-
const mockPostMessage = (msg: WorkerConsoleMessage) => messages.push(msg)
|
|
288
|
-
|
|
289
|
-
forwardConsole(mockPostMessage, "km:worker:test")
|
|
290
|
-
console.log("worker message", { count: 42 })
|
|
291
|
-
restoreConsole()
|
|
292
|
-
|
|
293
|
-
// Simulate main thread side
|
|
294
|
-
const handler = createWorkerConsoleHandler()
|
|
295
|
-
handler(messages[0]!)
|
|
296
|
-
|
|
297
|
-
expect(consoleOutput).toHaveLength(1)
|
|
298
|
-
expect(consoleOutput[0]!.message).toContain("km:worker:test")
|
|
299
|
-
expect(consoleOutput[0]!.message).toContain("worker message")
|
|
300
|
-
})
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
// ============ Full Logger Tests ============
|
|
304
|
-
|
|
305
|
-
describe("type guards", () => {
|
|
306
|
-
test("isWorkerLogMessage", () => {
|
|
307
|
-
expect(
|
|
308
|
-
isWorkerLogMessage({
|
|
309
|
-
type: "log",
|
|
310
|
-
level: "info",
|
|
311
|
-
namespace: "test",
|
|
312
|
-
message: "hi",
|
|
313
|
-
timestamp: 1,
|
|
314
|
-
}),
|
|
315
|
-
).toBe(true)
|
|
316
|
-
expect(isWorkerLogMessage({ type: "console" })).toBe(false)
|
|
317
|
-
expect(isWorkerLogMessage(null)).toBe(false)
|
|
318
|
-
})
|
|
319
|
-
|
|
320
|
-
test("isWorkerSpanMessage", () => {
|
|
321
|
-
expect(isWorkerSpanMessage({ type: "span", event: "start" })).toBe(true)
|
|
322
|
-
expect(isWorkerSpanMessage({ type: "span", event: "end" })).toBe(true)
|
|
323
|
-
expect(isWorkerSpanMessage({ type: "log" })).toBe(false)
|
|
324
|
-
})
|
|
325
|
-
|
|
326
|
-
test("isWorkerMessage", () => {
|
|
327
|
-
expect(
|
|
328
|
-
isWorkerMessage({
|
|
329
|
-
type: "console",
|
|
330
|
-
level: "log",
|
|
331
|
-
args: [],
|
|
332
|
-
timestamp: 1,
|
|
333
|
-
}),
|
|
334
|
-
).toBe(true)
|
|
335
|
-
expect(
|
|
336
|
-
isWorkerMessage({
|
|
337
|
-
type: "log",
|
|
338
|
-
level: "info",
|
|
339
|
-
namespace: "test",
|
|
340
|
-
message: "hi",
|
|
341
|
-
timestamp: 1,
|
|
342
|
-
}),
|
|
343
|
-
).toBe(true)
|
|
344
|
-
expect(isWorkerMessage({ type: "span", event: "start" })).toBe(true)
|
|
345
|
-
expect(isWorkerMessage({ type: "unknown" })).toBe(false)
|
|
346
|
-
})
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
describe("createWorkerLogger", () => {
|
|
350
|
-
beforeEach(() => {
|
|
351
|
-
resetWorkerIds()
|
|
352
|
-
})
|
|
353
|
-
|
|
354
|
-
test("creates logger with namespace", () => {
|
|
355
|
-
const messages: WorkerMessage[] = []
|
|
356
|
-
const mockPostMessage = (msg: WorkerMessage) => messages.push(msg)
|
|
357
|
-
|
|
358
|
-
const log = createWorkerLogger(mockPostMessage, "km:worker:test")
|
|
359
|
-
expect(log.name).toBe("km:worker:test")
|
|
360
|
-
})
|
|
361
|
-
|
|
362
|
-
test("sends log messages", () => {
|
|
363
|
-
const messages: WorkerMessage[] = []
|
|
364
|
-
const mockPostMessage = (msg: WorkerMessage) => messages.push(msg)
|
|
365
|
-
|
|
366
|
-
const log = createWorkerLogger(mockPostMessage, "test")
|
|
367
|
-
log.info("hello world", { key: "value" })
|
|
368
|
-
|
|
369
|
-
expect(messages).toHaveLength(1)
|
|
370
|
-
const msg = messages[0] as WorkerLogMessage
|
|
371
|
-
expect(msg.type).toBe("log")
|
|
372
|
-
expect(msg.level).toBe("info")
|
|
373
|
-
expect(msg.namespace).toBe("test")
|
|
374
|
-
expect(msg.message).toBe("hello world")
|
|
375
|
-
expect(msg.data).toEqual({ key: "value" })
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
test("sends all log levels", () => {
|
|
379
|
-
const messages: WorkerMessage[] = []
|
|
380
|
-
const mockPostMessage = (msg: WorkerMessage) => messages.push(msg)
|
|
381
|
-
|
|
382
|
-
const log = createWorkerLogger(mockPostMessage, "test")
|
|
383
|
-
log.trace("t")
|
|
384
|
-
log.debug("d")
|
|
385
|
-
log.info("i")
|
|
386
|
-
log.warn("w")
|
|
387
|
-
log.error("e")
|
|
388
|
-
|
|
389
|
-
expect(messages).toHaveLength(5)
|
|
390
|
-
expect((messages[0] as WorkerLogMessage).level).toBe("trace")
|
|
391
|
-
expect((messages[1] as WorkerLogMessage).level).toBe("debug")
|
|
392
|
-
expect((messages[2] as WorkerLogMessage).level).toBe("info")
|
|
393
|
-
expect((messages[3] as WorkerLogMessage).level).toBe("warn")
|
|
394
|
-
expect((messages[4] as WorkerLogMessage).level).toBe("error")
|
|
395
|
-
})
|
|
396
|
-
|
|
397
|
-
test("handles Error objects", () => {
|
|
398
|
-
const messages: WorkerMessage[] = []
|
|
399
|
-
const mockPostMessage = (msg: WorkerMessage) => messages.push(msg)
|
|
400
|
-
|
|
401
|
-
const log = createWorkerLogger(mockPostMessage, "test")
|
|
402
|
-
log.error(new Error("test error"))
|
|
403
|
-
|
|
404
|
-
const msg = messages[0] as WorkerLogMessage
|
|
405
|
-
expect(msg.message).toBe("test error")
|
|
406
|
-
expect(msg.data?.error_type).toBe("Error")
|
|
407
|
-
expect(msg.data?.error_stack).toContain("Error: test error")
|
|
408
|
-
})
|
|
409
|
-
|
|
410
|
-
test("creates child loggers", () => {
|
|
411
|
-
const messages: WorkerMessage[] = []
|
|
412
|
-
const mockPostMessage = (msg: WorkerMessage) => messages.push(msg)
|
|
413
|
-
|
|
414
|
-
const log = createWorkerLogger(mockPostMessage, "parent", {
|
|
415
|
-
version: "1.0",
|
|
416
|
-
})
|
|
417
|
-
const child = log.logger("child", { extra: true })
|
|
418
|
-
|
|
419
|
-
expect(child.name).toBe("parent:child")
|
|
420
|
-
expect(child.props).toEqual({ version: "1.0", extra: true })
|
|
421
|
-
})
|
|
422
|
-
})
|
|
423
|
-
|
|
424
|
-
describe("createWorkerLogger spans", () => {
|
|
425
|
-
beforeEach(() => {
|
|
426
|
-
resetWorkerIds()
|
|
427
|
-
})
|
|
428
|
-
|
|
429
|
-
test("sends span start and end events", () => {
|
|
430
|
-
const messages: WorkerMessage[] = []
|
|
431
|
-
const mockPostMessage = (msg: WorkerMessage) => messages.push(msg)
|
|
432
|
-
|
|
433
|
-
const log = createWorkerLogger(mockPostMessage, "test")
|
|
434
|
-
|
|
435
|
-
{
|
|
436
|
-
using span = log.span("work")
|
|
437
|
-
span.spanData.count = 42
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Should have start and end events
|
|
441
|
-
const spanMessages = messages.filter((m) => m.type === "span") as WorkerSpanMessage[]
|
|
442
|
-
expect(spanMessages).toHaveLength(2)
|
|
443
|
-
|
|
444
|
-
const start = spanMessages.find((m) => m.event === "start")!
|
|
445
|
-
const end = spanMessages.find((m) => m.event === "end")!
|
|
446
|
-
|
|
447
|
-
expect(start.namespace).toBe("test:work")
|
|
448
|
-
expect(start.spanId).toBe("wsp_1")
|
|
449
|
-
expect(start.traceId).toBe("wtr_1")
|
|
450
|
-
|
|
451
|
-
expect(end.namespace).toBe("test:work")
|
|
452
|
-
expect(end.spanId).toBe("wsp_1")
|
|
453
|
-
expect(end.spanData.count).toBe(42)
|
|
454
|
-
expect(end.duration).toBeGreaterThanOrEqual(0)
|
|
455
|
-
})
|
|
456
|
-
|
|
457
|
-
test("nested spans share trace ID", () => {
|
|
458
|
-
const messages: WorkerMessage[] = []
|
|
459
|
-
const mockPostMessage = (msg: WorkerMessage) => messages.push(msg)
|
|
460
|
-
|
|
461
|
-
const log = createWorkerLogger(mockPostMessage, "test")
|
|
462
|
-
|
|
463
|
-
{
|
|
464
|
-
using outer = log.span("outer")
|
|
465
|
-
{
|
|
466
|
-
using inner = outer.span("inner")
|
|
467
|
-
inner.info("inside")
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
const spanMessages = messages.filter((m) => m.type === "span") as WorkerSpanMessage[]
|
|
472
|
-
const outerStart = spanMessages.find((m) => m.namespace === "test:outer" && m.event === "start")!
|
|
473
|
-
const innerStart = spanMessages.find((m) => m.namespace === "test:outer:inner" && m.event === "start")!
|
|
474
|
-
|
|
475
|
-
// Both should share the same trace ID
|
|
476
|
-
expect(innerStart.traceId).toBe(outerStart.traceId)
|
|
477
|
-
// Inner should have outer as parent
|
|
478
|
-
expect(innerStart.parentId).toBe(outerStart.spanId)
|
|
479
|
-
})
|
|
480
|
-
|
|
481
|
-
test("span can log messages", () => {
|
|
482
|
-
const messages: WorkerMessage[] = []
|
|
483
|
-
const mockPostMessage = (msg: WorkerMessage) => messages.push(msg)
|
|
484
|
-
|
|
485
|
-
const log = createWorkerLogger(mockPostMessage, "test")
|
|
486
|
-
|
|
487
|
-
{
|
|
488
|
-
using span = log.span("work")
|
|
489
|
-
span.info("processing")
|
|
490
|
-
span.debug("details")
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
const logMessages = messages.filter((m) => m.type === "log") as WorkerLogMessage[]
|
|
494
|
-
expect(logMessages).toHaveLength(2)
|
|
495
|
-
expect(logMessages[0]!.namespace).toBe("test:work")
|
|
496
|
-
expect(logMessages[0]!.message).toBe("processing")
|
|
497
|
-
})
|
|
498
|
-
})
|
|
499
|
-
|
|
500
|
-
describe("createWorkerLogHandler", () => {
|
|
501
|
-
test("handles log messages", () => {
|
|
502
|
-
const handler = createWorkerLogHandler()
|
|
503
|
-
|
|
504
|
-
handler({
|
|
505
|
-
type: "log",
|
|
506
|
-
level: "info",
|
|
507
|
-
namespace: "test",
|
|
508
|
-
message: "hello",
|
|
509
|
-
data: { key: "value" },
|
|
510
|
-
timestamp: Date.now(),
|
|
511
|
-
})
|
|
512
|
-
|
|
513
|
-
expect(consoleOutput).toHaveLength(1)
|
|
514
|
-
expect(consoleOutput[0]!.message).toContain("test")
|
|
515
|
-
expect(consoleOutput[0]!.message).toContain("hello")
|
|
516
|
-
})
|
|
517
|
-
|
|
518
|
-
test("handles span end events", () => {
|
|
519
|
-
enableSpans()
|
|
520
|
-
const handler = createWorkerLogHandler({ enableSpans: true })
|
|
521
|
-
|
|
522
|
-
handler({
|
|
523
|
-
type: "span",
|
|
524
|
-
event: "end",
|
|
525
|
-
namespace: "test:work",
|
|
526
|
-
spanId: "wsp_1",
|
|
527
|
-
traceId: "wtr_1",
|
|
528
|
-
parentId: null,
|
|
529
|
-
startTime: Date.now() - 100,
|
|
530
|
-
endTime: Date.now(),
|
|
531
|
-
duration: 100,
|
|
532
|
-
props: {},
|
|
533
|
-
spanData: { count: 42 },
|
|
534
|
-
timestamp: Date.now(),
|
|
535
|
-
})
|
|
536
|
-
|
|
537
|
-
// Should have span output
|
|
538
|
-
const spanOutput = consoleOutput.find((o) => o.message.includes("SPAN"))
|
|
539
|
-
expect(spanOutput).toBeDefined()
|
|
540
|
-
})
|
|
541
|
-
|
|
542
|
-
test("handles console messages", () => {
|
|
543
|
-
const handler = createWorkerLogHandler()
|
|
544
|
-
|
|
545
|
-
handler({
|
|
546
|
-
type: "console",
|
|
547
|
-
level: "info",
|
|
548
|
-
namespace: "test",
|
|
549
|
-
args: ["console message"],
|
|
550
|
-
timestamp: Date.now(),
|
|
551
|
-
})
|
|
552
|
-
|
|
553
|
-
expect(consoleOutput).toHaveLength(1)
|
|
554
|
-
expect(consoleOutput[0]!.message).toContain("console message")
|
|
555
|
-
})
|
|
556
|
-
})
|
|
557
|
-
|
|
558
|
-
describe("full logger end-to-end", () => {
|
|
559
|
-
beforeEach(() => {
|
|
560
|
-
resetWorkerIds()
|
|
561
|
-
})
|
|
562
|
-
|
|
563
|
-
test("worker logger -> main handler flow", () => {
|
|
564
|
-
enableSpans()
|
|
565
|
-
const messages: WorkerMessage[] = []
|
|
566
|
-
const mockPostMessage = (msg: WorkerMessage) => messages.push(msg)
|
|
567
|
-
|
|
568
|
-
// Worker side
|
|
569
|
-
const log = createWorkerLogger(mockPostMessage, "km:worker:test")
|
|
570
|
-
log.info("starting work")
|
|
571
|
-
{
|
|
572
|
-
using span = log.span("process")
|
|
573
|
-
span.info("processing...")
|
|
574
|
-
span.spanData.items = 5
|
|
575
|
-
}
|
|
576
|
-
log.info("done")
|
|
577
|
-
|
|
578
|
-
// Main thread side
|
|
579
|
-
const handler = createWorkerLogHandler({ enableSpans: true })
|
|
580
|
-
for (const msg of messages) {
|
|
581
|
-
handler(msg)
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// Should have log outputs and span output
|
|
585
|
-
expect(consoleOutput.length).toBeGreaterThanOrEqual(4) // 3 logs + 1 span
|
|
586
|
-
expect(consoleOutput.some((o) => o.message.includes("starting work"))).toBe(true)
|
|
587
|
-
expect(consoleOutput.some((o) => o.message.includes("processing"))).toBe(true)
|
|
588
|
-
expect(consoleOutput.some((o) => o.message.includes("done"))).toBe(true)
|
|
589
|
-
})
|
|
590
|
-
})
|