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.
Files changed (46) hide show
  1. package/README.md +67 -22
  2. package/package.json +24 -11
  3. package/src/context.ts +26 -11
  4. package/src/core.ts +118 -72
  5. package/src/file-writer.ts +12 -6
  6. package/src/index.browser.ts +9 -1
  7. package/src/index.ts +9 -1
  8. package/src/tracing.ts +11 -3
  9. package/src/worker.ts +119 -132
  10. package/.github/workflows/docs.yml +0 -58
  11. package/.github/workflows/release.yml +0 -31
  12. package/.github/workflows/test.yml +0 -20
  13. package/CHANGELOG.md +0 -45
  14. package/CLAUDE.md +0 -299
  15. package/CONTRIBUTING.md +0 -58
  16. package/benchmarks/overhead.ts +0 -267
  17. package/bun.lock +0 -479
  18. package/docs/api-reference.md +0 -400
  19. package/docs/benchmarks.md +0 -106
  20. package/docs/comparison.md +0 -315
  21. package/docs/conditional-logging-research.md +0 -159
  22. package/docs/guide.md +0 -205
  23. package/docs/migration-from-debug.md +0 -310
  24. package/docs/migration-from-pino.md +0 -178
  25. package/docs/migration-from-winston.md +0 -179
  26. package/docs/site/.vitepress/config.ts +0 -67
  27. package/docs/site/api/configuration.md +0 -94
  28. package/docs/site/api/index.md +0 -61
  29. package/docs/site/api/logger.md +0 -99
  30. package/docs/site/api/worker.md +0 -120
  31. package/docs/site/api/writers.md +0 -69
  32. package/docs/site/guide/getting-started.md +0 -143
  33. package/docs/site/guide/journey.md +0 -203
  34. package/docs/site/guide/migration-from-debug.md +0 -24
  35. package/docs/site/guide/spans.md +0 -139
  36. package/docs/site/guide/why.md +0 -55
  37. package/docs/site/guide/workers.md +0 -113
  38. package/docs/site/guide/zero-overhead.md +0 -87
  39. package/docs/site/index.md +0 -54
  40. package/tests/features.test.ts +0 -552
  41. package/tests/logger.test.ts +0 -944
  42. package/tests/tracing.test.ts +0 -618
  43. package/tests/universal.test.ts +0 -107
  44. package/tests/worker.test.ts +0 -590
  45. package/tsconfig.json +0 -20
  46. package/vitest.config.ts +0 -10
@@ -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
- })
@@ -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
- })