---
title: "Hono"
description: "一个中间件会将类型化日志记录器放在 `c.get('log')` 上，在流式正文关闭后延迟事件，并在运行时提供该功能时使用 `waitUntil`"
canonical_url: "https://evlog.zhcndoc.com/integrate/frameworks/hono"
---
# Hono

> 一个中间件会将类型化日志记录器放在 `c.get('log')` 上，在流式正文关闭后延迟事件，并在运行时提供该功能时使用 `waitUntil`

`evlog/hono` 中间件会自动创建一个请求作用域内的日志记录器，可通过 `c.get('log')` 访问，也可以在调用栈更深处的任何位置使用 `useLogger()` 访问，并在响应完成时发出一个广泛事件。

<prompt :actions="["copy","cursor","claude"]" description="在我的 Hono 应用中设置 evlog" icon="i-simple-icons-hono">

在我的 Hono 应用中设置 evlog。

- 安装 evlog：pnpm add evlog
- 创建 evlog.config.ts，其中包含 export default defineEvlog({ service: 'my-api' })
- 在启动时导入 evlog.config.ts，并调用 initLogger(toLoggerConfig(config))
- 从 'evlog/hono' 导入 evlog 中间件和 EvlogVariables 类型
- 添加 app.use(evlog(toMiddlewareOptions(config)))，并使用 Hono<evlog-variables>

为应用指定类型

</evlog-variables>
- 在路由处理程序中通过 c.get('log') 访问日志记录器，或在嵌套函数中使用来自 'evlog/hono' 的 useLogger()
- 使用 log.set() 在整个请求过程中累积上下文
- 将 drain、enrich、include 和 keep 放入 evlog.config.ts；toMiddlewareOptions(config) 会将它们传给 evlog()

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

</prompt>

## 快速开始

### 1. 安装

<code-group>

```bash [pnpm]
pnpm add evlog hono @hono/node-server
```

```bash [bun]
bun add evlog hono @hono/node-server
```

```bash [yarn]
yarn add evlog hono @hono/node-server
```

```bash [npm]
npm install evlog hono @hono/node-server
```

</code-group>

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

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

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

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

### 3. 初始化并注册中间件

```typescript [src/index.ts]
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
import { initLogger, toLoggerConfig, toMiddlewareOptions } from 'evlog'
import { evlog, type EvlogVariables } from 'evlog/hono'
import config from '../evlog.config'

initLogger(toLoggerConfig(config))

const app = new Hono<EvlogVariables>()

app.use(evlog(toMiddlewareOptions(config)))

app.get('/health', (c) => {
  c.get('log').set({ route: 'health' })
  return c.json({ ok: true })
})

serve({ fetch: app.fetch, port: 3000 })
```

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

**使用 Vite？** [`evlog/vite` 插件](https://evlog.zhcndoc.com/reference/vite-plugin) 会从生产构建中移除 `log.debug()`，并注入源代码位置。它不会读取 `evlog.config.ts`，因此保留 `initLogger(toLoggerConfig(config))` 调用：该调用会在插件自动初始化后运行，并替代它。

</callout>

`EvlogVariables` 类型让你在所有路由处理程序中都能对 `c.get('log')` 进行类型访问。

## 广泛事件

在处理程序中逐步构建上下文。一个请求 = 一个广泛事件：

```typescript [src/index.ts]
app.get('/users/:id', async (c) => {
  const log = c.get('log')
  const userId = c.req.param('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 c.json({ user, orders })
})
```

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

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

## 在调用栈更深处访问日志记录器

`c.get('log')` 是路由处理程序中惯用的访问方式。当你深入几层（服务、存储库）后，在各处传递 `c` 会变得冗长。`useLogger()` 无需传递 `c`，即可解析出同一个请求日志记录器：

```typescript [src/services/payment.ts]
import { useLogger } from 'evlog/hono'

export async function chargeCard(amount: number) {
  const log = useLogger()
  log.set({ payment: { amount } })
}
```

两者返回的是同一个日志记录器，因此请选择在调用位置读起来更合适的方式。

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

`useLogger()` 基于 `AsyncLocalStorage`。在 Cloudflare Workers 上，这需要在 `wrangler.toml` 中启用
`nodejs_compat`（或 `nodejs_als`）兼容性标志。`c.get('log')`
无论是否启用该标志都能正常工作，因此无法启用该标志的部署仍可继续使用它。

</callout>

### 后台工作

**log.fork()** 会在分叉的日志记录器下运行工作，并发出其自身的广泛事件，该事件通过 `_parentRequestId` 与请求关联：

```typescript [src/index.ts]
app.post('/api/orders', (c) => {
  c.get('log').fork('send-receipt', async () => {
    await sendReceipt()
    useLogger().set({ email: { sent: true } })
  })
  return c.json({ ok: true })
})
```

如果你在响应之后调度异步工作但没有进行分叉，事件发出后的 **[evlog] 警告** 会帮助你注意到过时的 `set()` 调用。请参阅广泛事件中的[事件发出后](https://evlog.zhcndoc.com/learn/wide-events#after-emit-sealing-and-background-work)。

## 错误处理

使用 `createError` 创建带有 `why`、`fix` 和 `link` 字段的结构化错误：

```typescript [src/index.ts]
import { createError, parseError } from 'evlog'

app.get('/checkout', (c) => {
  const log = c.get('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',
  })
})
```

使用 `app.onError` 全局处理错误，以返回结构化的 JSON 响应：

```typescript [src/index.ts]
import type { ContentfulStatusCode } from 'hono/utils/http-status'

app.onError((error, c) => {
  c.get('log').error(error)
  const parsed = parseError(error)

  return c.json(
    {
      message: parsed.message,
      why: parsed.why,
      fix: parsed.fix,
      link: parsed.link,
    },
    parsed.status as ContentfulStatusCode,
  )
})
```

`parseError()` 将 `status` 类型定义为 `number`，而 Hono 的 `c.json()` 第二个参数期望 `ContentfulStatusCode`。此类型转换与运行时实际返回的类型匹配，并满足 TypeScript 的类型检查。

错误会被捕获并记录，同时包含自定义上下文和结构化错误字段：

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

## 配置

查看 [配置参考](https://evlog.zhcndoc.com/reference/configuration) 获取所有可用选项（`initLogger`、中间件选项、采样、静默模式等）。所有选项都位于 `evlog.config.ts` 中。

## 排水与增强器

在 `evlog.config.ts` 中设置 `drain` 和 `enrich`，它们会通过 `toMiddlewareOptions(config)` 到达应用。也可以直接传入中间件选项；内联键会替换文件中的同名键，而不是与其合并：

```typescript [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` 包装适配器以实现事件批处理和失败重试：

```typescript [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 }))
```

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

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

</callout>

## 抽样

使用 `keep` 强制保留特定事件，无论头部抽样如何：

```typescript [src/index.ts]
app.use(evlog({
  drain: createAxiomDrain(),
  keep: (ctx) => {
    if (ctx.duration && ctx.duration > 2000) ctx.shouldKeep = true
  },
}))
```

## 路由过滤

使用 `include` 和 `exclude` 模式控制哪些路由被记录：

```typescript [src/index.ts]
app.use(evlog({
  include: ['/api/**'],
  exclude: ['/_internal/**', '/health'],
  routes: {
    '/api/auth/**': { service: 'auth-service' },
    '/api/payment/**': { service: 'payment-service' },
  },
}))
```

## 客户端日志记录

使用 `evlog/http` 从任何前端向 Hono 服务器发送结构化日志。这适用于任何客户端框架（React、Vue、Svelte、原生 JS）。

### 浏览器设置

```typescript [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[]`：

```typescript [src/index.ts]
import type { DrainContext } from 'evlog'

app.post('/v1/ingest', async (c) => {
  const batch = await c.req.json<DrainContext[]>()
  for (const ctx of batch) {
    console.log('[BROWSER]', JSON.stringify(ctx.event))
  }
  return c.body(null, 204)
})
```

<callout color="neutral" icon="i-lucide-globe">

查看完整的 [HTTP drain](https://evlog.zhcndoc.com/extend/drain-pipeline) 适配器文档，了解批处理、重试、sendBeacon 回退和身份验证选项。

</callout>

## 本地运行

```bash [终端]
git clone https://github.com/evloghq/evlog.git
cd evlog
pnpm install
pnpm example hono
```

打开 [http://localhost:3000](http://localhost:3000) 探索交互式测试 UI。

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

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

</card>

::。

## 后续步骤

深入学习 **Hono** 集成：

- [广泛事件](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/hono)


## Sitemap

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