TanStack Start
TanStack Start 使用 Nitro v3 作为其服务器层,因此 evlog 通过 evlog/nitro/v3 模块进行集成。相同的基于插件的钩子系统也适用。
在 TanStack Start 应用中设置 evlog。
- 安装 evlog:pnpm add evlog
- 创建 nitro.config.ts 并引入 evlog/nitro/v3 模块,启用 experimental.asyncContext
- 配置 env.service 作为你的应用名称
- 在根路由添加 evlogErrorHandler 中间件以获取结构化的错误响应
- 在路由处理器中通过 useRequest().context.log 访问记录器
- 使用 log.set() 累加上下文,使用 throw createError() 抛出结构化错误
文档:https://www.evlog.dev/frameworks/tanstack-start
适配器:https://www.evlog.dev/adapters
快速开始
从一个使用 npm create @tanstack/start@latest 创建的 TanStack Start 项目开始:
1. 安装
bun add evlog
2. 添加 nitro.config.ts
在项目根目录创建一个 nitro.config.ts 以注册 evlog 模块。你的 vite.config.ts 已经通过 CLI 添加了 nitro() 插件,因此无需修改。
import { defineConfig } from 'nitro'
import evlog from 'evlog/nitro/v3'
export default defineConfig({
experimental: {
asyncContext: true,
},
modules: [
evlog({
env: { service: 'my-app' },
}),
],
})
启用 asyncContext 允许你通过 useRequest() 在调用栈任意位置访问请求作用域内的记录器。
3. 错误处理中间件
TanStack Start 拥有自己的错误处理层,会在 Nitro 之前运行。为确保 throw createError() 返回包含 why、fix 和 link 的正确 JSON 响应,请将 evlogErrorHandler 中间件添加到根路由:
import { createRootRoute } from '@tanstack/react-router'
import { createMiddleware } from '@tanstack/react-start'
import { evlogErrorHandler } from 'evlog/nitro/v3'
export const Route = createRootRoute({
server: {
middleware: [createMiddleware().server(evlogErrorHandler)],
},
// ... head, shellComponent, etc.
})
至此完成。evlog 会自动捕获每个请求作为一个广泛事件,包含方法、路径、状态和持续时间。
evlog/vite 可在生产构建中剥离 log.debug() 并注入源码位置,请将其添加到 vite.config.ts 以及 TanStack Start 插件中。广泛事件
启用 experimental.asyncContext: true 后,可通过 useRequest() 从 nitro/context 访问请求作用域记录器并逐步构建上下文:
import { createFileRoute } from '@tanstack/react-router'
import { useRequest } from 'nitro/context'
import type { RequestLogger } from 'evlog'
export const Route = createFileRoute('/api/hello')({
server: {
handlers: {
GET: async () => {
const req = useRequest()
const log = req.context.log as RequestLogger
log.set({ user: { id: 'user_123', plan: 'pro' } })
log.set({ action: 'fetch_profile' })
log.set({ cache: { hit: true, ttl: 3600 } })
return Response.json({ ok: true })
},
},
},
})
所有字段会在请求完成时合并为一个广泛事件并发出:
14:58:15 INFO [my-app] GET /api/hello 200 in 52ms
├─ cache: hit=true ttl=3600
├─ action: fetch_profile
├─ user: id=user_123 plan=pro
└─ requestId: 4a8ff3a8-...
useRequest() 是 Nitro v3 的实验性特性,由 AsyncLocalStorage 提供支持。它在 Node.js 和 Bun 运行时中有效。错误处理
使用 createError 来抛出包含 why、fix 和 link 字段的结构化错误:
import { createFileRoute } from '@tanstack/react-router'
import { useRequest } from 'nitro/context'
import { createError } from 'evlog'
import type { RequestLogger } from 'evlog'
export const Route = createFileRoute('/api/checkout')({
server: {
handlers: {
POST: async ({ request }) => {
const req = useRequest()
const log = req.context.log as RequestLogger
const body = await request.json()
log.set({ user: { id: body.userId, plan: body.plan } })
log.set({ cart: { items: body.items, total: body.total } })
const result = await chargeCard(body)
if (!result.success) {
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',
})
}
return Response.json({ success: true, orderId: result.orderId })
},
},
},
})
错误会被捕获并连同自定义上下文和结构化错误字段一起记录:
14:58:20 ERROR [my-app] POST /api/checkout 402 in 104ms
├─ error: name=EvlogError message=Payment failed status=402
├─ cart: items=3 total=9999
├─ user: id=user_123 plan=pro
└─ requestId: 880a50ac-...
在客户端解析错误
使用 parseError 从任意错误响应中提取结构化字段:
import { parseError } from 'evlog'
try {
const res = await fetch('/api/checkout', {
method: 'POST',
body: JSON.stringify({ userId: 'user_123' }),
})
if (!res.ok) throw { data: await res.json(), status: res.status }
} catch (error) {
const { message, status, why, fix, link } = parseError(error)
}
配置
请参考 配置参考文档 查看所有可用选项(initLogger、中间件选项、采样、静默模式等)。
路由过滤
通过模块选项中的 include 和 exclude 控制哪些路由被记录:
import { defineConfig } from 'nitro'
import evlog from 'evlog/nitro/v3'
export default defineConfig({
experimental: { asyncContext: true },
modules: [
evlog({
env: { service: 'my-app' },
include: ['/api/**'],
exclude: ['/_internal/**', '/health'],
routes: {
'/api/auth/**': { service: 'auth-service' },
'/api/payment/**': { service: 'payment-service' },
},
}),
],
})
排放与增强器
由于 TanStack Start 使用 Nitro v3,可通过 Nitro 插件配置排放和增强器。创建 server/plugins/ 目录并注册钩子:
import { definePlugin } from 'nitro'
import { createAxiomDrain } from 'evlog/axiom'
export default definePlugin((nitroApp) => {
const axiom = createAxiomDrain()
nitroApp.hooks.hook('evlog:drain', axiom)
})
import { definePlugin } from 'nitro'
import { createUserAgentEnricher, createRequestSizeEnricher } from 'evlog/enrichers'
export default definePlugin((nitroApp) => {
const enrichers = [createUserAgentEnricher(), createRequestSizeEnricher()]
nitroApp.hooks.hook('evlog:enrich', (ctx) => {
for (const enricher of enrichers) enricher(ctx)
})
})
管道(批量与重试)
在生产环境中,使用 createDrainPipeline 包装你的排放器以实现事件批量发送和失败重试:
import { definePlugin } from 'nitro'
import type { DrainContext } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
import { createDrainPipeline } from 'evlog/pipeline'
export default definePlugin((nitroApp) => {
const pipeline = createDrainPipeline<DrainContext>({
batch: { size: 50, intervalMs: 5000 },
retry: { maxAttempts: 3 },
})
const drain = pipeline(createAxiomDrain())
nitroApp.hooks.hook('evlog:drain', drain)
})
drain.flush() 以确保所有缓冲事件被发送。更多选项请参考 管道文档。采样
使用 evlog:emit:keep 钩子强制保留特定事件,无论头部采样如何:
import { definePlugin } from 'nitro'
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:emit:keep', (ctx) => {
if (ctx.duration && ctx.duration > 2000) ctx.shouldKeep = true
if (ctx.status && ctx.status >= 500) ctx.shouldKeep = true
})
})
本地运行
git clone https://github.com/hugorcd/evlog.git
cd evlog/examples/tanstack-start
bun install
bun run dev
打开 http://localhost:3000 并访问 evlog 演示页面以测试 API 端点。