---
title: "Sentry 适配器"
description: "将宽事件发送到 Sentry Logs，并将服务器错误转为携带事件 trace 的 Sentry Issue。通过 DSN 即可完成零配置设置。"
canonical_url: "https://evlog.zhcndoc.com/integrate/adapters/cloud/sentry"
---
# Sentry 适配器

> 将宽事件发送到 Sentry Logs，并将服务器错误转为携带事件 trace 的 Sentry Issue。通过 DSN 即可完成零配置设置。

[Sentry](https://sentry.io) 是错误追踪和性能监控平台。evlog Sentry 适配器会将宽事件作为 **Sentry Structured Logs** 发送，可在 Sentry 控制台的 **Explore > Logs** 中查看，并支持高基数属性搜索。下方快速开始会设置 `issues: true`，此时每个服务器错误还会创建一个 **Sentry Issue**，并附带堆栈和宽事件的 trace，让 Issue 告警和详情页能关联到 evlog 错误及完整事件。

<prompt :actions="["copy","cursor","claude"]" description="添加 Sentry 排水器适配器" icon="i-simple-icons-sentry">

添加 Sentry drain 适配器，将 evlog 宽事件发送到 Sentry Logs，并为服务器错误创建 Sentry Issue。

1. 确认我使用的框架，并遵循对应的 evlog 集成方式
2. 如果尚未安装 evlog，请安装
3. 从 'evlog/sentry' 导入 createSentryDrain
4. 在框架的 drain 配置中接入 createSentryDrain({ issues: true })。如果应用已通过 @sentry/node 或其他 Sentry SDK 捕获错误，则使用不带 issues 的 createSentryDrain()，避免重复上报
5. 设置 SENTRY_DSN 环境变量
6. 触发请求进行测试，并在 Sentry > Explore > Logs 中检查；然后触发服务器错误，并在 Sentry > Issues 中检查

适配器文档：[https://www.evlog.dev/integrate/adapters/cloud/sentry](https://www.evlog.dev/integrate/adapters/cloud/sentry)
框架设置：[https://www.evlog.dev/integrate/frameworks/overview](https://www.evlog.dev/integrate/frameworks/overview)

</prompt>

## 安装

Sentry 适配器已与 evlog 捆绑提供：

```typescript [src/index.ts]
import { createSentryDrain } from 'evlog/sentry'
```

## 快速开始

### 1. 获取你的 Sentry DSN

1. 创建一个 [Sentry 账户](https://sentry.io)
2. 创建一个新的项目（Node.js 或 JavaScript）
3. 在 **设置 > 项目 > 你的项目 > 客户端密钥（DSN）** 中找到你的 DSN

### 2. 设置环境变量

```bash [.env]
SENTRY_DSN=https://your-public-key@o0.ingest.sentry.io/your-project-id
```

### 3. 将排水器连接到你的框架

<framework-tabs>

```typescript [Nuxt]server/plugins/evlog-drain.ts
import { createSentryDrain } from 'evlog/sentry'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createSentryDrain({ issues: true }))
})
```

```typescript [Nitro]server/plugins/evlog-drain.ts
import { definePlugin } from 'nitro'
import { createSentryDrain } from 'evlog/sentry'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createSentryDrain({ issues: true }))
})
```

```typescript [Next.js]lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { createSentryDrain } from 'evlog/sentry'

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',
  drain: createSentryDrain({ issues: true }),
})
```

```typescript [SvelteKit]src/hooks.server.ts
import { createEvlogHooks } from 'evlog/sveltekit'
import { createSentryDrain } from 'evlog/sentry'

export const { handle, handleError } = createEvlogHooks({ drain: createSentryDrain({ issues: true }) })
```

```typescript [TanStack Start]server/plugins/evlog-drain.ts
import { definePlugin } from 'nitro'
import { createSentryDrain } from 'evlog/sentry'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createSentryDrain({ issues: true }))
})
```

```typescript [React Router]app/root.tsx
import { evlog } from 'evlog/react-router'
import { createSentryDrain } from 'evlog/sentry'

export const middleware: Route.MiddlewareFunction[] = [
  evlog({ drain: createSentryDrain({ issues: true }) }),
]
```

```typescript [Hono]src/index.ts
import { createSentryDrain } from 'evlog/sentry'

app.use(evlog({ drain: createSentryDrain({ issues: true }) }))
```

```typescript [Express]src/index.ts
import { createSentryDrain } from 'evlog/sentry'

app.use(evlog({ drain: createSentryDrain({ issues: true }) }))
```

```typescript [Fastify]src/index.ts
import { createSentryDrain } from 'evlog/sentry'

await app.register(evlog, { drain: createSentryDrain({ issues: true }) })
```

```typescript [Elysia]src/index.ts
import { createSentryDrain } from 'evlog/sentry'

app.use(evlog({ drain: createSentryDrain({ issues: true }) }))
```

```typescript [NestJS]src/app.module.ts
import { createSentryDrain } from 'evlog/sentry'

EvlogModule.forRoot({ drain: createSentryDrain({ issues: true }) })
```

```typescript [oRPC]server/orpc.ts
import { withEvlog } from 'evlog/orpc'
import { createSentryDrain } from 'evlog/sentry'

const handler = withEvlog(new RPCHandler(router), { drain: createSentryDrain({ issues: true }) })
```

```typescript [Cloudflare Workers]src/worker.ts
import { withEvlog } from 'evlog/workers'
import { createSentryDrain } from 'evlog/sentry'

export default withEvlog(handler, { drain: createSentryDrain({ issues: true }) })
```

```typescript [Standalone]
import { createSentryDrain } from 'evlog/sentry'

initLogger({ drain: createSentryDrain({ issues: true }) })
```

</framework-tabs>

现在，你的宽事件会显示在 **Explore > Logs** 中；每个状态码不是 4xx 的 `error` 或 `fatal` 事件都会在 **Issues** 中创建 Issue。如果应用已通过 `@sentry/node` 捕获错误，请移除 `issues: true`，参见[问题与告警](#issues-and-alerts)。

## 配置

适配器按照优先级顺序从多个来源读取配置（优先级从高到低）：

1. **覆盖项** 传递给 `createSentryDrain()`
2. **运行时配置** 位于 `runtimeConfig.sentry`（仅限 Nuxt/Nitro）
3. **环境变量**（`SENTRY_*`）

### 环境变量

<table>
<thead>
  <tr>
    <th>
      变量
    </th>
    
    <th>
      描述
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        SENTRY_DSN
      </code>
    </td>
    
    <td>
      Sentry DSN（必需）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        SENTRY_ENVIRONMENT
      </code>
    </td>
    
    <td>
      环境名称覆盖项
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        SENTRY_RELEASE
      </code>
    </td>
    
    <td>
      发布版本覆盖项
    </td>
  </tr>
</tbody>
</table>

### 运行时配置（仅限 Nuxt）

通过在 `nuxt.config.ts` 中配置，实现类型安全的配置：

```typescript [nuxt.config.ts]
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    sentry: {
      dsn: '', // 通过 SENTRY_DSN 设置
      environment: 'production',
      release: '1.0.0',
    },
  },
})
```

### 覆盖选项

直接传递选项以覆盖任何配置：

<framework-tabs>

```typescript [Nuxt]server/plugins/evlog-drain.ts
import { createSentryDrain } from 'evlog/sentry'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }))
})
```

```typescript [Nitro]server/plugins/evlog-drain.ts
import { definePlugin } from 'nitro'
import { createSentryDrain } from 'evlog/sentry'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }))
})
```

```typescript [Next.js]lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { createSentryDrain } from 'evlog/sentry'

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
})
```

```typescript [SvelteKit]src/hooks.server.ts
import { createEvlogHooks } from 'evlog/sveltekit'
import { createSentryDrain } from 'evlog/sentry'

export const { handle, handleError } = createEvlogHooks({
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
})
```

```typescript [TanStack Start]server/plugins/evlog-drain.ts
import { definePlugin } from 'nitro'
import { createSentryDrain } from 'evlog/sentry'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }))
})
```

```typescript [React Router]app/root.tsx
import { evlog } from 'evlog/react-router'
import { createSentryDrain } from 'evlog/sentry'

export const middleware: Route.MiddlewareFunction[] = [
  evlog({
      drain: createSentryDrain({
        dsn: 'https://key@o0.ingest.sentry.io/123',
        tags: { team: 'backend' },
        timeout: 10000,
      }),
    }),
]
```

```typescript [Hono]src/index.ts
import { evlog } from 'evlog/hono'
import { createSentryDrain } from 'evlog/sentry'

app.use(evlog({
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
}))
```

```typescript [Express]src/index.ts
import { evlog } from 'evlog/express'
import { createSentryDrain } from 'evlog/sentry'

app.use(evlog({
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
}))
```

```typescript [Fastify]src/index.ts
import { evlog } from 'evlog/fastify'
import { createSentryDrain } from 'evlog/sentry'

await app.register(evlog, {
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
})
```

```typescript [Elysia]src/index.ts
import { evlog } from 'evlog/elysia'
import { createSentryDrain } from 'evlog/sentry'

app.use(evlog({
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
}))
```

```typescript [NestJS]src/app.module.ts
import { EvlogModule } from 'evlog/nestjs'
import { createSentryDrain } from 'evlog/sentry'

EvlogModule.forRoot({
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
})
```

```typescript [oRPC]server/orpc.ts
import { withEvlog } from 'evlog/orpc'
import { createSentryDrain } from 'evlog/sentry'

const handler = withEvlog(new RPCHandler(router), {
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
})
```

```typescript [Cloudflare Workers]src/worker.ts
import { withEvlog } from 'evlog/workers'
import { createSentryDrain } from 'evlog/sentry'

export default withEvlog(handler, {
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
})
```

```typescript [Standalone]src/index.ts
import { initLogger } from 'evlog'
import { createSentryDrain } from 'evlog/sentry'

initLogger({
  drain: createSentryDrain({
    dsn: 'https://key@o0.ingest.sentry.io/123',
    tags: { team: 'backend' },
    timeout: 10000,
  }),
})
```

</framework-tabs>

### 完整配置参考

<table>
<thead>
  <tr>
    <th>
      选项
    </th>
    
    <th>
      类型
    </th>
    
    <th>
      默认值
    </th>
    
    <th>
      说明
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        dsn
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      Sentry DSN（必需）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        environment
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      事件环境
    </td>
    
    <td>
      环境名称
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        release
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      事件版本
    </td>
    
    <td>
      发布版本
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tags
      </code>
    </td>
    
    <td>
      <code>
        Record<string, string>
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      要附加的其他属性
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        timeout
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        5000
      </code>
    </td>
    
    <td>
      请求超时时间（毫秒）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        issues
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      同时将 <code>
        error
      </code>
      
       和 <code>
        fatal
      </code>
      
       事件作为 Sentry 错误事件发送
    </td>
  </tr>
</tbody>
</table>

## 日志转换

evlog 宽事件通过 `toSentryLog()` 转换为 Sentry 日志：

- **等级映射**：evlog 等级直接映射（`debug`、`info`、`warn`、`error`）
- **严重性数字**：遵循 OpenTelemetry 规范（`debug=5`、`info=9`、`warn=13`、`error=17`）
- **正文**：来自事件的 `message`、`action` 或 `path` 字段（按可用性优先）
- **属性**：所有宽事件字段作为类型化属性发送（字符串、整数、浮点数、布尔值）。复杂对象会被序列化为 JSON 字符串。
- **Sentry 属性**：自动设置 `sentry.environment` 和 `sentry.release`
- **跟踪 ID**：如果存在 `event.traceId` 则使用，否则生成随机值。

## 问题与告警 [](undefined)

单独发送日志不会创建 Sentry Issue。设置 `issues: true` 后，每个 `error` 和 `fatal` 宽事件也会作为 Sentry 错误事件发送，从而归并到 Issue 并触发 Issue 告警。这是推荐设置，也是快速开始中的配置：Sentry 围绕 Issue 工作；不创建 Issue 时，evlog 错误只是一条不会触发告警的日志。该选项默认值为 `false`，因此升级后不会自动开始创建 Issue：

```typescript [server/plugins/evlog-drain.ts]
createSentryDrain({ issues: true })
```

状态码为 4xx 的事件不会作为 Issue 发送。抛出的 404、401 或校验错误 400 仍会将宽事件标记为 `error`，并作为日志发送到 Sentry，但它们属于预期流量，因此不会创建 Issue。状态依次从 `error.status`、`error.statusCode` 和事件的 `status` 读取。

通过[客户端传输](https://evlog.zhcndoc.com/use-cases/client-logging)（`source: 'client'`）发送的浏览器错误也会创建 Issue。任何人都能向该端点发请求，因此请保留 Sentry 的流量激增保护，或为项目设置速率限制。

错误事件携带宽事件的 trace 上下文：`contexts.trace.trace_id` 等于事件的 `traceId`，也等于对应日志的 `trace_id`，因此打开 Issue 时会定位到该日志。如果 `traceId` 来自传入的 `traceparent`，同一个 ID 还会将 Issue 与 trace 中的其他服务关联起来。事件没有 `traceId` 时，Sentry drain 会生成一个 ID，只供该日志和 Issue 共用；管道中的其他 drain 不会看到它。

异常类型和值分别来自 `error.name` 和 `error.message`，`error.stack` 会成为 Sentry 用于归并的 Issue 堆栈帧。没有堆栈的错误会按类型和 transaction 归并，因此带有 ID 的消息（`User 8231 not found`）仍会归为同一个 Issue。transaction 为 `METHOD /path` 或 `action`；`service` 和你的 `tags` 会成为 Issue 标签，`userId` 或 `user.id` 会设置 Issue 用户。其他字段只保留在日志中。

日志会先发送，每个 envelope 最多包含 100 条；日志 envelope 失败会使整个批次失败。Issue 随后逐个请求发送。第一个失败的 Issue 会被报告，并停止发送该批次剩余 Issue。因此，当批次不超过 100 条日志时，重试不会重新发送已成功发送的日志。超过 100 条日志时，重试可能会再次发送已被接收的 envelope。

<callout color="warning" icon="i-lucide-triangle-alert">

如果应用还运行启用了错误捕获的 `@sentry/node`（或其他 Sentry SDK），请关闭 `issues`：两者会同时上报同一个故障，导致每个 Issue 都被创建两次。

</callout>

## 在 Sentry 中查询日志

evlog 以结构化日志形式发送宽事件。在 Sentry 控制台中：

- **Explore > 日志**：查看所有 evlog 宽事件，并支持完整属性搜索
- **按属性过滤**：如 `service:my-app`、`level:error` 或任意宽事件字段
- **跟踪关联**：日志通过 `trace_id` 与跟踪关联，便于交叉引用

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

Sentry 结构化日志支持高基数属性，非常适合 evlog 的宽事件。你的宽事件中的每个字段都会成为 Sentry 中可搜索的属性。

</callout>

## 故障排除

### 缺少 DSN 错误

```text [控制台]
[evlog/sentry] 缺少 DSN。请设置 SENTRY_DSN 环境变量或在 createSentryDrain() 中传递
```

请确保已设置环境变量，并在添加后重启服务器。

### 无效的 DSN

如果 DSN 格式错误（缺少公钥或项目 ID），适配器会抛出错误。请验证 DSN 格式：

```text [Sentry DSN 格式]
https://<公钥>@<主机>/<项目ID>
```

### 401 未授权

你的 DSN 可能被吊销或无效。请在 **设置 > 项目 > 客户端密钥（DSN）** 中生成新的 DSN。

## 直接 API 用法

对于高级用例，你可以使用底层函数：

```typescript [server/utils/sentry.ts]
import { sendToSentry, sendBatchToSentry } from 'evlog/sentry'

// 将单个事件发送为 Sentry 日志
await sendToSentry(event, {
  dsn: process.env.SENTRY_DSN!,
})

// 批量发送多个事件
await sendBatchToSentry(events, {
  dsn: process.env.SENTRY_DSN!,
})
```

## 下一步

- [Axiom 适配器](https://evlog.zhcndoc.com/integrate/adapters/cloud/axiom) - 将日志发送到 Axiom，以便查询和创建仪表板
- [OTLP 适配器](https://evlog.zhcndoc.com/integrate/adapters/hybrid/otlp) - 通过 OpenTelemetry 协议发送日志
- [PostHog 适配器](https://evlog.zhcndoc.com/integrate/adapters/cloud/posthog) - 将日志发送到 PostHog
- [自定义适配器](https://evlog.zhcndoc.com/extend/custom-drains) - 构建您自己的适配器

---

- [Sentry 控制台](https://sentry.io)
- [OTLP 适配器](https://evlog.zhcndoc.com/integrate/adapters/hybrid/otlp)


## Sitemap

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