核心概念

性能

采样配置
evlog 每个请求增加约 3µs 开销。比 pino、consola 和 winston 更快(在大多数场景下),同时发出更丰富、更有用的事件。

evlog 每个请求增加 约 3µs 的开销,也就是 0.003ms,远低于任何 HTTP 框架或数据库调用的量级。性能在每次拉取时都会通过 CodSpeed 进行跟踪。

evlog 与其他方案对比

所有基准测试均使用 JSON 输出到无操作目标。pino 写入 /dev/null(同步),winston 写入无操作流,consola 使用无操作报告器,evlog 使用静默模式。

结果

场景evlogpinoconsolawinston
简单字符串日志1.96M 次/秒1.06M2.67M977.6K
结构化(5 个字段)1.74M 次/秒705.6K1.75M440.6K
深度嵌套日志1.75M 次/秒507.8K1.04M202.5K
子级 / 作用域日志1.85M 次/秒871.0K272.2K568.5K
广泛事件生命周期1.68M 次/秒209.0K114.6K
突发(100 条日志)19.1K 次/秒10.0K40.8K7.6K
日志创建20.52M 次/秒7.36M299.3K5.43M

evlog 在 7 次直接对比中赢得 4 次,且关键优势明显:广泛事件生命周期中比 pino 快 8 倍,日志创建快 2.8 倍,深度嵌套日志快 3.5 倍。consola 在简单字符串和突发场景略胜一筹(它使用无操作报告器且无需序列化),但 evlog 为每个请求生成一条单一的相关事件,而传统日志生成 N 条独立行。

为什么这很重要:在广泛事件生命周期(真实世界模式)下,evlog 比 pino 快 8 倍,比 winston 快 14.7 倍,同时向日志接收器发送的数据减少 75%,并提供一条可查询的事件而非 4 条断开连接的行。

什么是“广泛事件生命周期”?

该基准测试模拟了一个真实的 API 请求:

const log = createLogger({ method: 'POST', path: '/api/checkout', requestId: 'req_abc' })
log.set({ user: { id: 'usr_123', plan: 'pro' } })
log.set({ cart: { items: 3, total: 9999 } })
log.set({ payment: { method: 'card', last4: '4242' } })
log.emit({ status: 200 })

相同的 CPU 开销,但 evlog 将所有内容集中在一个位置。

为什么 evlog 更快?

上述数字并非魔法,源于深思熟虑的架构选择:

原地修改,而非复制。 log.set() 通过递归的 mergeInto 函数直接写入上下文对象。其他日志记录器在每次调用时克隆对象(使用对象展开或 Object.assign)。evlog 在上下文累积过程中从不分配中间对象。

直到输出阶段才序列化。 上下文在整个请求生命周期中保持为普通 JavaScript 对象。JSON.stringify 仅在输出时运行一次。传统日志记录器在每次 .info() 调用时都进行序列化,4 条日志行意味着 4 次序列化。

惰性分配。 时间戳、采样上下文和覆盖对象仅在真正需要时创建。如果尾采样被禁用(常见情况),其上下文对象永远不会被分配。用于 ISO 时间戳的 Date 实例在多次调用间复用。

一条事件,而非 N 行。 对于典型请求,pino 会发出 4 条以上的 JSON 行,每条都需要序列化、传输和索引。evlog 只发出一条。这样为日志接收器减少 75% 的工作量,减少传输中的字节数,并且查询时只需处理一行而非四行。

正则表达式缓存。 全局模式(用于采样和路由匹配)只编译一次并缓存。重复评估命中缓存而非重新编译。

实际开销

对于典型 API 请求:

组件开销
日志创建49ns
3 次 set() 调用63ns
emit()570ns
采样23ns
增强器管道2.05µs
总计约 2.8µs

作为参考,数据库查询需 1-50ms,HTTP 调用需 10-500ms。evlog 的开销几乎不可察觉

打包体积

每个入口点都支持 tree-shaking。你只为你导入的部分付费。

入口Gzip 体积
logger3.78 kB
utils1.41 kB
error1.21 kB
enrichers1.92 kB
pipeline1.35 kB
http1.21 kB

典型 Nuxt 配置加载 logger + utils,总体积约为 5.2 kB Gzip。打包体积在每次拉取时都会被跟踪并与 main 基线比较。

详细基准测试

日志创建

操作ops/sec平均值
createLogger()(无上下文)19.35M52ns
createLogger()(浅层上下文)20.38M49ns
createLogger()(嵌套上下文)19.10M52ns
createRequestLogger()19.27M52ns

上下文累积(log.set()

操作ops/sec平均值
浅层合并(3 个字段)9.54M105ns
浅层合并(10 个字段)4.78M209ns
深度嵌套合并8.40M119ns
4 次连续调用7.53M133ns

事件发射(log.emit()

操作ops/sec平均值
发射最小事件1.75M570ns
发射带上下文1.76M569ns
完整生命周期(创建 + 3 次 set + 发射)1.69M592ns
发射带错误66.1K15.13µs
发射带错误 更慢,因为 Error.captureStackTrace() 是昂贵的 V8 操作(约 15µs)。仅当抛出错误时触发。

负载扩展

负载ops/sec平均值
小(2 个字段)1.76M567ns
中等(50 个字段)555.5K1.80µs
大(200 个嵌套字段)115.7K8.65µs

采样

操作ops/sec平均值
尾采样(shouldKeep)43.76M23ns
完整发射(包含头部 + 尾部)7.57M132ns

增强器

增强器ops/sec平均值
用户代理(Chrome)2.57M389ns
地理定位(Vercel)5.32M188ns
请求大小24.16M41ns
跟踪上下文4.86M206ns
全部组合487.2K2.05µs

错误处理

操作ops/sec平均值
createError()226.9K4.41µs
parseError()43.92M23ns
往返(创建 + 解析)227.6K4.39µs

方法论与可信度

你能相信这些数字吗?

本页面上的每个基准测试都是开源的可复现的。基准测试文件位于 packages/evlog/bench/。你可以阅读确切代码、在本机运行并验证结果。

所有库在相同条件下测试:

  • 相同输出模式:JSON 输出到无操作目标(不测量磁盘或网络 I/O)
  • 相同预热:每个基准测试在 JIT 稳定后运行 500ms
  • 相同工具Vitest bench,由 tinybench 提供支持
  • 相同机器:比较库时,所有基准测试在同一进程的同一硬件上运行

CI 回归跟踪

性能回归在每次拉取请求时通过两个系统跟踪:

  • CodSpeed 使用 CPU 指令计数运行所有基准测试(而非壁钟时间)。这消除了共享 CI 运行器中的噪声,并产生确定性、可复现的结果。回归直接在 PR 上标记。
  • 打包大小比较 测量所有入口点与 main 基线的对比,并在 PR 评论中发布大小差异报告。

自行运行

Terminal
cd packages/evlog

bun run bench                          # 全部基准测试
bunx vitest bench bench/comparison/    # 仅与备选方案对比
bun bench/scripts/size.ts              # 打包体积