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
package/CLAUDE.md DELETED
@@ -1,299 +0,0 @@
1
- # loggily
2
-
3
- Structured logging with spans. Logger-first architecture: Span = Logger + Duration.
4
-
5
- ## Quick Start
6
-
7
- ```typescript
8
- import { createLogger } from "loggily"
9
- const log = createLogger("myapp")
10
-
11
- log.info("starting")
12
- log.error(new Error("failed"))
13
-
14
- // Spans for timing (implements Disposable)
15
- {
16
- using span = log.span("import", { file: "data.csv" })
17
- span.info("working...")
18
- span.spanData.count = 42
19
- }
20
- // → SPAN myapp:import (15ms) {count: 42, file: "data.csv"}
21
- ```
22
-
23
- ## Environment Variables
24
-
25
- | Variable | Values | Effect |
26
- | ------------ | --------------------------------------- | -------------------------- |
27
- | LOG_LEVEL | trace, debug, info, warn, error, silent | Filter output by level |
28
- | DEBUG | \*, namespace prefixes, -prefix | Filter output by namespace |
29
- | TRACE | 1, true, or namespace prefixes | Enable span output |
30
- | TRACE_FORMAT | json | Force JSON output |
31
- | NODE_ENV | production | Auto-enable JSON format |
32
-
33
- ### Examples
34
-
35
- ```bash
36
- LOG_LEVEL=debug bun run app # Enable debug logging
37
- DEBUG=km:storage bun run app # Only show km:storage (+ children), auto-enables debug level
38
- DEBUG='km:*,-km:sql' bun run app # Show all km namespaces except km:sql
39
- DEBUG='*' bun run app # Show all namespaces at debug level
40
- TRACE=1 bun run app # Enable all span timing output
41
- TRACE=myapp:import bun run app # Enable spans for specific namespace
42
- TRACE=myapp,other bun run app # Enable spans for multiple prefixes
43
- ```
44
-
45
- ## API
46
-
47
- ### createLogger(name, props?)
48
-
49
- Create a logger. Props are inherited by children.
50
-
51
- ```typescript
52
- const log = createLogger("myapp", { version: "1.0" })
53
- ```
54
-
55
- ### Logger Methods
56
-
57
- | Method | Purpose |
58
- | ----------------------------- | ------------------ |
59
- | `.trace(msg, data?)` | Verbose debugging |
60
- | `.debug(msg, data?)` | Debug information |
61
- | `.info(msg, data?)` | Normal operation |
62
- | `.warn(msg, data?)` | Recoverable issues |
63
- | `.error(msg \| Error, data?)` | Failures |
64
-
65
- ### Child Loggers
66
-
67
- ```typescript
68
- // Extend namespace, inherit props
69
- const child = log.logger("import", { file: "data.csv" })
70
- // → namespace: "myapp:import", props: { version: "1.0", file: "data.csv" }
71
-
72
- // Create span (child with timing)
73
- {
74
- using span = log.span("import")
75
- span.info("working...")
76
- }
77
- ```
78
-
79
- ### Spans
80
-
81
- Spans are loggers with timing. They implement `Disposable` for use with `using`:
82
-
83
- ```typescript
84
- {
85
- using span = log.span("operation", { context: "value" })
86
- span.debug("step 1")
87
- span.spanData.processed = 100 // Set custom attributes
88
- }
89
- // On block exit: SPAN myapp:operation (15ms) {processed: 100, context: "value"}
90
- ```
91
-
92
- For environments without `using` support, call `.end()` manually:
93
-
94
- ```typescript
95
- const span = log.span("operation")
96
- try {
97
- span.info("working...")
98
- span.spanData.count = 42
99
- } finally {
100
- span.end()
101
- }
102
- ```
103
-
104
- ### Span Data
105
-
106
- | Property | Type | Description |
107
- | -------------------- | ------------------------- | ------------------------------------- |
108
- | `spanData.id` | string (readonly) | Unique span ID (sp_1, sp_2...) |
109
- | `spanData.traceId` | string (readonly) | Trace ID (shared across nested spans) |
110
- | `spanData.parentId` | string \| null (readonly) | Parent span ID |
111
- | `spanData.startTime` | number (readonly) | Start timestamp (ms) |
112
- | `spanData.duration` | number (readonly) | Live duration since start |
113
- | `spanData.custom` | any (writable) | Set custom attributes |
114
-
115
- ### Configuration Functions
116
-
117
- ```typescript
118
- import {
119
- setLogLevel,
120
- getLogLevel,
121
- enableSpans,
122
- disableSpans,
123
- spansAreEnabled,
124
- setTraceFilter,
125
- getTraceFilter,
126
- setDebugFilter,
127
- getDebugFilter,
128
- } from "loggily"
129
-
130
- setLogLevel("debug") // Set minimum level
131
- getLogLevel() // Get current level: "debug"
132
- enableSpans() // Enable span output
133
- disableSpans() // Disable span output
134
- spansAreEnabled() // Check if spans are enabled
135
- setTraceFilter(["myapp"]) // Only output spans for "myapp" and "myapp:*"
136
- setTraceFilter(null) // Clear filter, output all spans
137
- getTraceFilter() // Get current filter: ["myapp"] or null
138
- setDebugFilter(["myapp"]) // Only show output for "myapp" and "myapp:*"
139
- setDebugFilter(["myapp", "-myapp:sql"]) // Show myapp but exclude myapp:sql
140
- setDebugFilter(null) // Clear filter, show all namespaces
141
- getDebugFilter() // Get current filter: ["myapp", "-myapp:sql"] or null
142
- ```
143
-
144
- ## Distributed Tracing (opt-in)
145
-
146
- ### ID Format
147
-
148
- ```typescript
149
- import { setIdFormat, getIdFormat } from "loggily"
150
-
151
- setIdFormat("simple") // sp_1, tr_1 (default)
152
- setIdFormat("w3c") // 16-char hex span, 32-char hex trace (W3C Trace Context)
153
- ```
154
-
155
- ### traceparent Header
156
-
157
- ```typescript
158
- import { traceparent } from "loggily"
159
-
160
- const span = log.span("http-request")
161
- const header = traceparent(span.spanData)
162
- // → "00-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6-1a2b3c4d5e6f7a8b-01"
163
- fetch(url, { headers: { traceparent: header } })
164
- ```
165
-
166
- ### Sampling
167
-
168
- ```typescript
169
- import { setSampleRate, getSampleRate } from "loggily"
170
-
171
- setSampleRate(0.1) // Sample 10% of traces (head-based)
172
- setSampleRate(1.0) // Sample everything (default)
173
- ```
174
-
175
- ### Context Propagation (Node.js/Bun only)
176
-
177
- ```typescript
178
- import { enableContextPropagation, getCurrentSpan } from "loggily/context"
179
-
180
- enableContextPropagation()
181
-
182
- const log = createLogger("myapp")
183
- {
184
- using span = log.span("request")
185
- // Logs auto-tagged with trace_id/span_id
186
- log.info("handling") // includes trace_id, span_id in output
187
-
188
- // Child spans from ANY logger auto-parent via AsyncLocalStorage
189
- const other = createLogger("db")
190
- const dbSpan = other.span("query") // parentId = span.id
191
-
192
- getCurrentSpan() // { spanId, traceId, parentId }
193
- }
194
- ```
195
-
196
- ## Output Format
197
-
198
- ### Console (development)
199
-
200
- ```
201
- 14:32:15 INFO myapp starting
202
- 14:32:15 DEBUG myapp:import loading {file: "data.csv"}
203
- 14:32:16 SPAN myapp:import (1234ms) {count: 42}
204
- ```
205
-
206
- ### JSON (production / TRACE_FORMAT=json)
207
-
208
- ```json
209
- {"time":"2024-01-15T14:32:15.123Z","level":"info","name":"myapp","msg":"starting"}
210
- {"time":"2024-01-15T14:32:16.456Z","level":"span","name":"myapp:import","msg":"(1234ms)","duration":1234,"count":42}
211
- ```
212
-
213
- ## Zero-Overhead Pattern (Optional Chaining)
214
-
215
- `createLogger` returns `undefined` for disabled log levels, enabling zero-overhead logging.
216
-
217
- **Log levels** (most → least verbose): `trace < debug < info < warn < error < silent`
218
- **Default level**: `warn` for km CLI (trace, debug, and info disabled)
219
-
220
- ```typescript
221
- import { createLogger } from "loggily"
222
-
223
- const log = createLogger("km:tui")
224
-
225
- // All methods support ?. for zero-overhead when their level is disabled
226
- log.trace?.(`very verbose: ${expensiveDebug()}`) // Skipped at default (warn)
227
- log.debug?.(`state: ${getState()}`) // Skipped at default (warn)
228
- log.info?.("starting") // Skipped at default (warn)
229
- log.warn?.("deprecated") // Enabled at default (warn)
230
- log.error?.("failed") // Enabled at default
231
-
232
- // With -v flag or LOG_LEVEL=info, info is enabled:
233
- log.info?.("starting") // Enabled when level=info
234
- ```
235
-
236
- ### Why optional chaining?
237
-
238
- **Benchmark results** (10M iterations, Bun 1.1.x):
239
-
240
- | Scenario | ops/s | ns/op | Notes |
241
- | ------------------------- | -------- | ------- | ----------------------------------- |
242
- | noop (cheap args) | 2168M | 0.5 | Fastest for trivial args |
243
- | `?.` (cheap args) | 1406M | 0.7 | ~0.2ns overhead - negligible |
244
- | noop (expensive args) | 17M | 57.6 | Args still evaluated - wasted! |
245
- | **`?.` (expensive args)** | **408M** | **2.5** | Args NOT evaluated - **22x faster** |
246
-
247
- **Key insight**: Optional chaining is only ~0.2ns slower for cheap args, but **22x faster** for expensive args because it skips argument evaluation entirely.
248
-
249
- - `log.debug?.()` skips the entire call including argument evaluation when debug is disabled
250
- - TypeScript enforces `?.` at compile time (methods are typed as possibly undefined)
251
- - Main benefit: expensive string formatting and function calls are completely skipped
252
-
253
- See [docs/conditional-logging-research.md](docs/conditional-logging-research.md) for detailed research and external references.
254
-
255
- ## Lazy Messages
256
-
257
- Messages can be functions — only called when the log level is enabled:
258
-
259
- ```typescript
260
- log.debug?.(() => `expensive: ${JSON.stringify(bigObject)}`)
261
- // Function never called when debug is disabled
262
- ```
263
-
264
- Type: `LazyMessage = string | (() => string)`
265
-
266
- ## Child Context Loggers
267
-
268
- Create child loggers with additional structured context (not just namespace):
269
-
270
- ```typescript
271
- const child = log.child({ requestId: "abc-123", userId: 42 })
272
- child.info?.("handling request")
273
- // → 14:32:15 INFO myapp handling request {requestId: "abc-123", userId: 42}
274
- ```
275
-
276
- ## JSON Output Format
277
-
278
- ```bash
279
- LOG_FORMAT=json bun run app # Force JSON output in any environment
280
- ```
281
-
282
- In addition to `TRACE_FORMAT=json` and `NODE_ENV=production`, `LOG_FORMAT=json` explicitly enables structured JSON output.
283
-
284
- ## File Writer
285
-
286
- ```typescript
287
- import { createFileWriter } from "loggily"
288
-
289
- const writer = createFileWriter("/tmp/app.log")
290
- const log = createLogger("myapp", { writer })
291
- ```
292
-
293
- ## Best Practices
294
-
295
- 1. **Namespace hierarchy**: Use `:` to create hierarchy (`myapp:db:query`)
296
- 2. **Props for context**: Pass structured data, not string interpolation
297
- 3. **Spans for timing**: Wrap operations you want to measure
298
- 4. **Level filtering**: Use `LOG_LEVEL` to control verbosity
299
- 5. **Conditional logging**: Use `?.` pattern in hot paths to skip arg evaluation
package/CONTRIBUTING.md DELETED
@@ -1,58 +0,0 @@
1
- # Contributing to loggily
2
-
3
- ## Development Setup
4
-
5
- ```bash
6
- git clone https://github.com/beorn/loggily.git
7
- cd logger
8
- bun install
9
- bun run test # Run tests
10
- bun run typecheck # Type check
11
- ```
12
-
13
- ## Code Style
14
-
15
- - TypeScript strict mode
16
- - ESM imports only (`import`/`export`, never `require`)
17
- - Factory functions over classes
18
- - Zero external dependencies
19
-
20
- ## Testing
21
-
22
- All changes should include tests. Run the test suite before submitting:
23
-
24
- ```bash
25
- bun run test
26
- ```
27
-
28
- Tests must be silent on success. Use `vi.spyOn(console, 'log').mockImplementation(() => {})` to suppress output in tests.
29
-
30
- ## Pull Requests
31
-
32
- 1. Fork the repository
33
- 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
34
- 3. Make your changes with tests
35
- 4. Ensure `bun run test` and `bun run typecheck` pass
36
- 5. Commit with [conventional commit](https://conventionalcommits.org/) messages
37
- 6. Push and open a pull request
38
-
39
- ## Commit Messages
40
-
41
- - `feat:` -- New features
42
- - `fix:` -- Bug fixes
43
- - `docs:` -- Documentation only
44
- - `test:` -- Test additions
45
- - `refactor:` -- Code changes that neither fix bugs nor add features
46
- - `chore:` -- Maintenance tasks
47
-
48
- ## Design Principles
49
-
50
- 1. **Logger-first** -- Spans are loggers with timing, not separate concepts
51
- 2. **Minimal surface** -- Few, well-designed functions
52
- 3. **Type safe** -- TypeScript enforces correct usage (e.g., `?.` for disabled levels)
53
- 4. **Zero-cost** -- Optional chaining skips argument evaluation when disabled
54
- 5. **Structured** -- JSON in production, readable console in development
55
-
56
- ## Questions?
57
-
58
- Open an issue for discussion before starting large changes.
@@ -1,267 +0,0 @@
1
- /**
2
- * loggily Benchmark Suite
3
- *
4
- * Compares zero-overhead disabled logging and enabled logging performance
5
- * against popular alternatives: pino, winston, debug.
6
- *
7
- * All "enabled" benchmarks use the same kind of sink (noop writer) for a fair
8
- * apples-to-apples comparison of formatting + serialization throughput.
9
- *
10
- * Run: bun benchmarks/overhead.ts
11
- */
12
-
13
- import { addWriter, createLogger, setLogLevel, setOutputMode, setSuppressConsole, disableSpans } from "../src/index.ts"
14
-
15
- // ── Helpers ──────────────────────────────────────────────────────────────────
16
-
17
- function measure(
18
- name: string,
19
- fn: () => void,
20
- iterations: number,
21
- ): { name: string; opsPerSec: number; nsPerOp: number } {
22
- // Warmup
23
- for (let i = 0; i < 1000; i++) fn()
24
-
25
- const start = Bun.nanoseconds()
26
- for (let i = 0; i < iterations; i++) fn()
27
- const elapsed = Bun.nanoseconds() - start
28
-
29
- const nsPerOp = elapsed / iterations
30
- const opsPerSec = 1e9 / nsPerOp
31
-
32
- return { name, opsPerSec, nsPerOp }
33
- }
34
-
35
- function formatOps(ops: number): string {
36
- if (ops >= 1e9) return `${(ops / 1e9).toFixed(0)}B`
37
- if (ops >= 1e6) return `${(ops / 1e6).toFixed(0)}M`
38
- if (ops >= 1e3) return `${(ops / 1e3).toFixed(0)}K`
39
- return `${ops.toFixed(0)}`
40
- }
41
-
42
- function formatNs(ns: number): string {
43
- if (ns >= 1e6) return `${(ns / 1e6).toFixed(1)}ms`
44
- if (ns >= 1e3) return `${(ns / 1e3).toFixed(1)}µs`
45
- return `${ns.toFixed(1)}ns`
46
- }
47
-
48
- function printResults(title: string, results: Array<{ name: string; opsPerSec: number; nsPerOp: number }>) {
49
- console.log(`\n${title}`)
50
- console.log("─".repeat(70))
51
-
52
- const maxNameLen = Math.max(...results.map((r) => r.name.length))
53
-
54
- for (const r of results) {
55
- const name = r.name.padEnd(maxNameLen)
56
- const ops = formatOps(r.opsPerSec).padStart(6)
57
- const ns = formatNs(r.nsPerOp).padStart(8)
58
- console.log(` ${name} ${ops} ops/s ${ns}/op`)
59
- }
60
- }
61
-
62
- // ── Expensive argument simulation ────────────────────────────────────────────
63
-
64
- function expensiveArg(): string {
65
- return JSON.stringify({ a: 1, b: 2, c: [3, 4, 5], d: { e: "hello", f: true } })
66
- }
67
-
68
- // ── Noop stream (shared sink type for fair enabled comparisons) ──────────────
69
-
70
- const { Writable } = await import("stream")
71
- const noopStream = () =>
72
- new Writable({
73
- write(_chunk: unknown, _encoding: string, callback: () => void) {
74
- callback()
75
- },
76
- })
77
-
78
- // ── loggily setup ──────────────────────────────────────────────────────
79
-
80
- const beornLog = createLogger("bench")
81
- // Route all output to noop writer, suppress console
82
- setSuppressConsole(true)
83
- setOutputMode("writers-only")
84
- addWriter(() => {}) // noop writer — receives formatted output, discards it
85
- disableSpans()
86
-
87
- // ── Pino setup ───────────────────────────────────────────────────────────────
88
-
89
- type LogFn = {
90
- (msg: string): void
91
- (obj: Record<string, unknown>, msg: string): void
92
- }
93
-
94
- interface BenchLogger {
95
- debug: LogFn
96
- info: LogFn
97
- warn: LogFn
98
- }
99
-
100
- // Disabled pino (level=warn, debug/info disabled) — second arg = noop stream
101
- // Enabled pino (level=debug, all levels active) — second arg = noop stream
102
- let pinoDisabled: BenchLogger
103
- let pinoEnabled: BenchLogger
104
- try {
105
- const pino = (await import("pino")).default
106
- pinoDisabled = pino({ level: "warn" }, noopStream())
107
- pinoEnabled = pino({ level: "debug" }, noopStream())
108
- } catch {
109
- console.log("pino not installed — install with: bun add -d pino")
110
- const stub: BenchLogger = { debug: () => {}, info: () => {}, warn: () => {} }
111
- pinoDisabled = stub
112
- pinoEnabled = stub
113
- }
114
-
115
- // ── Winston setup ────────────────────────────────────────────────────────────
116
-
117
- // Disabled winston (level=warn, debug/info disabled)
118
- // Enabled winston (level=debug, all levels active) — noop stream transport
119
- let winstonDisabled: BenchLogger
120
- let winstonEnabled: BenchLogger
121
- try {
122
- const winston = await import("winston")
123
- winstonDisabled = winston.createLogger({
124
- level: "warn",
125
- transports: [new winston.transports.Console({ silent: true })],
126
- })
127
- winstonEnabled = winston.createLogger({
128
- level: "debug",
129
- transports: [new winston.transports.Stream({ stream: noopStream() })],
130
- })
131
- } catch {
132
- console.log("winston not installed — install with: bun add -d winston")
133
- const stub: BenchLogger = { debug: () => {}, info: () => {}, warn: () => {} }
134
- winstonDisabled = stub
135
- winstonEnabled = stub
136
- }
137
-
138
- // ── Debug setup ──────────────────────────────────────────────────────────────
139
-
140
- let debugFn: (msg: string) => void
141
- try {
142
- const debug = (await import("debug")).default
143
- debugFn = debug("bench") // DEBUG env not set → disabled
144
- } catch {
145
- console.log("debug not installed — install with: bun add -d debug")
146
- debugFn = () => {}
147
- }
148
-
149
- // ── Baseline: noop ───────────────────────────────────────────────────────────
150
-
151
- const noop = (): void => {}
152
-
153
- // ── Benchmarks ───────────────────────────────────────────────────────────────
154
-
155
- const N = 10_000_000
156
-
157
- console.log("loggily Benchmark Suite")
158
- console.log(`Iterations: ${(N / 1e6).toFixed(0)}M per test`)
159
- console.log(`Runtime: Bun ${Bun.version}`)
160
- console.log(`Platform: ${process.platform} ${process.arch}`)
161
-
162
- // ─── PART 1: DISABLED LOGGING ────────────────────────────────────────────────
163
-
164
- // 1. Disabled debug call — cheap args
165
- {
166
- setLogLevel("warn") // debug disabled
167
-
168
- const results = [
169
- measure("noop()", () => noop(), N),
170
- measure("beorn: log.debug?.(str)", () => beornLog.debug?.("hello"), N),
171
- measure("pino: log.debug(str)", () => pinoDisabled.debug("hello"), N),
172
- measure("winston: log.debug(str)", () => winstonDisabled.debug("hello"), N),
173
- measure('debug: debug("hello")', () => debugFn("hello"), N),
174
- ]
175
-
176
- printResults("DISABLED DEBUG — cheap argument (string literal)", results)
177
- }
178
-
179
- // 2. Disabled debug call — expensive args
180
- {
181
- setLogLevel("warn") // debug disabled
182
-
183
- const results = [
184
- measure("noop(expensive)", () => noop(), N),
185
- measure("beorn: log.debug?.(expensive)", () => beornLog.debug?.(`state: ${expensiveArg()}`), N),
186
- measure("pino: log.debug(expensive)", () => pinoDisabled.debug(`state: ${expensiveArg()}`), N),
187
- measure("winston: log.debug(expensive)", () => winstonDisabled.debug(`state: ${expensiveArg()}`), N),
188
- measure("debug: debug(expensive)", () => debugFn(`state: ${expensiveArg()}`), N),
189
- ]
190
-
191
- printResults("DISABLED DEBUG — expensive argument (JSON.stringify)", results)
192
- }
193
-
194
- // ─── PART 2: ENABLED LOGGING (all to noop writers) ───────────────────────────
195
- // Fair comparison: all loggers format + serialize, all write to noop sinks.
196
- // beorn: addWriter(noop) + setSuppressConsole(true) + setOutputMode("writers-only")
197
- // pino: pino(opts, noopWritableStream)
198
- // winston: Stream transport with noop Writable
199
-
200
- // 3. Enabled info — cheap args (string literal)
201
- {
202
- setLogLevel("info") // info enabled
203
-
204
- const results = [
205
- measure("beorn: log.info?.(str)", () => beornLog.info?.("hello"), N / 10),
206
- measure("pino: log.info(str)", () => pinoEnabled.info("hello"), N / 10),
207
- measure("winston: log.info(str)", () => winstonEnabled.info("hello"), N / 10),
208
- ]
209
-
210
- printResults("ENABLED INFO — cheap argument (string literal) — all to noop sink", results)
211
- }
212
-
213
- // 4. Enabled info — structured data
214
- {
215
- setLogLevel("info") // info enabled
216
-
217
- const structuredData = { key: "value", count: 42 }
218
-
219
- const results = [
220
- measure("beorn: log.info?.(str, data)", () => beornLog.info?.("request", structuredData), N / 10),
221
- measure("pino: log.info(obj, str)", () => pinoEnabled.info(structuredData, "request"), N / 10),
222
- measure("winston: log.info(str, data)", () => winstonEnabled.info("request", structuredData), N / 10),
223
- ]
224
-
225
- printResults("ENABLED INFO — structured data ({ key, count }) — all to noop sink", results)
226
- }
227
-
228
- // 5. Enabled warn — with Error object
229
- {
230
- setLogLevel("warn") // warn enabled
231
-
232
- const err = new Error("something broke")
233
-
234
- const results = [
235
- measure("beorn: log.warn?.(Error)", () => beornLog.warn?.(err), N / 10),
236
- measure("pino: log.warn(Error)", () => pinoEnabled.warn({ err }, "something broke"), N / 10),
237
- measure("winston: log.warn(str, Error)", () => winstonEnabled.warn("something broke", { error: err }), N / 10),
238
- ]
239
-
240
- printResults("ENABLED WARN — Error object — all to noop sink", results)
241
- }
242
-
243
- // ─── PART 3: SPANS ──────────────────────────────────────────────────────────
244
-
245
- // 6. Span creation + disposal
246
- {
247
- setLogLevel("warn")
248
- disableSpans()
249
-
250
- const results = [
251
- measure(
252
- "beorn: span create+dispose",
253
- () => {
254
- using _s = beornLog.span("op")
255
- },
256
- N / 10,
257
- ),
258
- ]
259
-
260
- printResults("SPAN — create + dispose (no output)", results)
261
- }
262
-
263
- console.log("\n" + "─".repeat(70))
264
- console.log("Key: ops/s = operations per second, /op = time per operation")
265
- console.log("beorn uses ?. for zero-overhead: disabled calls skip argument evaluation")
266
- console.log("Enabled benchmarks: all loggers write to noop sinks (fair comparison)")
267
- console.log("")