本节是 evlog 的心智模型。到最后,你会清楚地知道 evlog 的作用、每个 API 适合什么场景,以及一个事件如何从你的代码流向你的 drain。
如果你是新手,请按顺序阅读。如果你已经在项目中使用过 evlog,可以直接跳到与你问题相符的页面。
这三种模式都共存于同一个日志记录器中。按调用场景选择即可——没有升级路径,没有高级模式,也没有需要切换的开关。底层使用相同的 drain、相同的脱敏机制、相同的类型。
不运行 HTTP 框架?请查看 独立 TypeScript 和 Cloudflare Workers。
三种日志模式
快速对比
简单日志(log)
每次调用一个事件。不累积上下文,也不管理生命周期。
src/index.ts
import { log } from 'evlog'
log.info('auth', '用户已登录')
log.error({ action: 'payment', error: 'card_declined', userId: 42 })
大型事件(createLogger / createRequestLogger)
每个工作单元一个事件。逐步累积上下文,完成时发出。
import { createLogger } from 'evlog'
const log = createLogger({ jobId: 'sync-001', queue: 'emails' })
log.set({ batch: { size: 50, processed: 50 } })
log.emit()
import { createRequestLogger } from 'evlog'
const log = createRequestLogger({ method: 'POST', path: '/api/checkout' })
log.set({ user: { id: 1, plan: 'pro' } })
log.emit()
createRequestLogger 是 createLogger 的轻量封装,预填充了 method、path 和 requestId。
请求日志(框架中间件)
框架集成会在每个请求上自动创建一个大型事件日志记录器。useLogger(event) 用于获取已附加到请求上下文的日志记录器:
server/api/checkout.post.ts
import { useLogger } from 'evlog'
export default defineEventHandler(async (event) => {
const log = useLogger(event)
log.set({ user: { id: 1, plan: 'pro' } })
return { success: true }
// 在响应结束时自动发出
})
useLogger(event) 并不会创建日志记录器,而是获取框架中间件已附加到事件上的日志记录器。每个框架有各自的获取方式(useLogger、req.log、c.get('log') 等)。在 Nuxt 中,useLogger 是自动导入的。何时使用什么
log | createLogger / createRequestLogger | 框架中间件 | |
|---|---|---|---|
| 使用场景 | 快速的一次性事件 | 脚本、任务、工作者、队列、无框架的 HTTP 请求 | 使用框架集成的 API 路由 |
| 上下文 | 单次调用 | 使用 set() 累积 | 使用 set() 累积 |
| 发射 | 立即发射 | 手动调用 emit() | 响应结束时自动发射 |
| 生命周期 | 无 | 手动管理 | 框架管理 |
| 输出 | 控制台 + 导出 | 控制台 + 导出 | 控制台 + 导出 + 增强 |
按上下文
| Context | Best fit | Why |
|---|---|---|
| Nuxt / Next / Hono / Express / … 中的 HTTP 路由 | 通过 框架集成 使用 useLogger(event) | 每个请求一个宽事件,在响应结束时自动发出 |
| 没有框架的 HTTP 处理器 | createRequestLogger({ method, path }) | 与框架中间件相同的形状,手动发出 |
| CLI 工具 / 一次性脚本 | 步骤用 log.*,运行汇总用 createLogger —— 见 独立模式 | 开发环境中友好美观,CI 中结构化输出,为整个运行生成一个汇总事件 |
| 已发布的库 | 只用 createLogger —— 不要使用 initLogger —— 见 独立模式 | 不污染宿主应用的全局配置,也不会强迫使用者接入 drain |
| 后台任务 / 队列 worker / 定时任务 | 每次调用使用一个 createLogger({ jobId, queue }) —— 见 独立模式 | 每次任务运行一个宽事件,非常适合重试分析 |
| Cloudflare Worker / 边缘函数 | createWorkersLogger(req) 或 createRequestLogger —— 见 Cloudflare Workers | 每个请求一个事件,不需要 process 全局变量 |
| AWS Lambda | 先 initLogger 一次,再为每次调用使用 createLogger —— 见 AWS Lambda | 冷启动初始化、每个事件独立作用域、在处理器中刷新 drain |
| 批处理 / 流水线步骤 | 每个阶段使用一个 createLogger({ step }) | 每个阶段一个事件,输入和输出并排呈现 |
| AI 代理 / LLM 调用 | createLogger + createAILogger | 同一个宽事件上记录 token 使用、工具调用、流式指标 |
| 请求内被调用的库函数 | 从调用方传入 useLogger(event),或接受一个 logger 作为参数 | 继承父级请求上下文,贡献到同一个宽事件 |
| 共享工作区包 | 把它当作库来处理——见 独立模式 | 宿主应用负责 initLogger / drain;包使用 createLogger 或接收一个 logger |
这些都不是另一种的“升级版”。在合适的地方,可以在同一个文件中同时使用
log 和 createLogger——它们共享全局 drain、脱敏和类型。共同基础
所有三种模式都基于同一底层能力:
- 开发环境中的美化输出,生产环境中的 JSON(默认,无需配置)
- Drain 管道,可将事件发送到 Axiom、Sentry、PostHog 等更多目标——见 集成 / 适配器
- 带有
why、fix和link的结构化错误,以及仅供后端使用的可选internal - 采样(头部 + 尾部),用于控制生产环境中的日志量
- 在任何秘密信息离开进程之前就将其清除的脱敏
- 零依赖,约 6 kB gzip
本节其余内容
在介绍完三种模式之后,Learn 的其余部分会涵盖所有模式都会出现的概念:
- 结构化错误 —
why、fix、link、internal,以及createError与throw new Error的区别 - 目录 — 可在重构后仍然保留类型的错误 / 审计目录
- 生命周期 — 从
emit()到你的 drain 之间究竟发生了什么 - 采样 — 保留所有错误和慢请求;丢弃健康噪音
- 类型化字段 — 扩展
RequestLogger,让log.set具有自动补全 - 脱敏 — 在进入 drain 之前清除
authorization、password、token等字段的规则
看完 Learn 之后,请前往 集成,将 evlog 接入你的技术栈。