---
title: "快速开始"
description: "几分钟内上手 evlog。学习日志 API、为广泛事件创建 createLogger、处理请求的 useLogger，以及结构化错误。"
canonical_url: "https://evlog.zhcndoc.com/start/quick-start"
---
# 快速开始

> 几分钟内上手 evlog。学习日志 API、为广泛事件创建 createLogger、处理请求的 useLogger，以及结构化错误。

本指南介绍你在使用 evlog 时最常用的核心 API。更喜欢通过运行一个完整的服务器来学习？请按照 [Node.js 中的结构化日志记录](https://evlog.zhcndoc.com/use-cases/structured-logging-nodejs)进行操作，然后回到这里查看 API 概览。

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

在 Nuxt 中，evlog 会**自动导入** `useLogger`、`log` 和 `parseError`。`createError` 从 `evlog` 中导入：自动导入的那个是 Nuxt 自带的，并且它会丢弃 `why`、`fix` 和 `link`。

</callout>

<prompt :actions="["copy","cursor","claude"]" description="2 分钟内让 evlog 运行起来" icon="i-lucide-zap">

在我的项目中，2 分钟内让 evlog 运行起来。

- 安装 evlog：pnpm add evlog
- 创建 evlog.config.ts，其中包含 export default defineEvlog({ service: 'my-app' })
- 检测我的框架并接入相应的集成，使其加载 evlog.config.ts
- 在路由处理程序中添加一次 useLogger(event) 调用，并配合 log.set({ ... })
- 针对无效输入情况抛出一个 createError({ message, status, why, fix })
- 在本地触发该路由，并确认终端只打印一条广泛事件

文档： [https://www.evlog.dev/start/quick-start](https://www.evlog.dev/start/quick-start)
框架： [https://www.evlog.dev/integrate/frameworks/overview](https://www.evlog.dev/integrate/frameworks/overview)

</prompt>

## 在 evlog.config.ts 中一次配置

所有选项都位于应用根目录的一个文件中。`npx evlog init` 会为你写入该文件：

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

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

Nuxt、Nitro 和 TanStack Start 会自动加载它；其他框架只需按各自的[框架指南](https://evlog.zhcndoc.com/integrate/frameworks/overview)所示用一行代码导入。所有选项请参阅[配置](https://evlog.zhcndoc.com/reference/configuration)。

## log（简单日志记录）

使用 evlog 最简单的方式。随时随地发送结构化日志，无需担心日志处理：

<code-group>

```typescript [服务端]
import { log } from 'evlog'

log.info('auth', 'User logged in')
log.error({ action: 'payment', error: 'card_declined' })
log.warn('cache', 'Cache miss')
```

```bash [输出]
10:23:45.612 [auth] User logged in
10:23:45.613 ERROR [my-app] action=payment error=card_declined
10:23:45.614 [cache] Cache miss
```

</code-group>

两种调用方式：

- **标签式**：`log.info('tag', 'message')`，适合快速、易读的控制台输出
- **结构化**：`log.info({ key: value })`，适合传入 drain 管道的丰富事件

如果你从 pino 或 consola 迁移，默认导入就是无需设置行的同一个 `log` API：`import logger from 'evlog'`，然后 `logger.info('auth', 'User logged in')` 会立即打印。对象形式只接收事件，因此 pino 的 `logger.info(obj, msg)` 会变成 `logger.info(obj)`。无设置路径在生产环境中有代价：没有 `initLogger()` 时，事件不会脱敏，也不会发送到 drain；第一次发出的事件会记录一次性警告，指向 `initLogger({ drain })`。

<callout color="neutral" icon="i-lucide-arrow-right">

查看完整的[简单日志记录](https://evlog.zhcndoc.com/learn/simple-logging)指南，了解所有模式和 drain 集成。

</callout>

## createLogger（广泛事件）

当你需要在操作的多个步骤中**累积上下文**时，无论是在脚本、后台作业、队列工作者还是工作流中，请使用 `createLogger`：

<code-group>

```typescript [scripts/sync-job.ts]
import { initLogger, createLogger, toLoggerConfig } from 'evlog'
import config from '../evlog.config'

initLogger(toLoggerConfig(config))

const log = createLogger({ jobId: job.id, queue: 'emails' })

log.set({ batch: { size: 50 } })
log.set({ batch: { processed: 50 } })
log.emit()
```

```bash [输出（美化后）]
10:23:45.612 信息 [sync-worker] 1204ms
  ├─ jobId: job_abc123
  ├─ queue: emails
  └─ batch: size=50 processed=50
```

</code-group>

`createLogger()` 接受一个纯对象作为初始上下文。它会返回一个带有 `set`、`error`、`info`、`warn`、`emit` 和 `getContext` 方法的日志记录器。

对于 HTTP 请求上下文，请使用 `createRequestLogger()`，它会自动预填充 `method`、`path` 和 `requestId`：

```typescript [src/worker.ts]
import { createRequestLogger } from 'evlog'

const log = createRequestLogger({ method: 'POST', path: '/api/checkout' })
```

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

使用 `createLogger` 和 `createRequestLogger` 时，必须手动调用 `log.emit()`。在框架集成中，此操作会自动完成。

</callout>

## useLogger（获取请求日志记录器）

在使用框架集成（Nuxt、Hono、Express 等）时，中间件会在请求开始时自动创建一个广泛事件日志记录器，并在响应结束时发出它。`useLogger(event)` 从请求上下文中获取该日志记录器：

<framework-tabs>

```typescript [Nuxt]server/api/checkout.post.ts
import { useLogger } from 'evlog'

export default defineEventHandler(async (event) => {
  const log = useLogger(event)

  log.set({ user: { id: 1, plan: 'pro' } })
  log.set({ cart: { items: 3, total: 9999 } })

  const order = await processCheckout()
  log.set({ orderId: order.id })

  return { success: true, orderId: order.id }
})
```

```typescript [Nitro]routes/api/checkout.post.ts
import { defineHandler } from 'nitro/h3'
import { useLogger } from 'evlog/nitro/v3'

export default defineHandler(async (event) => {
  const log = useLogger(event)

  log.set({ user: { id: 1, plan: 'pro' } })
  log.set({ cart: { items: 3, total: 9999 } })

  const order = await processCheckout()
  log.set({ orderId: order.id })

  return { success: true, orderId: order.id }
})
```

```typescript [Next.js]app/api/checkout/route.ts
import { withEvlog, useLogger } from '@/lib/evlog'

export const POST = withEvlog(async () => {
  const log = useLogger()

  log.set({ user: { id: 1, plan: 'pro' } })
  log.set({ cart: { items: 3, total: 9999 } })

  const order = await processCheckout()
  log.set({ orderId: order.id })

  return Response.json({ success: true, orderId: order.id })
})
```

```typescript [Hono]src/index.ts
app.post('/api/checkout', async (c) => {
  const log = c.get('log')

  log.set({ user: { id: 1, plan: 'pro' } })
  log.set({ cart: { items: 3, total: 9999 } })

  const order = await processCheckout()
  log.set({ orderId: order.id })

  return c.json({ success: true, orderId: order.id })
})
```

```typescript [Express]src/index.ts
app.post('/api/checkout', async (req, res) => {
  const log = req.log

  log.set({ user: { id: 1, plan: 'pro' } })
  log.set({ cart: { items: 3, total: 9999 } })

  const order = await processCheckout()
  log.set({ orderId: order.id })

  return res.json({ success: true, orderId: order.id })
})
```

</framework-tabs>

```bash [输出（美化后）]
10:23:45.612 INFO [my-app] POST /api/checkout 200 in 234ms
  ├─ user: id=1 plan=pro
  ├─ cart: items=3 total=9999
  └─ orderId: ord_abc123
```

<callout color="success" icon="i-lucide-check">

`useLogger` 不会创建日志记录器；框架中间件已经创建好了。它只是从事件上下文中检索出来，以便你可以通过 `set()` 添加数据。

</callout>

### 何时使用什么

<table>
<thead>
  <tr>
    <th>
      使用 <code>
        log
      </code>
    </th>
    
    <th>
      使用 <code>
        createLogger()
      </code>
      
       / <code>
        createRequestLogger()
      </code>
    </th>
    
    <th>
      使用 <code>
        useLogger(event)
      </code>
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      快速的一次性事件
    </td>
    
    <td>
      脚本、作业、工作者、队列、无框架的 HTTP 操作
    </td>
    
    <td>
      使用框架集成的 API 路由
    </td>
  </tr>
  
  <tr>
    <td>
      无需累积上下文
    </td>
    
    <td>
      在操作过程中累积上下文
    </td>
    
    <td>
      获取自动管理的广泛事件
    </td>
  </tr>
  
  <tr>
    <td>
      客户端日志记录
    </td>
    
    <td>
      每个操作一个广泛事件
    </td>
    
    <td>
      访问自动管理的广泛事件
    </td>
  </tr>
</tbody>
</table>

### 服务识别

在多服务架构中，使用以下方式区分日志属于哪个服务：

#### 基于路由的配置

在 `evlog.config.ts` 中按路由模式映射服务名称：

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

export default defineEvlog({
  service: 'default-service',
  routes: {
    '/api/auth/**': { service: 'auth-service' },
    '/api/payment/**': { service: 'payment-service' },
    '/api/booking/**': { service: 'booking-service' },
  },
})
```

Nuxt、Nitro 和 TanStack Start 会自动加载它；其他框架通过 `toMiddlewareOptions(config)` 传入。路由映射需要框架集成：`initLogger` 不接受 `routes`。

匹配这些路由模式的日志会自动包含配置的服务名称：

```bash [输出]
21:57:10.442 INFO [auth-service] POST /api/auth/login 200 in 1ms
  ├─ requestId: 88ced16a-bef2-4483-86cb-2b4fb677ea52
  ├─ user: id=user_123 email=demo@example.com
  └─ action: login
```

#### 显式服务参数

使用第二个参数覆盖特定路由的服务名称：

```typescript [server/api/legacy/process.post.ts]
import { useLogger } from 'evlog'

export default defineEventHandler((event) => {
  const log = useLogger(event, 'legacy-service')

  log.set({ action: 'process_legacy_request' })

  return { success: true }
})
```

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

**优先级顺序：** 显式 `useLogger` 参数 > 路由配置 > `evlog.config.ts` 中的 `service` > 从环境自动检测

</callout>

## createError（结构化错误）

使用 `createError()` 抛出带有可操作上下文的错误：

<code-group>

```typescript [services/checkout.ts]
import { createError } from 'evlog'

throw createError({
  message: '支付失败',
  status: 402,
  why: '卡被发卡行拒绝',
  fix: '尝试其他支付方式',
  link: 'https://docs.example.com/payments/declined',
})
```

```json [响应]
{
  "statusCode": 402,
  "message": "支付失败",
  "data": {
    "why": "卡被发卡行拒绝",
    "fix": "尝试其他支付方式",
    "link": "https://docs.example.com/payments/declined"
  }
}
```

</code-group>

### 错误字段

<table>
<thead>
  <tr>
    <th>
      字段
    </th>
    
    <th>
      必填
    </th>
    
    <th>
      描述
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        message
      </code>
    </td>
    
    <td>
      是
    </td>
    
    <td>
      发生了什么（面向用户）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        status
      </code>
    </td>
    
    <td>
      否
    </td>
    
    <td>
      HTTP 状态码（默认：500）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        why
      </code>
    </td>
    
    <td>
      否
    </td>
    
    <td>
      技术原因（用于调试）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        fix
      </code>
    </td>
    
    <td>
      否
    </td>
    
    <td>
      可操作的解决方案
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        link
      </code>
    </td>
    
    <td>
      否
    </td>
    
    <td>
      更多信息文档 URL
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        cause
      </code>
    </td>
    
    <td>
      否
    </td>
    
    <td>
      原始错误（如果包装）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        internal
      </code>
    </td>
    
    <td>
      否
    </td>
    
    <td>
      仅用于日志和广泛事件的后端字段 — 永远不会包含在 HTTP JSON 或 <code>
        parseError()
      </code>
      
       中
    </td>
  </tr>
</tbody>
</table>

### 前端集成

使用 `parseError()` 在客户端提取所有错误字段：

```typescript [composables/useCheckout.ts]
import { parseError } from 'evlog'

export async function checkout(cart: Cart) {
  try {
    await $fetch('/api/checkout', { method: 'POST', body: cart })
  } catch (err) {
    const error = parseError(err)

    toast.add({
      title: error.message,
      description: error.why,
      color: 'error',
      actions: error.link
        ? [{ label: '了解更多', onClick: () => window.open(error.link) }]
        : undefined,
    })

    if (error.fix) {
      console.info(`修复：${error.fix}`)
    }
  }
}
```

## log（客户端）

相同的 `log` API 在客户端也适用，输出到浏览器控制台：

<code-group>

```vue [组件/CheckoutButton.vue]
<script setup lang="ts">
async function handleCheckout() {
  log.info('checkout', '用户已发起结算')

  try {
    await $fetch('/api/checkout', { method: 'POST' })
    log.info({ action: 'checkout', status: 'success' })
  } catch (err) {
    log.error({ action: 'checkout', error: 'failed' })
  }
}
</script>
```

```typescript [composables/useAnalytics.ts]
export function useAnalytics() {
  function trackEvent(event: string, data?: Record<string, unknown>) {
    log.info('analytics', `事件: ${event}`)
    if (data) {
      log.debug({ event, ...data })
    }
  }

  return { trackEvent }
}
```

</code-group>

<callout color="neutral" icon="i-lucide-arrow-right">

参阅[客户端日志记录](https://evlog.zhcndoc.com/use-cases/client-logging)，了解 drain 配置、身份上下文和浏览器设置。

</callout>

## 第一天需要用到其中多少内容？

两个调用。此页面上的其他所有内容都可以在你需要时使用，在你还没有使用它们时也不会破坏任何东西。

`log.info()` 的行为类似于 `console.log`，因此第一次提交只是查找并替换，不会改变行为。从这里开始，最能立即带来价值的是两个调用：`log.set()` 会向已经在处理中的事件添加字段，而 `createError()` 会为失败附加 `why` 和 `fix`，而不是只有一段堆栈信息。

一次迁移一个处理程序。一个仍在调用 `console.log` 的路由与另一个使用 `log.set()` 的路由并存，是正常的过渡状态，并不是错误状态：它们写入同一个位置，彼此也互不知晓。选择你上次在生产环境中调试过的处理程序，从那里开始，或者让 [`evlog map`](https://evlog.zhcndoc.com/cli/map) 为你进行排序。

类型化字段、目录、采样和 drains 都是可选启用的；当你感到缺少它们时再使用它们，而不是在安装它们的那一天就全部采用，每一项都更值得如此使用。

## 后续步骤

- [`evlog map`](https://evlog.zhcndoc.com/cli/map)：查看你刚刚解锁的内容，评估覆盖率，并找到仍然未覆盖的入口（`npx evlog map`）
- [日志概览](https://evlog.zhcndoc.com/learn/overview)：了解全部三种日志记录模式
- [广泛事件](https://evlog.zhcndoc.com/learn/wide-events)：学习如何设计有效的广泛事件
- [类型化字段](https://evlog.zhcndoc.com/learn/typed-fields)：为广泛事件添加编译时类型安全
- [结构化错误](https://evlog.zhcndoc.com/learn/structured-errors)：掌握使用 evlog 进行错误处理的方法
- [最佳实践](https://evlog.zhcndoc.com/reference/best-practices)：安全指南和生产环境技巧

---

- [日志概览](https://evlog.zhcndoc.com/learn/overview)
- [结构化错误](https://evlog.zhcndoc.com/learn/structured-errors)


## Sitemap

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