---
title: "NestJS"
description: "`EvlogModule.forRoot()` 会在 NestJS 应用中注册中间件、异常过滤器和 `useLogger()`，并在需要时支持异步配置。"
canonical_url: "https://evlog.zhcndoc.com/integrate/frameworks/nestjs"
---
# NestJS

> `EvlogModule.forRoot()` 会在 NestJS 应用中注册中间件、异常过滤器和 `useLogger()`，并在需要时支持异步配置。

`evlog/nestjs` 模块提供 `EvlogModule.forRoot()`，它会注册一个全局中间件，创建一个请求作用域的日志记录器（可通过 `useLogger()` 或 `req.log` 访问），并在响应完成时发送一个广泛事件。

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

在我的 NestJS 应用中设置 evlog。

- 安装 evlog：pnpm add evlog
- 创建 evlog.config.ts，其中包含 export default defineEvlog({ service: 'my-api' })
- 从 'evlog/nestjs' 导入 EvlogModule，并将 EvlogModule.forRoot(toMiddlewareOptions(config)) 添加到 AppModule 的导入中
- 在 main.ts 中调用 initLogger(toLoggerConfig(config))
- 全局中间件会为每个请求自动创建一个请求作用域的日志记录器
- 在任意控制器或服务中使用 useLogger() 访问日志记录器
- 使用 log.set() 累积上下文，使用 throw createError() 发送结构化错误
- 可选地向 forRoot() 传递 drain、enrich 和 keep 回调函数

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

::。

## 快速开始

### 1. 安装

<code-group>

```bash [pnpm]
pnpm add evlog @nestjs/common @nestjs/core @nestjs/platform-express
```

```bash [bun]
bun add evlog @nestjs/common @nestjs/core @nestjs/platform-express
```

```bash [yarn]
yarn add evlog @nestjs/common @nestjs/core @nestjs/platform-express
```

```bash [npm]
npm install evlog @nestjs/common @nestjs/core @nestjs/platform-express
```

</code-group>
</prompt>

### 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/app.module.ts]
import { Module } from '@nestjs/common'
import { toMiddlewareOptions } from 'evlog'
import { EvlogModule } from 'evlog/nestjs'
import config from '../evlog.config'

@Module({
  imports: [
    EvlogModule.forRoot(toMiddlewareOptions(config)),
  ],
})
export class AppModule {}
```

### 4. 使用 evlog 引导应用

```typescript [src/main.ts]
import 'reflect-metadata'
import { NestFactory } from '@nestjs/core'
import { initLogger, toLoggerConfig } from 'evlog'
import { AppModule } from './app.module'
import config from '../evlog.config'

initLogger(toLoggerConfig(config))

const app = await NestFactory.create(AppModule)
await app.listen(3000)
```

`EvlogModule.forRoot()` 会注册为全局模块，因此中间件会自动应用于所有路由。

## Fastify 平台

该模块在 `@nestjs/platform-fastify` 上的工作方式相同。请用它替代 `@nestjs/platform-express` 安装，并将适配器传给 `NestFactory.create()`：

```typescript [src/main.ts]
import 'reflect-metadata'
import { NestFactory } from '@nestjs/core'
import { FastifyAdapter } from '@nestjs/platform-fastify'
import type { NestFastifyApplication } from '@nestjs/platform-fastify'
import { initLogger } from 'evlog'
import { AppModule } from './app.module'

initLogger({
  env: { service: 'my-api' },
})

const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter())
await app.listen(3000)
```

`EvlogModule.forRoot()` 会作为一个全局模块注册，因此中间件会自动应用于所有路由。Nest 会在原始 Node 请求上运行中间件，因此在 Fastify 上该模块还会注册一个 `preHandler` 钩子，将 evlog 日志记录器设置到 `FastifyRequest.log` 上，替代 Fastify 自己的请求日志记录器。Guard、拦截器、管道和控制器都可以从 `request.log` 获取 evlog 日志记录器。Fastify 的 `onRequest` 钩子会在它之前运行，仍然会看到 Fastify 的日志记录器。

不要在同一个应用中注册 `evlog/fastify` 插件：两者都会发出自己的广泛事件，因此每个请求都会被记录两次。

## 广泛事件

通过在控制器和服务中逐步构建上下文，一个请求对应一个广泛事件：

```typescript [src/users.controller.ts]
import { Controller, Get, Param } from '@nestjs/common'
import { useLogger } from 'evlog/nestjs'

@Controller('users')
export class UsersController {
  @Get(':id')
  async findOne(@Param('id') id: string) {
    const log = useLogger()

    log.set({ user: { id } })

    const user = await db.findUser(id)
    log.set({ user: { name: user.name, plan: user.plan } })

    const orders = await db.findOrders(id)
    log.set({ orders: { count: orders.length, totalRevenue: sum(orders) } })

    return { 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-...
```

## useLogger()

使用 `useLogger()` 可以从调用堆栈中的任何位置访问请求作用域的日志记录器，而无需将请求对象通过服务层注入：

```typescript [src/users.service.ts]
import { useLogger } from 'evlog/nestjs'

export class UsersService {
  async 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
  }
}
```

```typescript [src/users.controller.ts]
@Controller('users')
export class UsersController {
  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.usersService.findUser(id)
  }
}
```

`req.log` 和 `useLogger()` 返回相同的日志记录器实例。`useLogger()` 使用 `AsyncLocalStorage` 在异步边界之间传播日志记录器。

## 后台工作 (`log.fork`)

使用 `req.log.fork(label, fn)`（或在同一请求中使用 `useLogger()` 获取的日志记录器）创建子广泛事件。请参阅广泛事件中的[发送后](https://evlog.zhcndoc.com/learn/wide-events#after-emit-sealing-and-background-work)。

```typescript [src/orders.controller.ts]
import { useLogger } from 'evlog/nestjs'

@Post()
create(@Req() req: Express.Request) {
  req.log.fork!('enqueue', async () => {
    const log = useLogger()
    log.set({ queued: true })
  })
  return { ok: true }
}
```

## 错误处理

使用 `createError` 创建带有 `why`、`fix` 和 `link` 字段的结构化错误。创建一个 NestJS 异常过滤器来记录并格式化错误：

```typescript [src/evlog-exception.filter.ts]
import { Catch } from '@nestjs/common'
import type { ExceptionFilter, ArgumentsHost } from '@nestjs/common'
import { parseError } from 'evlog'
import { useLogger } from 'evlog/nestjs'

@Catch()
export class EvlogExceptionFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const response = host.switchToHttp().getResponse()
    const error = exception instanceof Error ? exception : new Error(String(exception))

    try {
      useLogger().error(error)
    } catch {
      // 在 evlog 请求作用域之外——日志不可用
    }

    const parsed = parseError(error)
    response.status(parsed.status).json({
      message: parsed.message,
      why: parsed.why,
      fix: parsed.fix,
      link: parsed.link,
    })
  }
}
```

将其应用到控制器中：

```typescript [src/checkout.controller.ts]
import { Controller, Get, UseFilters } from '@nestjs/common'
import { createError } from 'evlog'
import { EvlogExceptionFilter } from './evlog-exception.filter'

@Controller()
@UseFilters(new EvlogExceptionFilter())
export class CheckoutController {
  @Get('checkout')
  checkout() {
    throw createError({
      message: '支付失败',
      status: 402,
      why: '卡片被发卡行拒绝',
      fix: '尝试使用其他支付方式',
      link: 'https://docs.example.com/payments/declined',
    })
  }
}
```

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

```bash [终端输出]
14:58:20 ERROR [my-api] GET /checkout 402 in 3ms
  ├─ error: name=EvlogError message=Payment failed status=402
  └─ requestId: 880a50ac-...
```

## 配置

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

## 排水（drain）与增强器（enrichers）

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

```typescript [src/app.module.ts]
import { Module } from '@nestjs/common'
import { EvlogModule } from 'evlog/nestjs'
import { createAxiomDrain } from 'evlog/axiom'
import { createUserAgentEnricher } from 'evlog/enrichers'

const userAgent = createUserAgentEnricher()

@Module({
  imports: [
    EvlogModule.forRoot({
      drain: createAxiomDrain(),
      enrich: (ctx) => {
        userAgent(ctx)
        ctx.event.region = process.env.FLY_REGION
      },
    }),
  ],
})
export class AppModule {}
```

### 异步配置

当选项依赖于其他提供者（例如 `ConfigService`）时，请使用 `forRootAsync()`：

```typescript [src/app.module.ts]
import { Module } from '@nestjs/common'
import { ConfigModule, ConfigService } from '@nestjs/config'
import { EvlogModule } from 'evlog/nestjs'
import { createAxiomDrain } from 'evlog/axiom'

@Module({
  imports: [
    ConfigModule.forRoot(),
    EvlogModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        drain: createAxiomDrain({ apiKey: config.get('AXIOM_API_KEY') }),
      }),
    }),
  ],
})
export class AppModule {}
```

### 管道（批处理与重试）

在生产环境中，使用 `createDrainPipeline` 包装适配器以实现事件批处理和失败重试：

```typescript [src/app.module.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())

EvlogModule.forRoot({ drain })
```

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

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

</callout>

## 抽样

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

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

## 路由过滤

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

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

## 本地运行

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

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

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

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

</card>

:::

## 下一步

深入你的 **NestJS** 集成：

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


## Sitemap

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