Request Context
把请求作用域的字段累积到一条 access log
Elogs 在请求过程中累积 context,合并到最终的 access log 里 —— 类似 evlog 的 wide event,但不替换你已有的 logger.info() / log.info() API。
基本用法
默认行为 route handler 里调 store.logger.mergeContext(request, partial),partial 里的字段会被并入这条请求最终的 access log。
import { Elysia } from 'elysia'
import { createElogs } from '@eastgold15/elogs'
const app = new Elysia()
.use(createElogs())
.get('/checkout', ({ request, store }) => {
store.logger.mergeContext(request, { userId: 'usr_123' })
store.logger.mergeContext(request, { cartTotal: 9999 })
return { ok: true }
})
最终的 INFO access log 里会带一个 context 对象,合并 userId 和 cartTotal(config.showContextTree 开启时显示为树形):
{
"level": "INFO",
"method": "GET",
"pathname": "/checkout",
"status": 200,
"context": {
"userId": "usr_123",
"cartTotal": 9999
}
}
store.logger 上的 9 个方法
| 方法 | 用途 |
|---|---|
debug(request, message, context?) |
记录 DEBUG |
info(request, message, context?) |
记录 INFO |
warn(request, message, context?) |
记录 WARNING |
error(request, message, context?) |
记录 ERROR |
log(level, request, data, store) |
显式级别 + 结构化数据入口(底层 emit 走这里) |
handleHttpError(request, error, store, options?) |
处理抛出的错误,按状态码派生日志级别 |
getContext(request) |
读当前请求累积的 context(返回冻结视图) |
mergeContext(request, partial) |
合并 partial 到当前请求的 context bag |
pino |
底层 Pino Logger 实例,直接走 pino 协议时用 |
优先级
调 logger.info(request, message, { ...explicit }) 时,显式 context 里的 key 覆盖累积值,不冲突的 key 双方都保留。
store.logger.mergeContext(request, { userId: 'usr_123', role: 'guest' })
store.logger.info(request, 'fetched user', { role: 'admin', email: 'a@b.co' })
// 最终 context: { userId: 'usr_123', role: 'admin', email: 'a@b.co' }
显式覆盖发生在最终 emit 的 merge 阶段(mergeLogDataContext 负责),所以累积值并不会被改写 —— 下一次 access log 还是能拿到原值。
读 context
const ctx = store.logger.getContext(request)
// → Readonly<Record<string, unknown>>
返回的是冻结视图({ ...bag } 浅拷贝),改它没效果。要加字段用 mergeContext。
请求作用域 logger & AsyncLocalStorage
不想每个调用都手动传 request,可以开 useAsyncLocalStorage: true,让 Elysia handler context 上自动派生一个 log,而且深调用栈里 useLogger() 也能拿到同一份 logger。
import { createElogs } from '@eastgold15/elogs'
createElogs({
config: {
useAsyncLocalStorage: true,
},
})
1. Handler context(log)
路由 handler 里直接解构 log:
app.get('/user/:id', ({ log, params }) => {
log.mergeContext({ userId: params.id })
log.info('Fetched user profile')
return { success: true }
})
log 没有 request 参数 —— 已经通过 AsyncLocalStorage 绑定到当前请求。log.mergeContext / log.info / log.warn / log.error / log.debug 跟 store.logger 同名方法一样,只是少了 request。
2. 全局 hook(useLogger())
在嵌套服务层或 helper 里,导入 useLogger() 拿当前请求的 logger:
import { useLogger } from '@eastgold15/elogs'
async function findUser(id: string) {
const log = useLogger()
log.mergeContext({ action: 'db_query' })
log.info('Executing database lookup…')
// 数据库查询逻辑…
return { id, name: 'John Doe' }
}
useLogger() 返回的跟 handler 里 ({ log }) 一样的 RequestScopedLogger。在请求外(启动时、worker、cron 任务)调用会返回 no-op,所有方法静默无效 —— 不会抛,只是日志被吞。
注意事项
- ALS 透传只在经过
createElogs插件.request()钩子的请求内生效。在请求生命周期外调度的后台任务会拿到 no-oplog,不输出任何东西。 - ALS 是 opt-in。默认行为只保留显式
store.loggerAPI —— 想要深调用栈日志的新代码再开 ALS。 useLogger()在 Elysia 2 下不可靠,生产深调用栈推荐用({ log })derive —— 见下文。
useLogger() 的限制(Elysia 2)
useLogger() 走的是 Node.js AsyncLocalStorage,在 createElogs 自己的 .request() 钩子里用 enterWith() 设置 scope。Elysia 2 内部对每个请求会包一层自己的 als.run() scope,所以 enterWith 设进去的值能透传到路由 handler 和 .afterHandle()。
前提 2 自己在路由 handler 之前不额外再起一次 als.run()。这是 Elysia 2 当前的实现细节,未来升级 Elysia 时需要回归测试 —— 一旦 Elysia 在中间再 als.run() 一次,useLogger() 在 await / setTimeout 之后的子 async 树就会拿到 no-op,日志静默丢失。
实践建议
| 场景 | 推荐方式 |
|---|---|
| 路由 handler 直接记日志 | ({ log }) derive ✅ |
一层 service / helper(await service.foo()) |
({ log }) 显式传下去 ✅ |
深调用栈(setTimeout / setImmediate / Worker) |
不要依赖 useLogger() ⚠️ |
| 请求外(模块 init / cron / 测试 setup) | 拿到 no-op,不要打日志 |
显式 vs ALS 的取舍:({ log }) 走的是 Elysia 自己的 derive 机制,不依赖 ALS —— 升级 Elysia 时不会受影响。代码上多一行解构,换的是长期可预测性。
相关 API
RequestScopedLogger—({ log })/useLogger()的返回类型useLogger— 从AsyncLocalStorage拿当前请求 loggercreateRequestScopedLogger— 工厂函数(测试 / 自定义中间件)createRequestContextStore— 底层RequestContextStore工厂mergeLogDataContext— 显式 vs 累积的 merge 逻辑RequestContextStore— 累积 / 读取 / 清空 context 的接口ContextKey—Request | object(WebSocket 也用它作 key)useAsyncLocalStorage—ElogsConfig上的开关