---
title: "TanStack Start"
description: "自动广泛事件、结构化错误记录以及 TanStack Start API 路由和服务器函数中的日志记录。"
canonical_url: "https://evlog.zhcndoc.com/integrate/frameworks/tanstack-start"
---
# TanStack Start

> 自动广泛事件、结构化错误记录以及 TanStack Start API 路由和服务器函数中的日志记录。

TanStack Start 使用 [Nitro v3](https://evlog.zhcndoc.com/integrate/frameworks/nitro) 作为其服务器层，因此 evlog 通过 `evlog/nitro/v3` 模块集成。相同的基于插件的钩子系统同样适用。

<callout color="info" icon="i-lucide-info">

**TanStack Router vs TanStack Start**：TanStack Router 是一个客户端路由器，不需要服务器端日志记录。本页面介绍的是 **TanStack Start**，即全栈框架。如果你使用的是 SPA 模式下的 TanStack Router，请改看 [客户端日志](https://evlog.zhcndoc.com/use-cases/client-logging)。

</callout>

<prompt :actions="["copy","cursor","claude"]" description="在我的 TanStack Start 应用中设置 evlog" icon="i-custom-tanstack">

在我的 TanStack Start 应用中设置 evlog。

- 安装 evlog：pnpm add evlog
- 创建 nitro.config.ts 并引入 evlog/nitro/v3 模块，启用 experimental.asyncContext
- 在应用根目录创建 evlog.config.ts，其中包含 export default defineEvlog({ service: 'my-app' })；evlog/nitro/v3 模块会加载它，因此调用 evlog() 时无需传入选项
- 在根路由添加 evlogErrorHandler 中间件以获取结构化的错误响应
- 在路由处理器中通过 useRequest().context.log 访问记录器
- 使用 log.set() 累加上下文，使用 throw createError() 抛出结构化错误

文档：[https://www.evlog.dev/integrate/frameworks/tanstack-start](https://www.evlog.dev/integrate/frameworks/tanstack-start)
适配器：[https://www.evlog.dev/integrate/adapters/overview](https://www.evlog.dev/integrate/adapters/overview)

</prompt>

## 快速开始

从一个使用 `npm create @tanstack/start@latest` 创建的 TanStack Start 项目开始：

### 1. 安装

<code-group>

```bash [pnpm]
pnpm add evlog
```

```bash [bun]
bun add evlog
```

```bash [yarn]
yarn add evlog
```

```bash [npm]
npm install evlog
```

</code-group>

### 2. 创建 evlog.config.ts

```typescript [evlog.config.ts]
import { defineEvlog } from 'evlog'

export default defineEvlog({
  service: 'my-app',
})
```

所有选项都位于此文件中：采样、drain、`enrich` 和路由过滤器。`npx evlog init` 会为你写入它。请参阅[配置](https://evlog.zhcndoc.com/reference/configuration)。

### 3. 添加 `nitro.config.ts`

在项目根目录创建一个 `nitro.config.ts` 以注册 evlog 模块。你的 `vite.config.ts` 已经通过 CLI 添加了 `nitro()` 插件，因此无需修改。

```typescript [nitro.config.ts]
import { defineConfig } from 'nitro'
import evlog from 'evlog/nitro/v3'

export default defineConfig({
  experimental: {
    asyncContext: true,
  },
  modules: [
    evlog(),
  ],
})
```

evlog/nitro/v3 模块会找到 `evlog.config.ts` 并将其打包到服务器中，无需额外导入。启用 `asyncContext` 允许你通过 `useRequest()` 在调用栈任意位置访问请求作用域内的记录器。

### 4. 错误处理中间件

TanStack Start 拥有自己的错误处理层，会在 Nitro 之前运行。为确保 `throw createError()` 返回包含 `why`、`fix` 和 `link` 的正确 JSON 响应，请将 `evlogErrorHandler` 中间件添加到根路由：

```typescript [src/routes/__root.tsx]
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 会自动捕获每个请求作为一个广泛事件，包含方法、路径、状态和持续时间。

<callout color="info" icon="i-custom-vite">

**使用 Vite？** TanStack Start 基于 Vite。[`evlog/vite`](https://evlog.zhcndoc.com/reference/vite-plugin) 插件会从生产构建中移除 `log.debug()` 并注入源代码位置，请将其与 TanStack Start 插件一起添加到你的 `vite.config.ts` 中。

</callout>

## 广泛事件

启用 `experimental.asyncContext: true` 后，可通过 `useRequest()` 从 `nitro/context` 访问请求作用域记录器并逐步构建上下文：

```typescript [src/routes/api/hello.ts]
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 })
      },
    },
  },
})
```

所有字段会在请求完成时合并为一个广泛事件并发出：

```bash [终端输出]
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-...
```

<callout color="info" icon="i-lucide-info">

`useRequest()` 是 Nitro v3 的实验性特性，由 `AsyncLocalStorage` 提供支持。它在 Node.js 和 Bun 运行时中有效。

</callout>

## 后台工作

`log.fork()` 在此集成中不可用。`evlog/nitro/v3` 模块会将记录器附加到请求事件，但不会自行注册 `AsyncLocalStorage` 存储，因此即使启用了 `experimental.asyncContext`，也没有可供子广泛事件运行的作用域（该选项支持的是 Nitro 的 `useRequest()`，而不是 `log.fork()`）。请改为在处理器内部等待后台工作；在广泛事件发出后修改记录器会触发排放后警告。请参阅广泛事件中的[排放后](https://evlog.zhcndoc.com/learn/wide-events#after-emit-sealing-and-background-work)。

## 错误处理

使用 `createError` 来抛出包含 `why`、`fix` 和 `link` 字段的结构化错误：

<code-collapse>

```typescript [src/routes/api/checkout.ts]
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 })
      },
    },
  },
})
```

</code-collapse>

错误会被捕获并连同自定义上下文和结构化错误字段一起记录：

```bash [终端输出]
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` 从任意错误响应中提取结构化字段：

```tsx [src/routes/checkout.tsx]
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)
}
```

## 配置

查看 [配置参考](https://evlog.zhcndoc.com/reference/configuration) 了解所有可用选项（`initLogger`、中间件选项、采样、静默模式等）。所有选项都位于 `evlog.config.ts` 中。`nitro.config.ts` 中传给 `evlog()` 的选项会按键覆盖文件中的设置。文件中的 `drain`、`enrich` 和 `keep` 永远不会被覆盖：它们会与 `evlog:*` 钩子一起运行，因此每个事件都会到达两者。

## 路由过滤

通过文件中的 `include` 和 `exclude` 控制哪些路由被记录：

```typescript [evlog.config.ts]
export default defineEvlog({
  service: 'my-app',
  include: ['/api/**'],
  exclude: ['/_internal/**', '/health'],
  routes: {
    '/api/auth/**': { service: 'auth-service' },
    '/api/payment/**': { service: 'payment-service' },
  },
})
```

## 排放与增强器

在 `evlog.config.ts` 中设置 `drain` 和 `enrich`：

```typescript [evlog.config.ts]
import { defineEvlog } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
import { createUserAgentEnricher } from 'evlog/enrichers'

const userAgent = createUserAgentEnricher()

export default defineEvlog({
  service: 'my-app',
  drain: createAxiomDrain(),
  enrich: (ctx) => {
    userAgent(ctx)
  },
})
```

文件中的缓冲 drain（例如使用 `createDrainPipeline` 构建的 drain）会在服务器关闭时刷新。挂接 `evlog:drain` 和 `evlog:enrich` 的 Nitro 插件也仍然有效，并会与文件配置一起运行：

```typescript [server/plugins/evlog-drain.ts]
import { definePlugin } from 'nitro'
import { createAxiomDrain } from 'evlog/axiom'

export default definePlugin((nitroApp) => {
  const axiom = createAxiomDrain()

  nitroApp.hooks.hook('evlog:drain', axiom)
})
```

```typescript [server/plugins/evlog-enrich.ts]
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)
  })
})
```

<callout color="info" icon="i-lucide-info">

查看 [适配器](https://evlog.zhcndoc.com/integrate/adapters/overview) 和 [增强器](https://evlog.zhcndoc.com/use-cases/enrichers) 文档，了解所有可用的排放适配器和增强器。

</callout>

### 管道（批量与重试）

在生产环境中，使用 `createDrainPipeline` 包装你的排放器以实现事件批量发送和失败重试：

```typescript [server/plugins/evlog-drain.ts]
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)
})
```

<callout color="info" icon="i-lucide-info">

在服务器关闭时调用 `drain.flush()` 以确保所有缓冲事件都已发送。有关所有选项，请参阅 [管道文档](https://evlog.zhcndoc.com/extend/drain-pipeline)。

</callout>

## 采样

使用 `evlog:emit:keep` 钩子强制保留特定事件，无论头部采样如何：

```typescript [server/plugins/evlog-keep.ts]
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
  })
})
```

## 本地运行

```bash [Terminal]
git clone https://github.com/evloghq/evlog.git
cd evlog/examples/tanstack-start
pnpm install
pnpm run dev
```

打开 [http://localhost:3000](http://localhost:3000) 并访问 evlog 演示页面以测试 API 端点。

<card-group>
<card icon="i-simple-icons-github" title="源代码" to="https://github.com/evloghq/evlog/tree/main/examples/tanstack-start">

在 GitHub 上浏览完整的 TanStack Start 示例源代码。

</card>

::

## 后续步骤

深入集成 **TanStack Start**：

- [广泛事件](https://evlog.zhcndoc.com/learn/wide-events)：通过上下文分层设计全面事件
- [适配器](https://evlog.zhcndoc.com/integrate/adapters/overview)：将日志发送到 Axiom、Sentry、PostHog 等
- [采样](https://evlog.zhcndoc.com/learn/sampling)：通过头部和尾部采样控制日志量
- [结构化错误](https://evlog.zhcndoc.com/learn/structured-errors)：抛出包含 `why`、`fix` 和 `link` 字段的错误

</card-group>

---

- [源代码](https://github.com/evloghq/evlog/tree/main/examples/tanstack-start)


## Sitemap

See the full [sitemap](https://evlog.zhcndoc.com/sitemap.md) for all pages.
