框架
Elysia
Automatic wide events, structured errors, drain adapters, enrichers, and tail sampling in Elysia applications.
evlog/elysia 插件会自动创建一个请求作用域的日志记录器,通过 log 在路由上下文和 useLogger() 中访问,当响应完成时发出一个广泛事件。
Prompt
Set up evlog in my Elysia app.
- Install evlog: pnpm add evlog
- Call initLogger({ env: { service: 'my-api' } }) at startup
- Alternatively, use evlog/vite plugin in vite.config.ts for auto-init (replaces initLogger)
- Import evlog from 'evlog/elysia' and add .use(evlog()) to your Elysia app
- Access the logger via the log property in route context destructuring
- Use useLogger() from 'evlog/elysia' to access the logger from anywhere
- Optionally pass drain, enrich, include, and keep options to evlog()
Docs: https://www.evlog.dev/frameworks/elysia
Adapters: https://www.evlog.dev/adapters
快速开始
1. 安装
Terminal
bun add evlog elysia
2. 初始化并注册插件
src/index.ts
import { Elysia } from 'elysia'
import { initLogger } from 'evlog'
import { evlog } from 'evlog/elysia'
initLogger({
env: { service: 'my-api' },
})
const app = new Elysia()
.use(evlog())
.get('/health', ({ log }) => {
log.set({ route: 'health' })
return { ok: true }
})
.listen(3000)
log 属性在所有路由处理程序中通过 Elysia 的 derive 自动可用。
广泛事件
通过在处理程序中逐步构建上下文。一个请求 = 一个广泛事件:
src/index.ts
app.get('/users/:id', async ({ log, params }) => {
const userId = params.id
log.set({ user: { id: userId } })
const user = await db.findUser(userId)
log.set({ user: { name: user.name, plan: user.plan } })
const orders = await db.findOrders(userId)
log.set({ orders: { count: orders.length, totalRevenue: sum(orders) } })
return { user, orders }
})
所有字段在请求完成时合并为一个单一广泛事件并发出:
Terminal output
14:58:15 INFO [my-api] GET /users/usr_123 200 in 12ms
├─ orders: count=2 totalRevenue=6298
├─ user: id=usr_123 name=Alice plan=pro
└─ requestId: 4a8ff3a8-...
useLogger()
使用 useLogger() 从调用堆栈的任何位置访问请求作用域日志记录器,而无需将上下文传递到服务层:
src/services/user.ts
import { useLogger } from 'evlog/elysia'
export async function findUser(id: string) {
const log = useLogger()
log.set({ user: { id } })
const user = await db.findUser(id)
log.set({ user: { name: user.name, plan: user.plan } })
return user
}
src/index.ts
import { findUser } from './services/user'
app.get('/users/:id', async ({ params }) => {
const user = await findUser(params.id)
return user
})
log 在上下文和 useLogger() 中都返回相同的日志记录器实例。useLogger() 使用 AsyncLocalStorage 在异步边界之间传播日志记录器。
错误处理
使用 createError 创建带有 why、fix 和 link 字段的结构化错误。Elysia 通过 onError 捕获抛出的错误:
src/index.ts
import { createError, parseError } from 'evlog'
app
.use(evlog())
.get('/checkout', ({ log }) => {
log.set({ cart: { items: 3, total: 9999 } })
throw createError({
message: 'Payment failed',
status: 402,
why: 'Card declined by issuer',
fix: 'Try a different payment method',
link: 'https://docs.example.com/payments/declined',
})
})
.onError(({ error, set }) => {
const parsed = parseError(error)
set.status = parsed.status
return {
message: parsed.message,
why: parsed.why,
fix: parsed.fix,
link: parsed.link,
}
})
错误被捕获并记录,同时包含自定义上下文和结构化错误字段:
Terminal output
14:58:20 ERROR [my-api] GET /checkout 402 in 3ms
├─ error: name=EvlogError message=Payment failed status=402
├─ cart: items=3 total=9999
└─ requestId: 880a50ac-...
配置
请参阅 配置参考 了解所有可用选项(initLogger、中间件选项、采样、静默模式等)。
排水和增强器
在插件选项中直接配置排水适配器和增强器:
src/index.ts
import { createAxiomDrain } from 'evlog/axiom'
import { createUserAgentEnricher } from 'evlog/enrichers'
const userAgent = createUserAgentEnricher()
app.use(evlog({
drain: createAxiomDrain(),
enrich: (ctx) => {
userAgent(ctx)
ctx.event.region = process.env.FLY_REGION
},
}))
管道(批处理和重试)
对于生产环境,使用 createDrainPipeline 包装您的适配器以批处理事件并在失败时重试:
src/index.ts
import type { DrainContext } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
import { createDrainPipeline } from 'evlog/pipeline'
const pipeline = createDrainPipeline<DrainContext>({
batch: { size: 50, intervalMs: 5000 },
retry: { maxAttempts: 3 },
})
const drain = pipeline(createAxiomDrain())
app.use(evlog({ drain }))
在服务器关闭时调用
drain.flush() 以确保所有缓冲事件已发送。有关所有选项,请参阅 管道文档。抽样
使用 keep 强制保留特定事件,无论头部抽样如何:
src/index.ts
app.use(evlog({
drain: createAxiomDrain(),
keep: (ctx) => {
if (ctx.duration && ctx.duration > 2000) ctx.shouldKeep = true
},
}))
路由过滤
使用 include 和 exclude 模式控制哪些路由被记录:
src/index.ts
app.use(evlog({
include: ['/api/**'],
exclude: ['/_internal/**', '/health'],
routes: {
'/api/auth/**': { service: 'auth-service' },
'/api/payment/**': { service: 'payment-service' },
},
}))
客户端日志记录
使用 evlog/http 从任何前端向 Elysia 服务器发送结构化日志。这适用于任何客户端框架(React、Vue、Svelte、vanilla JS)。
浏览器设置
client.ts
import { initLogger, log } from 'evlog'
import { createHttpLogDrain } from 'evlog/http'
const drain = createHttpLogDrain({
drain: { endpoint: '/v1/ingest' },
})
initLogger({ drain })
log.info({ action: 'page_view', path: location.pathname })
摄入端点
添加一个 POST 路由以接收来自浏览器的批处理 DrainContext[]:
src/index.ts
import type { DrainContext } from 'evlog'
app.post('/v1/ingest', async ({ body }) => {
const batch = body as DrainContext[]
for (const ctx of batch) {
console.log('[BROWSER]', JSON.stringify(ctx.event))
}
return new Response(null, { status: 204 })
})
请参阅完整的 HTTP 排水适配器 文档,了解批处理、重试、sendBeacon 回退和身份验证选项。
本地运行
Terminal
git clone https://github.com/hugorcd/evlog.git
cd evlog
bun install
bun run example:elysia
打开 http://localhost:3000 以探索交互式测试 UI。