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