Usage
在 Elysia 应用中使用 Elogs
安装
bun add @eastgold15/elogs elysia@next
next 标签对应 Elysia 2 open beta。如果你还在 Elysia 1.4,请见 Elysia 2 支持。
基本用法
import { Elysia } from 'elysia'
import { createElogs } from '@eastgold15/elogs'
const app = new Elysia()
.use(createElogs())
.get('/', () => 'Hello World')
.listen(3000)
createElogs() 返回一个 Elysia 插件。Logger 挂在 Elysia store 上,可以通过 store.logger 访问;请求作用域的 log 也会派生到 handler context 上(不需要再手动传 request)。插件同时注册 .request、.afterHandle、.error 和 .setup 钩子,你不需要手动接线。
预设
三个 preset 一行设好环境默认。显式 config 字段永远覆盖 preset。
createElogs({ preset: 'dev' }) // 美化 console + 启动 banner
createElogs({ preset: 'prod' }) // JSON console + autoRedact + requestId
createElogs({ preset: 'json' }) // 极简 JSON console,无附加
也可以通过 registerPreset('staging', { ... }) 注册自己的预设。完整默认值见 Presets。
配置
最小化
createElogs({
config: {
showStartupMessage: true,
ip: true,
autoRedact: false,
},
})
请求作用域 context
在请求过程中累积字段,最终合并到 access log 的 context 下:
.get('/users/:id', ({ request, store, params }) => {
store.logger.mergeContext(request, { userId: params.id })
return { ok: true }
})
完整 API 含深调用栈用的 useLogger(),见 Request context。
Request ID
开启 X-Request-Id 头透传用 requestId: true。插件入口读这个头,缺失时自动生成,出口回显——包括错误响应。
createElogs({ config: { requestId: true } })
见 Request ID。
自定义日志格式
通过 customLogFormat 自定义 access log:
createElogs({
config: {
customLogFormat: '{now} {level} {duration}ms {method} {pathname} {status} {requestId}',
},
})
可用的占位符:
| 占位符 | 描述 | 示例 |
|---|---|---|
{now} |
当前时间戳 | 2026-08-15 10:00:00 |
{epoch} |
Unix 时间戳 | 1734729600 |
{level} |
日志级别(DEBUG、INFO、WARNING、ERROR) |
INFO |
{duration} |
请求耗时(已格式化) | 12ms、1.5s |
{method} |
HTTP 方法 | GET |
{pathname} |
请求路径(别名:{path});logQueryParams: true 时带 query |
/users |
{query} |
原始 query 字符串 | ?id=123 |
{status} |
响应状态码 | 200 |
{statusText} |
HTTP 状态文本 | Not Found |
{message} |
自定义消息 | User profile accessed |
{icon} |
Elogs 狐狸 🦊(颜色 + TTY 时为级别色块) |
🦊 |
{speed} |
慢请求徽章(耗时 ≥ verySlowThreshold) |
⚡ slow |
{service} |
config.service 服务名前缀 |
[my-api] |
{ip} |
客户端 IP 地址 | 127.0.0.1 |
{context} |
context 的 JSON 表示(树关掉或空时显示在主行) | {"id":1} |
{requestId} |
X-Request-Id 值(未开启时为空) |
8c2f… |
日志过滤
logFilter.level 接受单个级别或数组:
createElogs({
config: {
logFilter: { level: ['ERROR', 'WARNING'] },
},
})
根级有一个等价的 logLevel 简写:
createElogs({ logLevel: ['ERROR', 'WARNING'] })
见 Log Levels。
Pino 集成
Elogs 由 Pino 驱动。Pino 实例可以通过 store.pino 访问,也可以从请求作用域 log 拿到:
app.get('/users/:id', async ({ log, params, store }) => {
log.info({ userId: params.id, action: 'view_profile' }, 'profile accessed')
// 或者想用原始 Pino:store.pino.info({ ... }, '...')
return { user: 'data' }
})
配置 Pino 选项:
createElogs({
config: {
pino: {
level: 'debug',
prettyPrint: true,
base: { service: 'my-api' },
},
},
})
Pino 在首次访问时按需构造 —— 错误的 Pino 选项只在真正 log 时才报错。如果想启动时立即校验,把 config.pino 设为真值,proxy 就会立刻初始化。
见 Pino。
文件日志
createElogs({
config: {
logFilePath: './logs/app.log',
logRotation: {
maxSize: '10m',
interval: '1d',
maxFiles: '7d',
compress: true,
},
},
})
文件 sink 通过 queueMicrotask 批写、缓存文件句柄,跨文件 rotation 用 keyed mutex 串行,不同文件之间不会互相阻塞。文件创建时默认 0o600(文件)和 0o700(目录);可以用 logFileMode / logDirMode 覆盖。
见 File Logging 和 Log Rotation。
Transports
const consoleTransport = {
log: (level, message, meta) => {
console.log(`[${level}] ${message}`, meta)
},
}
createElogs({
config: {
transports: [consoleTransport],
},
})
transport 抛出的同步异常和异步 reject 会被捕获并打印到 console.error,同一 transport 5s 窗口内只输出一次,坏的 target 不会拖慢请求路径。
见 Transports。
OpenTelemetry
bun add @opentelemetry/api
import { injectTraceContext } from '@eastgold15/elogs/otel'
app.request(({ request, store }) => {
injectTraceContext(store.logger, request)
})
当存在 active span 时,trace_id 和 span_id 会合并进请求 context 包,出现在 access log 上。Elogs 不启动 OpenTelemetry SDK —— 你需要自己装 tracer provider 和 instrumentation。
AI SDK 指标
import { mergeAIMetrics } from '@eastgold15/elogs/ai'
app.post('/chat', async ({ request, store }) => {
// 在 AI SDK 调用之后:
mergeAIMetrics(store.logger, request, {
model: 'claude-sonnet',
provider: 'anthropic',
inputTokens: 1200,
outputTokens: 400,
totalTokens: 1600,
msToFinish: 2300,
})
return { ok: true }
})
access log 的 context.ai 字段沿用 evlog wide event 形态,方便两边仪表盘直接对比迁移。
见 AI SDK。
数据库错误翻译(Drizzle)
在 createElogs 上开 autoTranslate: { db: 'drizzle' },单点 onError 钩子就会把 Drizzle 抛出的 driver 错误(唯一约束、外键违反、连接失败等)翻译成合适的 HTTP 状态码来决定日志级别。翻译只影响日志输出,不劫持错误响应格式——原 error 继续传播,你的 .error("DrizzleError", ...) / Elysia 默认 application/problem+json 响应都按原逻辑走。
createElogs({
autoTranslate: { db: 'drizzle' },
config: { /* ... */ },
})
也可以手动在 try/catch 里调 translateDrizzleError(error)(来自 @eastgold15/elogs/translator)。详见 Database errors。
AsyncLocalStorage
深调用栈(服务层、helper、请求里的子任务)想拿到请求作用域 logger,不用逐层传 request,开启 ALS 即可:
createElogs({ config: { useAsyncLocalStorage: true } })
// 在任意位置:
import { useLogger } from '@eastgold15/elogs'
const log = useLogger()
log.mergeContext({ stage: 'db' })
log.info('query started')
输出控制
createElogs({
config: {
disableInternalLogger: false, // console 输出
disableFileLogging: false, // logFilePath 输出
useTransportsOnly: false, // 同时禁用上面两个,只走 transport
transports: [/* ... */],
},
})
路由拆分模式
跟 Elysia 2 推荐写法一致,把每个子路由写成 <App extends CreateElogs>(app: App) => app 的高阶函数,TypeScript 就能精确推断 store.logger / log 的类型:
import type { CreateElogs } from '@eastgold15/elogs'
import { createElogs } from '@eastgold15/elogs'
import { Elysia } from 'elysia'
const chatRouter = <App extends CreateElogs>(app: App) =>
app.post('/chat', ({ request, store }) => {
store.logger.info(request, 'chat received')
return { ok: true }
})
const app = new Elysia()
.use(createElogs())
.use(chatRouter)
.listen(3000)
参考 apps/elysia/src/routers/ 下的真实用法。
示例
生产配置
import { Elysia } from 'elysia'
import { createElogs } from '@eastgold15/elogs'
const app = new Elysia().use(
createElogs({
preset: 'prod',
autoTranslate: { db: 'drizzle' },
config: {
service: 'my-api',
logFilePath: './logs/production.log',
logRotation: { maxSize: '100m', interval: '1d', maxFiles: '30d', compress: true },
logFilter: { level: ['ERROR', 'WARNING'] },
pino: { level: 'info', redact: ['password', 'token', 'apiKey'] },
},
})
)
开发配置
import { Elysia } from 'elysia'
import { createElogs } from '@eastgold15/elogs'
const app = new Elysia().use(
createElogs({
preset: 'dev',
config: {
service: 'my-api',
pino: { level: 'debug', prettyPrint: true },
},
})
)