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/src/worker.ts
CHANGED
|
@@ -42,7 +42,9 @@
|
|
|
42
42
|
|
|
43
43
|
import {
|
|
44
44
|
createLogger,
|
|
45
|
+
createSpanDataProxy,
|
|
45
46
|
enableSpans,
|
|
47
|
+
writeSpan,
|
|
46
48
|
type ConditionalLogger,
|
|
47
49
|
type LazyMessage,
|
|
48
50
|
type Logger,
|
|
@@ -319,8 +321,19 @@ export function createWorkerLogger(
|
|
|
319
321
|
data: data ? { ...props, ...data } : Object.keys(props).length > 0 ? props : undefined,
|
|
320
322
|
timestamp: Date.now(),
|
|
321
323
|
})
|
|
322
|
-
} catch {
|
|
323
|
-
//
|
|
324
|
+
} catch (err) {
|
|
325
|
+
// postMessage failed (e.g. uncloneable data) — send a diagnostic fallback
|
|
326
|
+
try {
|
|
327
|
+
postMessage({
|
|
328
|
+
type: "log",
|
|
329
|
+
level: "error",
|
|
330
|
+
namespace,
|
|
331
|
+
message: `postMessage failed for ${level} "${resolved}": ${err instanceof Error ? err.message : String(err)}`,
|
|
332
|
+
timestamp: Date.now(),
|
|
333
|
+
})
|
|
334
|
+
} catch {
|
|
335
|
+
// Worker might be shutting down — truly nothing we can do
|
|
336
|
+
}
|
|
324
337
|
}
|
|
325
338
|
}
|
|
326
339
|
|
|
@@ -348,37 +361,34 @@ export function createWorkerLogger(
|
|
|
348
361
|
spanData: {},
|
|
349
362
|
timestamp: Date.now(),
|
|
350
363
|
})
|
|
351
|
-
} catch {
|
|
352
|
-
//
|
|
364
|
+
} catch (err) {
|
|
365
|
+
// postMessage failed (e.g. uncloneable props) — send a diagnostic fallback
|
|
366
|
+
try {
|
|
367
|
+
postMessage({
|
|
368
|
+
type: "log",
|
|
369
|
+
level: "error",
|
|
370
|
+
namespace: fullNamespace,
|
|
371
|
+
message: `postMessage failed for span start "${fullNamespace}": ${err instanceof Error ? err.message : String(err)}`,
|
|
372
|
+
timestamp: Date.now(),
|
|
373
|
+
})
|
|
374
|
+
} catch {
|
|
375
|
+
// Worker might be shutting down
|
|
376
|
+
}
|
|
353
377
|
}
|
|
354
378
|
|
|
355
379
|
let ended = false
|
|
356
380
|
|
|
357
|
-
const spanData: SpanData =
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (
|
|
369
|
-
prop !== "id" &&
|
|
370
|
-
prop !== "traceId" &&
|
|
371
|
-
prop !== "parentId" &&
|
|
372
|
-
prop !== "startTime" &&
|
|
373
|
-
prop !== "endTime" &&
|
|
374
|
-
prop !== "duration"
|
|
375
|
-
) {
|
|
376
|
-
customSpanData[prop as string] = value
|
|
377
|
-
return true
|
|
378
|
-
}
|
|
379
|
-
return false
|
|
380
|
-
},
|
|
381
|
-
})
|
|
381
|
+
const spanData: SpanData = createSpanDataProxy(
|
|
382
|
+
() => ({
|
|
383
|
+
id: spanId,
|
|
384
|
+
traceId: spanTraceId,
|
|
385
|
+
parentId: parentSpanId,
|
|
386
|
+
startTime,
|
|
387
|
+
endTime: ended ? Date.now() : null,
|
|
388
|
+
duration: Date.now() - startTime,
|
|
389
|
+
}),
|
|
390
|
+
customSpanData,
|
|
391
|
+
)
|
|
382
392
|
|
|
383
393
|
function endSpan(): void {
|
|
384
394
|
if (ended) return
|
|
@@ -402,8 +412,19 @@ export function createWorkerLogger(
|
|
|
402
412
|
spanData: customSpanData,
|
|
403
413
|
timestamp: Date.now(),
|
|
404
414
|
})
|
|
405
|
-
} catch {
|
|
406
|
-
//
|
|
415
|
+
} catch (err) {
|
|
416
|
+
// postMessage failed (e.g. uncloneable spanData) — send a diagnostic fallback
|
|
417
|
+
try {
|
|
418
|
+
postMessage({
|
|
419
|
+
type: "log",
|
|
420
|
+
level: "error",
|
|
421
|
+
namespace: fullNamespace,
|
|
422
|
+
message: `postMessage failed for span end "${fullNamespace}" (${duration}ms): ${err instanceof Error ? err.message : String(err)}`,
|
|
423
|
+
timestamp: Date.now(),
|
|
424
|
+
})
|
|
425
|
+
} catch {
|
|
426
|
+
// Worker might be shutting down
|
|
427
|
+
}
|
|
407
428
|
}
|
|
408
429
|
}
|
|
409
430
|
|
|
@@ -476,6 +497,60 @@ export interface WorkerConsoleHandlerOptions {
|
|
|
476
497
|
logger?: Logger
|
|
477
498
|
}
|
|
478
499
|
|
|
500
|
+
/** Safely stringify a value, handling circular refs and BigInt */
|
|
501
|
+
function safeStringify(value: unknown): string {
|
|
502
|
+
try {
|
|
503
|
+
return JSON.stringify(value)
|
|
504
|
+
} catch {
|
|
505
|
+
return String(value)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/** Format console args into a message string and optional data object */
|
|
510
|
+
function formatConsoleArgs(args: unknown[]): { message: string; data: Record<string, unknown> | undefined } {
|
|
511
|
+
const message =
|
|
512
|
+
args.length === 0
|
|
513
|
+
? ""
|
|
514
|
+
: args.length === 1 && typeof args[0] === "string"
|
|
515
|
+
? args[0]
|
|
516
|
+
: args.map((a) => (typeof a === "string" ? a : safeStringify(a))).join(" ")
|
|
517
|
+
|
|
518
|
+
const lastArg = args[args.length - 1]
|
|
519
|
+
const data =
|
|
520
|
+
args.length > 1 && typeof lastArg === "object" && lastArg !== null && !Array.isArray(lastArg)
|
|
521
|
+
? (lastArg as Record<string, unknown>)
|
|
522
|
+
: undefined
|
|
523
|
+
|
|
524
|
+
return { message, data }
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** Dispatch a message to a logger at the given console level */
|
|
528
|
+
function dispatchToLogger(
|
|
529
|
+
logger: ConditionalLogger,
|
|
530
|
+
level: "log" | "debug" | "info" | "warn" | "error" | "trace",
|
|
531
|
+
message: string,
|
|
532
|
+
data?: Record<string, unknown>,
|
|
533
|
+
): void {
|
|
534
|
+
switch (level) {
|
|
535
|
+
case "trace":
|
|
536
|
+
logger.trace?.(message, data)
|
|
537
|
+
break
|
|
538
|
+
case "debug":
|
|
539
|
+
logger.debug?.(message, data)
|
|
540
|
+
break
|
|
541
|
+
case "info":
|
|
542
|
+
case "log":
|
|
543
|
+
logger.info?.(message, data)
|
|
544
|
+
break
|
|
545
|
+
case "warn":
|
|
546
|
+
logger.warn?.(message, data)
|
|
547
|
+
break
|
|
548
|
+
case "error":
|
|
549
|
+
logger.error?.(message, data)
|
|
550
|
+
break
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
479
554
|
/**
|
|
480
555
|
* Create a handler for worker console messages.
|
|
481
556
|
*
|
|
@@ -519,42 +594,8 @@ export function createWorkerConsoleHandler(
|
|
|
519
594
|
|
|
520
595
|
return (message: WorkerConsoleMessage) => {
|
|
521
596
|
const logger = getLogger(message.namespace)
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
// Format args into a message string
|
|
525
|
-
const formattedMessage =
|
|
526
|
-
args.length === 0
|
|
527
|
-
? ""
|
|
528
|
-
: args.length === 1 && typeof args[0] === "string"
|
|
529
|
-
? args[0]
|
|
530
|
-
: args.map((a) => (typeof a === "string" ? a : JSON.stringify(a))).join(" ")
|
|
531
|
-
|
|
532
|
-
// Extract data object if present (last arg is object and not a string)
|
|
533
|
-
const lastArg = args[args.length - 1]
|
|
534
|
-
const data =
|
|
535
|
-
args.length > 1 && typeof lastArg === "object" && lastArg !== null && !Array.isArray(lastArg)
|
|
536
|
-
? (lastArg as Record<string, unknown>)
|
|
537
|
-
: undefined
|
|
538
|
-
|
|
539
|
-
// Log at the appropriate level (use ?. since level might be disabled)
|
|
540
|
-
switch (message.level) {
|
|
541
|
-
case "trace":
|
|
542
|
-
logger.trace?.(formattedMessage, data)
|
|
543
|
-
break
|
|
544
|
-
case "debug":
|
|
545
|
-
logger.debug?.(formattedMessage, data)
|
|
546
|
-
break
|
|
547
|
-
case "info":
|
|
548
|
-
case "log":
|
|
549
|
-
logger.info?.(formattedMessage, data)
|
|
550
|
-
break
|
|
551
|
-
case "warn":
|
|
552
|
-
logger.warn?.(formattedMessage, data)
|
|
553
|
-
break
|
|
554
|
-
case "error":
|
|
555
|
-
logger.error?.(formattedMessage, data)
|
|
556
|
-
break
|
|
557
|
-
}
|
|
597
|
+
const { message: msg, data } = formatConsoleArgs(message.args)
|
|
598
|
+
dispatchToLogger(logger, message.level, msg, data)
|
|
558
599
|
}
|
|
559
600
|
}
|
|
560
601
|
|
|
@@ -608,77 +649,23 @@ export function createWorkerLogHandler(options: WorkerLogHandlerOptions = {}): (
|
|
|
608
649
|
|
|
609
650
|
return (message: WorkerMessage) => {
|
|
610
651
|
if (isWorkerConsoleMessage(message)) {
|
|
611
|
-
// Handle console messages
|
|
612
652
|
const logger = getLogger(message.namespace || "worker")
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
args.length === 0
|
|
616
|
-
? ""
|
|
617
|
-
: args.length === 1 && typeof args[0] === "string"
|
|
618
|
-
? args[0]
|
|
619
|
-
: args.map((a) => (typeof a === "string" ? a : JSON.stringify(a))).join(" ")
|
|
620
|
-
|
|
621
|
-
const lastArg = args[args.length - 1]
|
|
622
|
-
const data =
|
|
623
|
-
args.length > 1 && typeof lastArg === "object" && lastArg !== null && !Array.isArray(lastArg)
|
|
624
|
-
? (lastArg as Record<string, unknown>)
|
|
625
|
-
: undefined
|
|
626
|
-
|
|
627
|
-
// Use ?. since level might be disabled
|
|
628
|
-
switch (message.level) {
|
|
629
|
-
case "trace":
|
|
630
|
-
logger.trace?.(formattedMessage, data)
|
|
631
|
-
break
|
|
632
|
-
case "debug":
|
|
633
|
-
logger.debug?.(formattedMessage, data)
|
|
634
|
-
break
|
|
635
|
-
case "info":
|
|
636
|
-
case "log":
|
|
637
|
-
logger.info?.(formattedMessage, data)
|
|
638
|
-
break
|
|
639
|
-
case "warn":
|
|
640
|
-
logger.warn?.(formattedMessage, data)
|
|
641
|
-
break
|
|
642
|
-
case "error":
|
|
643
|
-
logger.error?.(formattedMessage, data)
|
|
644
|
-
break
|
|
645
|
-
}
|
|
653
|
+
const { message: msg, data } = formatConsoleArgs(message.args)
|
|
654
|
+
dispatchToLogger(logger, message.level, msg, data)
|
|
646
655
|
} else if (isWorkerLogMessage(message)) {
|
|
647
|
-
// Handle structured log messages
|
|
648
656
|
const logger = getLogger(message.namespace)
|
|
649
|
-
|
|
650
|
-
// Use ?. since level might be disabled
|
|
651
|
-
switch (message.level) {
|
|
652
|
-
case "trace":
|
|
653
|
-
logger.trace?.(message.message, message.data)
|
|
654
|
-
break
|
|
655
|
-
case "debug":
|
|
656
|
-
logger.debug?.(message.message, message.data)
|
|
657
|
-
break
|
|
658
|
-
case "info":
|
|
659
|
-
logger.info?.(message.message, message.data)
|
|
660
|
-
break
|
|
661
|
-
case "warn":
|
|
662
|
-
logger.warn?.(message.message, message.data)
|
|
663
|
-
break
|
|
664
|
-
case "error":
|
|
665
|
-
logger.error?.(message.message, message.data)
|
|
666
|
-
break
|
|
667
|
-
}
|
|
657
|
+
dispatchToLogger(logger, message.level, message.message, message.data)
|
|
668
658
|
} else if (isWorkerSpanMessage(message)) {
|
|
669
659
|
// Handle span events
|
|
670
|
-
// For span end events,
|
|
660
|
+
// For span end events, output the span with original worker timing data
|
|
671
661
|
if (message.event === "end") {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
// End the span (this will output the span timing)
|
|
681
|
-
span.end()
|
|
662
|
+
writeSpan(message.namespace, message.duration ?? 0, {
|
|
663
|
+
span_id: message.spanId,
|
|
664
|
+
trace_id: message.traceId,
|
|
665
|
+
parent_id: message.parentId,
|
|
666
|
+
...message.props,
|
|
667
|
+
...message.spanData,
|
|
668
|
+
})
|
|
682
669
|
}
|
|
683
670
|
// Start events are informational only on main thread
|
|
684
671
|
// (the actual timing happens in the worker)
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
name: Deploy Documentation
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
paths:
|
|
7
|
-
- "docs/**"
|
|
8
|
-
- "src/**"
|
|
9
|
-
- ".github/workflows/docs.yml"
|
|
10
|
-
workflow_dispatch:
|
|
11
|
-
|
|
12
|
-
permissions:
|
|
13
|
-
contents: read
|
|
14
|
-
pages: write
|
|
15
|
-
id-token: write
|
|
16
|
-
|
|
17
|
-
concurrency:
|
|
18
|
-
group: pages
|
|
19
|
-
cancel-in-progress: false
|
|
20
|
-
|
|
21
|
-
jobs:
|
|
22
|
-
build:
|
|
23
|
-
runs-on: ubuntu-latest
|
|
24
|
-
steps:
|
|
25
|
-
- name: Checkout
|
|
26
|
-
uses: actions/checkout@v4
|
|
27
|
-
with:
|
|
28
|
-
fetch-depth: 0
|
|
29
|
-
|
|
30
|
-
- name: Setup Bun
|
|
31
|
-
uses: oven-sh/setup-bun@v2
|
|
32
|
-
with:
|
|
33
|
-
bun-version: latest
|
|
34
|
-
|
|
35
|
-
- name: Setup Pages
|
|
36
|
-
uses: actions/configure-pages@v4
|
|
37
|
-
|
|
38
|
-
- name: Install dependencies
|
|
39
|
-
run: bun install
|
|
40
|
-
|
|
41
|
-
- name: Build docs
|
|
42
|
-
run: bun run docs:build
|
|
43
|
-
|
|
44
|
-
- name: Upload artifact
|
|
45
|
-
uses: actions/upload-pages-artifact@v3
|
|
46
|
-
with:
|
|
47
|
-
path: docs/site/.vitepress/dist
|
|
48
|
-
|
|
49
|
-
deploy:
|
|
50
|
-
environment:
|
|
51
|
-
name: github-pages
|
|
52
|
-
url: ${{ steps.deployment.outputs.page_url }}
|
|
53
|
-
needs: build
|
|
54
|
-
runs-on: ubuntu-latest
|
|
55
|
-
steps:
|
|
56
|
-
- name: Deploy to GitHub Pages
|
|
57
|
-
id: deployment
|
|
58
|
-
uses: actions/deploy-pages@v4
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
name: Release
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- 'v*'
|
|
7
|
-
|
|
8
|
-
permissions:
|
|
9
|
-
contents: write
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
release:
|
|
13
|
-
runs-on: ubuntu-latest
|
|
14
|
-
steps:
|
|
15
|
-
- uses: actions/checkout@v4
|
|
16
|
-
- uses: oven-sh/setup-bun@v2
|
|
17
|
-
with:
|
|
18
|
-
bun-version: latest
|
|
19
|
-
- run: bun install
|
|
20
|
-
- run: bun test
|
|
21
|
-
- uses: actions/setup-node@v4
|
|
22
|
-
with:
|
|
23
|
-
node-version: '22'
|
|
24
|
-
registry-url: 'https://registry.npmjs.org'
|
|
25
|
-
- run: npm publish --access public
|
|
26
|
-
env:
|
|
27
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
28
|
-
- name: Create GitHub Release
|
|
29
|
-
uses: softprops/action-gh-release@v2
|
|
30
|
-
with:
|
|
31
|
-
generate_release_notes: true
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
name: Tests
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [main]
|
|
8
|
-
workflow_dispatch:
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
test:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v4
|
|
15
|
-
- uses: oven-sh/setup-bun@v2
|
|
16
|
-
with:
|
|
17
|
-
bun-version: latest
|
|
18
|
-
- run: bun install
|
|
19
|
-
- run: bun run typecheck
|
|
20
|
-
- run: bun test
|
package/CHANGELOG.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to loggily will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [0.2.0] - 2026-03-04
|
|
9
|
-
|
|
10
|
-
### Added
|
|
11
|
-
|
|
12
|
-
- **Lazy messages** -- Pass `() => string` functions that are only called when the level is enabled
|
|
13
|
-
- **Child context loggers** -- `log.child({ requestId: "abc" })` creates a logger with structured context fields in every message
|
|
14
|
-
- **LOG_FORMAT env var** -- `LOG_FORMAT=json` explicitly enables structured JSON output
|
|
15
|
-
- `setLogFormat()` / `getLogFormat()` -- Programmatic log format control
|
|
16
|
-
- `setDebugFilter()` / `getDebugFilter()` -- Programmatic namespace filtering (like `DEBUG` env var)
|
|
17
|
-
- **File writer** -- `createFileWriter(path, opts?)` for buffered file output with auto-flush
|
|
18
|
-
- **Writer system** -- `addWriter(fn)` to subscribe to all formatted log output
|
|
19
|
-
- `setOutputMode()` / `getOutputMode()` -- Control output destination (`console`, `stderr`, `writers-only`)
|
|
20
|
-
- `setSuppressConsole()` -- Suppress console output while writers still receive
|
|
21
|
-
- Comprehensive test suite (153 tests)
|
|
22
|
-
|
|
23
|
-
### Changed
|
|
24
|
-
|
|
25
|
-
- `createLogger()` now returns a `ConditionalLogger` directly (no separate function needed)
|
|
26
|
-
- Improved documentation with full API reference and comparison guides
|
|
27
|
-
|
|
28
|
-
## [0.1.0] - 2026-01-15
|
|
29
|
-
|
|
30
|
-
### Added
|
|
31
|
-
|
|
32
|
-
- Initial release
|
|
33
|
-
- `createLogger(name, props?)` -- Create structured logger
|
|
34
|
-
- Logger methods: `trace`, `debug`, `info`, `warn`, `error`
|
|
35
|
-
- Child loggers with `.logger(namespace, props?)`
|
|
36
|
-
- Span timing with `.span(namespace, props?)` and `using` keyword support
|
|
37
|
-
- `SpanData` with id, traceId, parentId, startTime, endTime, duration
|
|
38
|
-
- Custom span attributes via `span.spanData.key = value`
|
|
39
|
-
- Configuration via environment variables: `LOG_LEVEL`, `TRACE`, `TRACE_FORMAT`
|
|
40
|
-
- Programmatic configuration: `setLogLevel`, `getLogLevel`, `enableSpans`, `disableSpans`, `spansAreEnabled`
|
|
41
|
-
- `setTraceFilter()` / `getTraceFilter()` -- Namespace-based span output control
|
|
42
|
-
- Dual output format: pretty console (dev) and JSON (production)
|
|
43
|
-
- Worker thread support: `createWorkerLogger`, `createWorkerLogHandler`, `forwardConsole`
|
|
44
|
-
- Span collection for testing: `startCollecting`, `stopCollecting`, `getCollectedSpans`, `clearCollectedSpans`
|
|
45
|
-
- `resetIds()` for deterministic tests
|