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.
- package/.github/workflows/docs.yml +58 -0
- package/.github/workflows/release.yml +31 -0
- package/.github/workflows/test.yml +20 -0
- package/CHANGELOG.md +45 -0
- package/CLAUDE.md +299 -0
- package/CONTRIBUTING.md +58 -0
- package/LICENSE +21 -0
- package/README.md +102 -3
- package/benchmarks/overhead.ts +267 -0
- package/bun.lock +479 -0
- package/docs/api-reference.md +400 -0
- package/docs/benchmarks.md +106 -0
- package/docs/comparison.md +315 -0
- package/docs/conditional-logging-research.md +159 -0
- package/docs/guide.md +205 -0
- package/docs/migration-from-debug.md +310 -0
- package/docs/migration-from-pino.md +178 -0
- package/docs/migration-from-winston.md +179 -0
- package/docs/site/.vitepress/config.ts +67 -0
- package/docs/site/api/configuration.md +94 -0
- package/docs/site/api/index.md +61 -0
- package/docs/site/api/logger.md +99 -0
- package/docs/site/api/worker.md +120 -0
- package/docs/site/api/writers.md +69 -0
- package/docs/site/guide/getting-started.md +143 -0
- package/docs/site/guide/journey.md +203 -0
- package/docs/site/guide/migration-from-debug.md +24 -0
- package/docs/site/guide/spans.md +139 -0
- package/docs/site/guide/why.md +55 -0
- package/docs/site/guide/workers.md +113 -0
- package/docs/site/guide/zero-overhead.md +87 -0
- package/docs/site/index.md +54 -0
- package/package.json +56 -8
- package/src/colors.ts +27 -0
- package/src/context.ts +155 -0
- package/src/core.ts +804 -0
- package/src/file-writer.ts +104 -0
- package/src/index.browser.ts +64 -0
- package/src/index.ts +10 -1
- package/src/tracing.ts +142 -0
- package/src/worker.ts +687 -0
- package/tests/features.test.ts +552 -0
- package/tests/logger.test.ts +944 -0
- package/tests/tracing.test.ts +618 -0
- package/tests/universal.test.ts +107 -0
- package/tests/worker.test.ts +590 -0
- package/tsconfig.json +20 -0
- 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("")
|