---
title: "PostHog 适配器"
description: "通过 OTLP 将广泛事件发送到 PostHog Logs，用于结构化日志查询、调试和监控。"
canonical_url: "https://evlog.zhcndoc.com/integrate/adapters/cloud/posthog"
---
# PostHog 适配器

> 通过 OTLP 将广泛事件发送到 PostHog Logs，用于结构化日志查询、调试和监控。

[PostHog](https://posthog.com) 是一个开源的产品分析平台。evlog 的 PostHog 适配器通过标准 OTLP 格式将你的广泛事件发送到 [PostHog Logs](https://posthog.com/docs/logs)，为你提供一个带有过滤、搜索和尾部模式的专用日志查看器，并可利用现有的 PostHog API 密钥。

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

添加 PostHog 排水适配器，将 evlog 广泛事件发送到 PostHog Logs。

1. 确认我正在使用的框架并遵循其 evlog 集成模式
2. 如果尚未安装 evlog，请进行安装
3. 从 'evlog/posthog' 导入 createPostHogDrain
4. 将 createPostHogDrain() 集成到我的框架的排水配置中
5. 设置 POSTHOG_API_KEY 环境变量
6. 可选地设置 POSTHOG_HOST 以用于欧盟或自托管实例
7. 通过触发请求并检查 PostHog > 日志进行测试

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

</prompt>

## 安装

PostHog 适配器已与 evlog 捆绑在一起：

```typescript [src/index.ts]
import { createPostHogDrain } from 'evlog/posthog'
```

## 快速开始

### 1. 获取 PostHog 项目 API 密钥

1. 登录你的 [PostHog 控制面板](https://app.posthog.com)
2. 进入 **设置** > **项目** > **项目 API 密钥**
3. 复制密钥（以 `phc_` 开头）

### 2. 设置环境变量

```bash [.env]
POSTHOG_API_KEY=phc_your-project-api-key
```

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

<framework-tabs>

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

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

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

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

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

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

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

export const { handle, handleError } = createEvlogHooks({ drain: createPostHogDrain() })
```

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

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

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

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

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

app.use(evlog({ drain: createPostHogDrain() }))
```

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

app.use(evlog({ drain: createPostHogDrain() }))
```

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

await app.register(evlog, { drain: createPostHogDrain() })
```

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

app.use(evlog({ drain: createPostHogDrain() }))
```

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

EvlogModule.forRoot({ drain: createPostHogDrain() })
```

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

const handler = withEvlog(new RPCHandler(router), { drain: createPostHogDrain() })
```

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

export default withEvlog(handler, { drain: createPostHogDrain() })
```

```typescript [Standalone]
import { createPostHogDrain } from 'evlog/posthog'

initLogger({ drain: createPostHogDrain() })
```

</framework-tabs>

就这样！现在你的广泛事件将以完整的 OTLP 结构（包括严重级别、跟踪上下文和结构化属性）出现在 PostHog 日志中。

## 配置

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

1. **通过 createPostHogDrain() 传入的覆盖项**
2. **位于 runtimeConfig.posthog 的运行时配置**（仅限 Nuxt/Nitro）
3. **环境变量**（`POSTHOG_*`）

### 环境变量

<table>
<thead>
  <tr>
    <th>
      变量
    </th>
    
    <th>
      说明
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        POSTHOG_API_KEY
      </code>
    </td>
    
    <td>
      项目 API 密钥（以 <code>
        phc_
      </code>
      
       开头）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        POSTHOG_HOST
      </code>
    </td>
    
    <td>
      PostHog 主机 URL（适用于 EU 或自托管）
    </td>
  </tr>
</tbody>
</table>

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

通过 `nuxt.config.ts` 进行配置，以实现类型安全的配置：

```typescript [nuxt.config.ts]
export default defineNuxtConfig({
  runtimeConfig: {
    posthog: {
      apiKey: '', // 通过 POSTHOG_API_KEY 设置
      host: '', // 通过 POSTHOG_HOST 设置
    },
  },
})
```

### 覆盖选项

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

<framework-tabs>

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

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createPostHogDrain({
    host: 'https://eu.i.posthog.com',
    timeout: 10000,
  }))
})
```

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

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createPostHogDrain({
    host: 'https://eu.i.posthog.com',
    timeout: 10000,
  }))
})
```

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

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',
  drain: createPostHogDrain({
    host: 'https://eu.i.posthog.com',
    timeout: 10000,
  }),
})
```

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

export const { handle, handleError } = createEvlogHooks({
  drain: createPostHogDrain({
    host: 'https://eu.i.posthog.com',
    timeout: 10000,
  }),
})
```

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

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createPostHogDrain({
    host: 'https://eu.i.posthog.com',
    timeout: 10000,
  }))
})
```

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

export const middleware: Route.MiddlewareFunction[] = [
  evlog({
      drain: createPostHogDrain({
        host: 'https://eu.i.posthog.com',
        timeout: 10000,
      }),
    }),
]
```

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

app.use(evlog({
  drain: createPostHogDrain({
    host: 'https://eu.i.posthog.com',
    timeout: 10000,
  }),
}))
```

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

app.use(evlog({
  drain: createPostHogDrain({
    host: 'https://eu.i.posthog.com',
    timeout: 10000,
  }),
}))
```

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

await app.register(evlog, {
  drain: createPostHogDrain({
    host: 'https://eu.i.posthog.com',
    timeout: 10000,
  }),
})
```

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

app.use(evlog({
  drain: createPostHogDrain({
    host: 'https://eu.i.posthog.com',
    timeout: 10000,
  }),
}))
```

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

EvlogModule.forRoot({
  drain: createPostHogDrain({
    host: 'https://eu.i.posthog.com',
    timeout: 10000,
  }),
})
```

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

const handler = withEvlog(new RPCHandler(router), {
  drain: createPostHogDrain({
    host: 'https://eu.i.posthog.com',
    timeout: 10000,
  }),
})
```

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

export default withEvlog(handler, {
  drain: createPostHogDrain({
    host: 'https://eu.i.posthog.com',
    timeout: 10000,
  }),
})
```

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

initLogger({
  drain: createPostHogDrain({
    host: 'https://eu.i.posthog.com',
    timeout: 10000,
  }),
})
```

</framework-tabs>

### 完整配置参考

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

<tbody>
  <tr>
    <td>
      <code>
        apiKey
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      项目 API 密钥（必填）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        host
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        https://us.i.posthog.com
      </code>
    </td>
    
    <td>
      PostHog 主机 URL
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        distinctId
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      每个事件使用的静态用户标识符
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        distinctIdField
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        userId
      </code>
    </td>
    
    <td>
      保存用户标识符的事件字段（点号路径）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sessionIdField
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        sessionId
      </code>
    </td>
    
    <td>
      保存 PostHog 会话 ID 的事件字段（点号路径）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        recordShape
      </code>
    </td>
    
    <td>
      <code>
        'json' | 'compact'
      </code>
    </td>
    
    <td>
      <code>
        'json'
      </code>
    </td>
    
    <td>
      日志记录形状，参见<a href="#choose-a-record-shape">
        下文
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        timeout
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        5000
      </code>
    </td>
    
    <td>
      请求超时时间，单位为毫秒
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        retries
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        2
      </code>
    </td>
    
    <td>
      临时故障时的重试次数
    </td>
  </tr>
</tbody>
</table>

## 日志与 PostHog 之间发生了什么

在底层，`createPostHogDrain()` 使用 PostHog 特定的默认值包装 OTLP 适配器的 `sendBatchToOTLP()`：

- **端点**：`{host}/i/v1/logs`（PostHog 的 OTLP 日志摄取端点）
- **身份验证**：`Authorization: Bearer {apiKey}` 请求头
- **格式**：标准 OTLP `ExportLogsServiceRequest`，包含严重级别、跟踪上下文和结构化属性
- **身份标识**：`posthogDistinctId` 和 `sessionId` 属性，PostHog 通过它们将日志与项目中的其他数据关联起来

## 选择记录形状

PostHog 将日志属性视为分面：你可以按这些属性进行过滤、细分和设置警报。使用默认的 `json` 形状时，嵌套字段会作为一个序列化属性到达（`ai` = `{"calls":2,"costUsd":0.0012}`），PostHog 可以显示它，但无法据此绘制图表。

`compact` 会将这些字段扁平化为 `ai.calls` 和 `ai.costUsd`，并使用一行摘要替代正文，从而避免重复整个事件：

<framework-tabs>

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

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createPostHogDrain({ recordShape: 'compact' }))
})
```

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

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createPostHogDrain({ recordShape: 'compact' }))
})
```

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

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',
  drain: createPostHogDrain({ recordShape: 'compact' }),
})
```

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

export const { handle, handleError } = createEvlogHooks({ drain: createPostHogDrain({ recordShape: 'compact' }) })
```

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

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createPostHogDrain({ recordShape: 'compact' }))
})
```

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

export const middleware: Route.MiddlewareFunction[] = [
  evlog({ drain: createPostHogDrain({ recordShape: 'compact' }) }),
]
```

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

app.use(evlog({ drain: createPostHogDrain({ recordShape: 'compact' }) }))
```

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

app.use(evlog({ drain: createPostHogDrain({ recordShape: 'compact' }) }))
```

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

await app.register(evlog, { drain: createPostHogDrain({ recordShape: 'compact' }) })
```

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

app.use(evlog({ drain: createPostHogDrain({ recordShape: 'compact' }) }))
```

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

EvlogModule.forRoot({ drain: createPostHogDrain({ recordShape: 'compact' }) })
```

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

const handler = withEvlog(new RPCHandler(router), { drain: createPostHogDrain({ recordShape: 'compact' }) })
```

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

export default withEvlog(handler, { drain: createPostHogDrain({ recordShape: 'compact' }) })
```

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

initLogger({ drain: createPostHogDrain({ recordShape: 'compact' }) })
```

</framework-tabs>

这是 PostHog 推荐的形状。它还会减少发送的数据量：Logs 按摄取的数据量计费，而默认形状会将每个字段传输两次：一次位于正文中，一次位于属性中。

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

下一个主版本中，`compact` 将成为默认值。现在就在新项目中设置它；之后切换意味着需要重写基于 `json` 形状构建的已保存视图和警报。

</callout>

## 将日志关联到用户和会话回放

携带用户标识符的日志会显示在该用户的 PostHog 个人资料中的 **日志** 选项卡下，无需通过猜测服务名称来查找特定用户访问过的内容。如果同时携带会话 ID，日志还会关联到该用户的会话回放。

PostHog 从日志属性中读取这两个值：使用 `posthogDistinctId` 表示用户，使用 `sessionId` 表示回放。适配器会从你的广泛事件中填充它们，因此只要事件携带这些值，就可以直接使用：

<framework-tabs>

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

const log = useLogger(event)

log.set({
  userId: user.id, // → posthogDistinctId，关联到该用户
  sessionId: body.sessionId, // → sessionId，关联到会话回放
})
```

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

const log = useLogger()

log.set({
  userId: user.id, // → posthogDistinctId, links to the person
  sessionId: body.sessionId, // → sessionId, links to the session replay
})
```

```typescript [Hono]src/index.ts
const log = c.get('log')

log.set({
  userId: user.id, // → posthogDistinctId, links to the person
  sessionId: body.sessionId, // → sessionId, links to the session replay
})
```

```typescript [Express]src/index.ts
const log = req.log

log.set({
  userId: user.id, // → posthogDistinctId, links to the person
  sessionId: body.sessionId, // → sessionId, links to the session replay
})
```

</framework-tabs>

会话 ID 来自前端。使用 `posthog.get_session_id()` 读取它，并将其随请求一起发送：

```typescript [app/checkout.ts]
import posthog from 'posthog-js'

await $fetch('/api/checkout', {
  method: 'POST',
  body: { ...payload, sessionId: posthog.get_session_id() },
})
```

### 指定你自己的字段

如果用户身份位于事件中的其他位置，可以让适配器指向该字段。两个选项都接受点号路径：

<framework-tabs>

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

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createPostHogDrain({
    distinctIdField: 'user.id',
    sessionIdField: 'session.id',
  }))
})
```

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

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createPostHogDrain({
    distinctIdField: 'user.id',
    sessionIdField: 'session.id',
  }))
})
```

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

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',
  drain: createPostHogDrain({
    distinctIdField: 'user.id',
    sessionIdField: 'session.id',
  }),
})
```

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

export const { handle, handleError } = createEvlogHooks({
  drain: createPostHogDrain({
    distinctIdField: 'user.id',
    sessionIdField: 'session.id',
  }),
})
```

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

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createPostHogDrain({
    distinctIdField: 'user.id',
    sessionIdField: 'session.id',
  }))
})
```

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

export const middleware: Route.MiddlewareFunction[] = [
  evlog({
      drain: createPostHogDrain({
        distinctIdField: 'user.id',
        sessionIdField: 'session.id',
      }),
    }),
]
```

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

app.use(evlog({
  drain: createPostHogDrain({
    distinctIdField: 'user.id',
    sessionIdField: 'session.id',
  }),
}))
```

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

app.use(evlog({
  drain: createPostHogDrain({
    distinctIdField: 'user.id',
    sessionIdField: 'session.id',
  }),
}))
```

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

await app.register(evlog, {
  drain: createPostHogDrain({
    distinctIdField: 'user.id',
    sessionIdField: 'session.id',
  }),
})
```

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

app.use(evlog({
  drain: createPostHogDrain({
    distinctIdField: 'user.id',
    sessionIdField: 'session.id',
  }),
}))
```

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

EvlogModule.forRoot({
  drain: createPostHogDrain({
    distinctIdField: 'user.id',
    sessionIdField: 'session.id',
  }),
})
```

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

const handler = withEvlog(new RPCHandler(router), {
  drain: createPostHogDrain({
    distinctIdField: 'user.id',
    sessionIdField: 'session.id',
  }),
})
```

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

export default withEvlog(handler, {
  drain: createPostHogDrain({
    distinctIdField: 'user.id',
    sessionIdField: 'session.id',
  }),
})
```

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

initLogger({
  drain: createPostHogDrain({
    distinctIdField: 'user.id',
    sessionIdField: 'session.id',
  }),
})
```

</framework-tabs>

对于 [eve agent](https://evlog.zhcndoc.com/use-cases/eve)，调用方主体就是 eve 用于路由的身份：

```typescript [agent/hooks/evlog.ts]
const drain = createPostHogDrain({ distinctIdField: 'eve.caller.principalId' })
```

静态 `distinctId` 会完全覆盖字段查找。对于作为单一身份运行而非代表用户运行的后端，可以使用它。

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

PostHog 会将属性值与它所知晓的某个用户的每个 `distinct_id` 进行匹配，因此该用户的任一标识符都可以使用。属性键可以在每个项目的 **设置** > **日志** 中配置。将其保留为默认值 `posthogDistinctId`，即可直接使用。

</callout>

## 区域

PostHog 提供美国和欧盟云托管。设置 `host` 以匹配你的区域：

<table>
<thead>
  <tr>
    <th>
      区域
    </th>
    
    <th>
      主机
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      美国（默认）
    </td>
    
    <td>
      <code>
        https://us.i.posthog.com
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      欧盟
    </td>
    
    <td>
      <code>
        https://eu.i.posthog.com
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      自托管
    </td>
    
    <td>
      你的实例 URL
    </td>
  </tr>
</tbody>
</table>

```bash [.env]
# 欧盟区域
POSTHOG_API_KEY=phc_xxx
POSTHOG_HOST=https://eu.i.posthog.com
```

## 在 PostHog 中查询日志

一旦日志开始流动，请在 PostHog 的 **日志** 选项卡中查询它们：

1. 进入 **日志**，并按服务、严重级别或任何结构化属性进行过滤
2. 使用搜索栏查找特定日志条目
3. 点击日志条目以查看所有结构化属性

## 将自定义事件与日志一起发送

如果你更喜欢将日志作为 PostHog 自定义事件发送（例如用于产品分析、群组或漏斗），请使用带有 `mode: 'events'` 的 `createPostHogDrain()`：

<framework-tabs>

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

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createPostHogDrain({
    mode: 'events',
    eventName: 'server_request',
    distinctId: 'my-backend-service',
  }))
})
```

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

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createPostHogDrain({
    mode: 'events',
    eventName: 'server_request',
    distinctId: 'my-backend-service',
  }))
})
```

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

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',
  drain: createPostHogDrain({
    mode: 'events',
    eventName: 'server_request',
    distinctId: 'my-backend-service',
  }),
})
```

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

export const { handle, handleError } = createEvlogHooks({
  drain: createPostHogDrain({
    mode: 'events',
    eventName: 'server_request',
    distinctId: 'my-backend-service',
  }),
})
```

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

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createPostHogDrain({
    mode: 'events',
    eventName: 'server_request',
    distinctId: 'my-backend-service',
  }))
})
```

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

export const middleware: Route.MiddlewareFunction[] = [
  evlog({
      drain: createPostHogDrain({
        mode: 'events',
        eventName: 'server_request',
        distinctId: 'my-backend-service',
      }),
    }),
]
```

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

app.use(evlog({
  drain: createPostHogDrain({
    mode: 'events',
    eventName: 'server_request',
    distinctId: 'my-backend-service',
  }),
}))
```

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

app.use(evlog({
  drain: createPostHogDrain({
    mode: 'events',
    eventName: 'server_request',
    distinctId: 'my-backend-service',
  }),
}))
```

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

await app.register(evlog, {
  drain: createPostHogDrain({
    mode: 'events',
    eventName: 'server_request',
    distinctId: 'my-backend-service',
  }),
})
```

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

app.use(evlog({
  drain: createPostHogDrain({
    mode: 'events',
    eventName: 'server_request',
    distinctId: 'my-backend-service',
  }),
}))
```

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

EvlogModule.forRoot({
  drain: createPostHogDrain({
    mode: 'events',
    eventName: 'server_request',
    distinctId: 'my-backend-service',
  }),
})
```

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

const handler = withEvlog(new RPCHandler(router), {
  drain: createPostHogDrain({
    mode: 'events',
    eventName: 'server_request',
    distinctId: 'my-backend-service',
  }),
})
```

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

export default withEvlog(handler, {
  drain: createPostHogDrain({
    mode: 'events',
    eventName: 'server_request',
    distinctId: 'my-backend-service',
  }),
})
```

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

initLogger({
  drain: createPostHogDrain({
    mode: 'events',
    eventName: 'server_request',
    distinctId: 'my-backend-service',
  }),
})
```

</framework-tabs>

然后像默认日志排水管道一样，将 `drain` 传递给你的框架（参见上面的[快速开始](#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B)）。

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

自定义事件会计入你的 PostHog 事件配额。PostHog 日志（默认的 `createPostHogDrain()`）成本要低得多。

</callout>

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

**旧版：** `createPostHogEventsDrain()` 已弃用，并会重定向到 `createPostHogDrain({ mode: 'events' })`。它将在下一个主要版本中移除。

</callout>

### 配置要发送的事件

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

<tbody>
  <tr>
    <td>
      <code>
        apiKey
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      项目 API 密钥（必填）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        host
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        https://us.i.posthog.com
      </code>
    </td>
    
    <td>
      PostHog 主机 URL
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        eventName
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        evlog_wide_event
      </code>
    </td>
    
    <td>
      PostHog 事件名称
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        distinctId
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      所有事件使用的静态 <code>
        distinct_id
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        distinctIdField
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        userId
      </code>
    </td>
    
    <td>
      保存 <code>
        distinct_id
      </code>
      
       的事件字段（点号路径）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        recordShape
      </code>
    </td>
    
    <td>
      <code>
        'json' | 'compact'
      </code>
    </td>
    
    <td>
      <code>
        'json'
      </code>
    </td>
    
    <td>
      事件属性形状：<code>
        compact
      </code>
      
       会扁平化嵌套字段（参见<a href="#event-format">
        下文
      </a>
      
      ）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        timeout
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        5000
      </code>
    </td>
    
    <td>
      请求超时时间，单位为毫秒
    </td>
  </tr>
</tbody>
</table>

### 事件格式

evlog 将广泛事件映射到 PostHog 事件：

<table>
<thead>
  <tr>
    <th>
      evlog 字段
    </th>
    
    <th>
      PostHog 字段
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        config.distinctId
      </code>
      
       或 <code>
        userId
      </code>
      
       或 <code>
        service
      </code>
    </td>
    
    <td>
      <code>
        distinct_id
      </code>
      
      （回退链）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        timestamp
      </code>
    </td>
    
    <td>
      <code>
        timestamp
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        level
      </code>
    </td>
    
    <td>
      <code>
        properties.level
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        service
      </code>
    </td>
    
    <td>
      <code>
        properties.service
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        environment
      </code>
    </td>
    
    <td>
      <code>
        properties.environment
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      所有其他字段
    </td>
    
    <td>
      <code>
        properties.*
      </code>
    </td>
  </tr>
</tbody>
</table>

使用 `recordShape: 'compact'` 时，其他字段会以与日志模式相同的方式扁平化为点号键：嵌套的 `ai` 对象会作为 `properties.ai.costUsd` 和 `properties.ai.calls` 到达，而不是作为一个不透明的 `properties.ai` 值。PostHog 可以对这些单独的属性进行过滤和细分；默认的 `json` 形状会将嵌套对象保留为单个序列化属性。有关其中的权衡，以及它如何影响已保存的视图和警报，请参见[选择记录形状](#choose-a-record-shape)。

### distinct ID 的解析方式

`distinct_id` 遵循回退链：

1. **config.distinctId** — `createPostHogDrain({ mode: 'events' })` 中的显式覆盖项
2. **event.userId** — 或 `distinctIdField` 指向的字段（当其值为字符串或数字时）
3. **event.service** — 最终回退值，以匿名事件发送

### 已识别事件和匿名事件

解析为真实用户的事件属于**已识别事件**：PostHog 会为其创建用户个人资料并附加用户属性。

当没有解析出标识符时，事件会作为**匿名事件**发送（`$process_person_profile: false`），而不是将每个请求都归入一个以服务命名的“用户”。PostHog 对匿名事件的收费更低，并且不会将其纳入用户个人资料。

```typescript
// Identified: events carrying `userId` create and update a person profile
const drain = createPostHogDrain({ mode: 'events' })

// 将其识别为单一后端身份，无论请求来自何处
const service = createPostHogDrain({ mode: 'events', distinctId: 'checkout-worker' })
```

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

匿名事件的成本最高可比已识别事件低 4 倍。仅在确实需要进行逐用户分析的事件上设置身份。

</callout>

### 何时使用日志，何时使用事件

<table>
<thead>
  <tr>
    <th>
      
    </th>
    
    <th>
      <code>
        createPostHogDrain()
      </code>
    </th>
    
    <th>
      <code>
        createPostHogDrain({ mode: 'events' })
      </code>
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <strong>
        格式
      </strong>
    </td>
    
    <td>
      OTLP 日志（<code>
        /i/v1/logs
      </code>
      
      ）
    </td>
    
    <td>
      PostHog 事件（<code>
        /batch/
      </code>
      
      ）
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        PostHog UI
      </strong>
    </td>
    
    <td>
      日志查看器
    </td>
    
    <td>
      事件探索器
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        成本
      </strong>
    </td>
    
    <td>
      较低（专用的日志管道）
    </td>
    
    <td>
      较高（计入事件）
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        最佳用途
      </strong>
    </td>
    
    <td>
      调试、日志搜索、监控
    </td>
    
    <td>
      产品分析、群组、漏斗
    </td>
  </tr>
</tbody>
</table>

你可以同时使用两种排水管道以获得最佳效果：

<framework-tabs>

```typescript [Nuxt]server/plugins/evlog.ts
import { createPostHogDrain } from 'evlog/posthog'

const logs = createPostHogDrain()
const events = createPostHogDrain({ mode: 'events' })

const drain = async (ctx) => {
  await Promise.allSettled([logs(ctx), events(ctx)])
}

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

```typescript [Nitro]server/plugins/evlog.ts
import { definePlugin } from 'nitro'
import { createPostHogDrain } from 'evlog/posthog'

const logs = createPostHogDrain()
const events = createPostHogDrain({ mode: 'events' })

const drain = async (ctx) => {
  await Promise.allSettled([logs(ctx), events(ctx)])
}

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

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

const logs = createPostHogDrain()
const events = createPostHogDrain({ mode: 'events' })

const drain = async (ctx) => {
  await Promise.allSettled([logs(ctx), events(ctx)])
}
export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',
  drain: drain,
})
```

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

const logs = createPostHogDrain()
const events = createPostHogDrain({ mode: 'events' })

const drain = async (ctx) => {
  await Promise.allSettled([logs(ctx), events(ctx)])
}
export const { handle, handleError } = createEvlogHooks({ drain: drain })
```

```typescript [TanStack Start]server/plugins/evlog.ts
import { definePlugin } from 'nitro'
import { createPostHogDrain } from 'evlog/posthog'

const logs = createPostHogDrain()
const events = createPostHogDrain({ mode: 'events' })

const drain = async (ctx) => {
  await Promise.allSettled([logs(ctx), events(ctx)])
}

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

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

const logs = createPostHogDrain()
const events = createPostHogDrain({ mode: 'events' })

const drain = async (ctx) => {
  await Promise.allSettled([logs(ctx), events(ctx)])
}
export const middleware: Route.MiddlewareFunction[] = [
  evlog({ drain: drain }),
]
```

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

const logs = createPostHogDrain()
const events = createPostHogDrain({ mode: 'events' })

const drain = async (ctx) => {
  await Promise.allSettled([logs(ctx), events(ctx)])
}
app.use(evlog({ drain: drain }))
```

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

const logs = createPostHogDrain()
const events = createPostHogDrain({ mode: 'events' })

const drain = async (ctx) => {
  await Promise.allSettled([logs(ctx), events(ctx)])
}
app.use(evlog({ drain: drain }))
```

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

const logs = createPostHogDrain()
const events = createPostHogDrain({ mode: 'events' })

const drain = async (ctx) => {
  await Promise.allSettled([logs(ctx), events(ctx)])
}
await app.register(evlog, { drain: drain })
```

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

const logs = createPostHogDrain()
const events = createPostHogDrain({ mode: 'events' })

const drain = async (ctx) => {
  await Promise.allSettled([logs(ctx), events(ctx)])
}
app.use(evlog({ drain: drain }))
```

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

const logs = createPostHogDrain()
const events = createPostHogDrain({ mode: 'events' })

const drain = async (ctx) => {
  await Promise.allSettled([logs(ctx), events(ctx)])
}
EvlogModule.forRoot({ drain: drain })
```

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

const logs = createPostHogDrain()
const events = createPostHogDrain({ mode: 'events' })

const drain = async (ctx) => {
  await Promise.allSettled([logs(ctx), events(ctx)])
}
const handler = withEvlog(new RPCHandler(router), { drain: drain })
```

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

const logs = createPostHogDrain()
const events = createPostHogDrain({ mode: 'events' })

const drain = async (ctx) => {
  await Promise.allSettled([logs(ctx), events(ctx)])
}
export default withEvlog(handler, { drain: drain })
```

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

const logs = createPostHogDrain()
const events = createPostHogDrain({ mode: 'events' })

const drain = async (ctx) => {
  await Promise.allSettled([logs(ctx), events(ctx)])
}
initLogger({ drain: drain })
```

</framework-tabs>

## 故障排除

### 缺少 API 密钥错误

```text [Console]
[evlog/posthog] 缺少 apiKey。设置 POSTHOG_API_KEY 环境变量或传递给 createPostHogDrain()
```

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

### 事件未出现

PostHog 异步处理事件。事件可能需要短暂延迟（通常不到 1 分钟）才能在仪表板中显示。

1. 检查服务器控制台是否有 `[evlog/posthog]` 错误信息
2. 验证 API 密钥是否正确并以 `phc_` 开头
3. 确认 `host` 与你的 PostHog 区域（美国或欧盟）匹配

### 区域错误

如果你在 PostHog EU 上但使用了默认的美国主机，事件交付将失败，并且适配器会在服务器控制台记录错误（例如 `[evlog/posthog]`）。设置正确的主机：

```bash [.env]
POSTHOG_HOST=https://eu.i.posthog.com
```

## 不使用框架调用排水管道

对于高级用例，你可以使用更低级别的函数：

```typescript [server/utils/posthog.ts]
import { sendToPostHog, sendBatchToPostHog } from 'evlog/posthog'

// 发送单个事件到 PostHog 日志（OTLP）
await sendToPostHog(event, {
  apiKey: 'phc_xxx',
})

// 批量发送多个事件
await sendBatchToPostHog(events, {
  apiKey: 'phc_xxx',
})
```

对于自定义事件，请使用专用的事件函数：

```typescript [server/utils/posthog.ts]
import { sendToPostHogEvents, sendBatchToPostHogEvents, toPostHogEvent } from 'evlog/posthog'

// 发送单个自定义事件
await sendToPostHogEvents(event, {
  apiKey: 'phc_xxx',
})

// 批量发送多个自定义事件
await sendBatchToPostHogEvents(events, {
  apiKey: 'phc_xxx',
})

// 转换为 PostHog 格式（用于检查）
const posthogEvent = toPostHogEvent(event, { apiKey: 'phc_xxx' })
```

## 下一步

- [Axiom 适配器](https://evlog.zhcndoc.com/integrate/adapters/cloud/axiom) - 将日志发送到 Axiom
- [OTLP 适配器](https://evlog.zhcndoc.com/integrate/adapters/hybrid/otlp) - 通过 OpenTelemetry 协议发送日志
- [自定义适配器](https://evlog.zhcndoc.com/extend/custom-drains) - 构建您自己的适配器
- [最佳实践](https://evlog.zhcndoc.com/reference/best-practices) - 安全性和生产环境提示。

---

- [PostHog 控制面板](https://app.posthog.com)
- [Axiom 适配器](https://evlog.zhcndoc.com/integrate/adapters/cloud/axiom)


## Sitemap

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