Skip to content
Elogs
Esc
navigateopen⌘Jpreview
On this page

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 对象,合并 userIdcartTotal(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.debugstore.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-op log,不输出任何东西。
  • ALS 是 opt-in。默认行为只保留显式 store.logger API —— 想要深调用栈日志的新代码再开 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

Last updated on August 15, 2026

Was this page helpful?