---
title: "内存适配器"
description: "将宽事件存储在内存环形缓冲区中。可在任何运行时中工作——包括 Cloudflare Workers（workerd）在内——即使文件系统不可用也是如此。"
canonical_url: "https://evlog.zhcndoc.com/integrate/adapters/self-hosted/memory"
---
# 内存适配器

> 将宽事件存储在内存环形缓冲区中。可在任何运行时中工作——包括 Cloudflare Workers（workerd）在内——即使文件系统不可用也是如此。

Memory 适配器将宽事件存储在模块级环形缓冲区中。与[文件系统适配器](https://evlog.zhcndoc.com/integrate/adapters/self-hosted/fs)不同，它**零运行时依赖**，可以在任何地方运行，包括 Cloudflare Workers（workerd）、Deno Deploy，以及其他不提供 Node `fs` 模块的边缘运行时。

其主要使用场景是**本地开发中的代理访问**：在开发期间接入 drain，暴露一个轻量级 HTTP 端点，然后让你的 AI 代理通过 HTTP 获取结构化日志，而无需任何外部工具。

<prompt :actions="["copy","cursor","claude"]" description="添加内存 drain 适配器" icon="i-lucide-cpu">

添加内存 drain 适配器，以将 evlog 宽事件存储在内存环形缓冲区中。

1. 识别我正在使用的框架，并遵循其 evlog 集成模式
2. 如果尚未安装，则安装 evlog
3. 从 'evlog/memory' 导入 createMemoryDrain 和 readMemoryLogs
4. 将 createMemoryDrain() 接入我框架的 drain 配置
5. 暴露一个仅在开发环境可用的 HTTP 端点，返回 readMemoryLogs() 的 JSON 结果
6. 代理现在可以访问该端点，通过 HTTP 检索结构化日志
7. 可选地配置 maxEvents（默认 1000）或使用命名存储

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

:: tunngatillugu

## 安装

内存适配器随 evlog 一起提供：

```typescript [src/index.ts]
import { createMemoryDrain, readMemoryLogs } from 'evlog/memory'
```

## 快速开始

<framework-tabs>

```typescript [Hono (Cloudflare Workers)]src/index.ts
import { Hono } from 'hono'
import { evlog } from 'evlog/hono'
import { createMemoryDrain, readMemoryLogs } from 'evlog/memory'

const app = new Hono()

app.use(evlog({ drain: createMemoryDrain() }))

// 仅限开发环境的端点——在生产环境中请限制访问或移除
app.get('/_evlog/logs', (c) => {
  return c.json(readMemoryLogs())
})
```

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

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

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

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

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

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

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

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

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

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

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

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

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

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

```typescript [独立使用]
import { createMemoryDrain } from 'evlog/memory'

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

</framework-tabs>
</prompt>

## 通过 HTTP 访问代理

暴露一个路由，让代理在本地开发会话期间检索结构化日志。使用 `parseReadMemoryLogsQuery` 让代理可以直接通过查询字符串传递过滤参数：

```typescript [src/index.ts（Hono）]
import { readMemoryLogs, parseReadMemoryLogsQuery } from 'evlog/memory'

// 仅限开发环境——代理会访问此端点来检索日志
if (process.env.NODE_ENV !== 'production') {
  app.get('/_evlog/logs', (c) => {
    return c.json(readMemoryLogs(parseReadMemoryLogsQuery(c.req.query())))
  })
}
```

现在，代理可以调用 `/_evlog/logs?level=error&limit=50&since=2026-01-01T00:00:00Z`，并且在传递给 `readMemoryLogs` 之前，查询参数会被强制转换为正确的类型。支持的查询参数：`store`、`since`、`until`、`level`（多个值可用逗号分隔）、`limit`。

响应是 [`WideEvent`](https://evlog.zhcndoc.com/reference/configuration) 对象组成的 JSON 数组，其结构与其他所有 evlog 适配器使用的结构相同。

## 配置

### 选项

<table>
<thead>
  <tr>
    <th>
      选项
    </th>
    
    <th>
      类型
    </th>
    
    <th>
      默认值
    </th>
    
    <th>
      描述
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        maxEvents
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1000
      </code>
    </td>
    
    <td>
      在环形缓冲区中保留的最大事件数（最旧的会被丢弃）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        store
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'default'
      </code>
    </td>
    
    <td>
      命名缓冲区键——共享同一键的多个 drain 会共享同一个缓冲区
    </td>
  </tr>
</tbody>
</table>

```typescript
// Keep only the last 500 events
createMemoryDrain({ maxEvents: 500 })

// 使用命名存储以实现隔离
createMemoryDrain({ store: 'my-service' })
```

### 环境变量

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

<tbody>
  <tr>
    <td>
      <code>
        EVLOG_MEMORY_STORE
      </code>
    </td>
    
    <td>
      命名缓冲区键（默认值：<code>
        'default'
      </code>
      
      ）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        EVLOG_MEMORY_MAX_EVENTS
      </code>
    </td>
    
    <td>
      环形缓冲区大小（默认值：<code>
        1000
      </code>
      
      ）
    </td>
  </tr>
</tbody>
</table>

配置优先级与其他适配器一致：覆盖项 → `runtimeConfig.evlog.memory` → 环境变量。

### 命名存储

使用命名存储来隔离来自不同服务的事件，或用于测试：

```typescript [src/index.ts]
import { createMemoryDrain, readMemoryLogs, clearMemoryLogs } from 'evlog/memory'

// 两个独立的缓冲区
const authDrain = createMemoryDrain({ store: 'auth' })
const apiDrain = createMemoryDrain({ store: 'api' })

// 从特定存储中读取
const authErrors = readMemoryLogs({ store: 'auth', level: 'error' })

// 清空某个存储（在测试中很有用）
clearMemoryLogs('auth')
```

## 查询

`readMemoryLogs` 支持与 `readFsLogs` 相同的过滤选项：

```typescript [src/index.ts]
import { readMemoryLogs } from 'evlog/memory'

// 所有事件
const all = readMemoryLogs()

// 仅错误
const errors = readMemoryLogs({ level: 'error' })

// 最近 10 分钟
const recent = readMemoryLogs({
  since: new Date(Date.now() - 10 * 60 * 1000),
})

// 自定义谓词
const slow = readMemoryLogs({
  filter: e => (e.durationMs ?? 0) > 1000,
})

// 最近的 50 个事件
const latest = readMemoryLogs({ limit: 50 })
```

### `readMemoryLogs` 选项

<table>
<thead>
  <tr>
    <th>
      选项
    </th>
    
    <th>
      类型
    </th>
    
    <th>
      描述
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        store
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      要读取的命名存储（默认：<code>
        'default'
      </code>
      
      ）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        since
      </code>
    </td>
    
    <td>
      <code>
        Date | string
      </code>
    </td>
    
    <td>
      仅返回 <code>
        timestamp >= since
      </code>
      
       的事件
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        until
      </code>
    </td>
    
    <td>
      <code>
        Date | string
      </code>
    </td>
    
    <td>
      仅返回 <code>
        timestamp <= until
      </code>
      
       的事件
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        level
      </code>
    </td>
    
    <td>
      <code>
        LogLevel | LogLevel[]
      </code>
    </td>
    
    <td>
      按级别过滤
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        filter
      </code>
    </td>
    
    <td>
      <code>
        (event) => boolean
      </code>
    </td>
    
    <td>
      自定义谓词
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        limit
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      最多返回 N 个最近匹配的事件
    </td>
  </tr>
</tbody>
</table>

## 与网络 drain 结合使用

在本地使用内存适配器，同时在生产环境中发送到可观测性平台：

<framework-tabs>

```typescript [Nuxt]server/plugins/evlog.ts
import { createMemoryDrain } from 'evlog/memory'
import { createAxiomDrain } from 'evlog/axiom'

const memory = createMemoryDrain()
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  if (process.env.NODE_ENV === 'development') {
    await memory(ctx)
  } else {
    await axiom(ctx)
  }
}

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

```typescript [Nitro]server/plugins/evlog.ts
import { definePlugin } from 'nitro'
import { createMemoryDrain } from 'evlog/memory'
import { createAxiomDrain } from 'evlog/axiom'

const memory = createMemoryDrain()
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  if (process.env.NODE_ENV === 'development') {
    await memory(ctx)
  } else {
    await axiom(ctx)
  }
}

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

```typescript [Next.js]lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { createMemoryDrain } from 'evlog/memory'
import { createAxiomDrain } from 'evlog/axiom'

const memory = createMemoryDrain()
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  if (process.env.NODE_ENV === 'development') {
    await memory(ctx)
  } else {
    await axiom(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 { createMemoryDrain } from 'evlog/memory'
import { createAxiomDrain } from 'evlog/axiom'

const memory = createMemoryDrain()
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  if (process.env.NODE_ENV === 'development') {
    await memory(ctx)
  } else {
    await axiom(ctx)
  }
}
export const { handle, handleError } = createEvlogHooks({ drain: drain })
```

```typescript [TanStack Start]server/plugins/evlog.ts
import { definePlugin } from 'nitro'
import { createMemoryDrain } from 'evlog/memory'
import { createAxiomDrain } from 'evlog/axiom'

const memory = createMemoryDrain()
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  if (process.env.NODE_ENV === 'development') {
    await memory(ctx)
  } else {
    await axiom(ctx)
  }
}

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

```typescript [React Router]app/root.tsx
import { evlog } from 'evlog/react-router'
import { createMemoryDrain } from 'evlog/memory'
import { createAxiomDrain } from 'evlog/axiom'

const memory = createMemoryDrain()
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  if (process.env.NODE_ENV === 'development') {
    await memory(ctx)
  } else {
    await axiom(ctx)
  }
}
export const middleware: Route.MiddlewareFunction[] = [
  evlog({ drain: drain }),
]
```

```typescript [Hono]src/index.ts
import { evlog } from 'evlog/hono'
import { createMemoryDrain } from 'evlog/memory'
import { createAxiomDrain } from 'evlog/axiom'

const memory = createMemoryDrain()
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  if (process.env.NODE_ENV === 'development') {
    await memory(ctx)
  } else {
    await axiom(ctx)
  }
}
app.use(evlog({ drain: drain }))
```

```typescript [Express]src/index.ts
import { evlog } from 'evlog/express'
import { createMemoryDrain } from 'evlog/memory'
import { createAxiomDrain } from 'evlog/axiom'

const memory = createMemoryDrain()
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  if (process.env.NODE_ENV === 'development') {
    await memory(ctx)
  } else {
    await axiom(ctx)
  }
}
app.use(evlog({ drain: drain }))
```

```typescript [Fastify]src/index.ts
import { evlog } from 'evlog/fastify'
import { createMemoryDrain } from 'evlog/memory'
import { createAxiomDrain } from 'evlog/axiom'

const memory = createMemoryDrain()
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  if (process.env.NODE_ENV === 'development') {
    await memory(ctx)
  } else {
    await axiom(ctx)
  }
}
await app.register(evlog, { drain: drain })
```

```typescript [Elysia]src/index.ts
import { evlog } from 'evlog/elysia'
import { createMemoryDrain } from 'evlog/memory'
import { createAxiomDrain } from 'evlog/axiom'

const memory = createMemoryDrain()
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  if (process.env.NODE_ENV === 'development') {
    await memory(ctx)
  } else {
    await axiom(ctx)
  }
}
app.use(evlog({ drain: drain }))
```

```typescript [NestJS]src/app.module.ts
import { EvlogModule } from 'evlog/nestjs'
import { createMemoryDrain } from 'evlog/memory'
import { createAxiomDrain } from 'evlog/axiom'

const memory = createMemoryDrain()
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  if (process.env.NODE_ENV === 'development') {
    await memory(ctx)
  } else {
    await axiom(ctx)
  }
}
EvlogModule.forRoot({ drain: drain })
```

```typescript [oRPC]server/orpc.ts
import { withEvlog } from 'evlog/orpc'
import { createMemoryDrain } from 'evlog/memory'
import { createAxiomDrain } from 'evlog/axiom'

const memory = createMemoryDrain()
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  if (process.env.NODE_ENV === 'development') {
    await memory(ctx)
  } else {
    await axiom(ctx)
  }
}
const handler = withEvlog(new RPCHandler(router), { drain: drain })
```

```typescript [Cloudflare Workers]src/worker.ts
import { withEvlog } from 'evlog/workers'
import { createMemoryDrain } from 'evlog/memory'
import { createAxiomDrain } from 'evlog/axiom'

const memory = createMemoryDrain()
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  if (process.env.NODE_ENV === 'development') {
    await memory(ctx)
  } else {
    await axiom(ctx)
  }
}
export default withEvlog(handler, { drain: drain })
```

```typescript [Standalone]src/index.ts
import { initLogger } from 'evlog'
import { createMemoryDrain } from 'evlog/memory'
import { createAxiomDrain } from 'evlog/axiom'

const memory = createMemoryDrain()
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  if (process.env.NODE_ENV === 'development') {
    await memory(ctx)
  } else {
    await axiom(ctx)
  }
}
initLogger({ drain: drain })
```

</framework-tabs>

## 环形缓冲区行为

该缓冲区是**有界的**：一旦达到 `maxEvents`，最旧的事件就会被丢弃，以便为新事件腾出空间。这意味着无论服务运行多久，内存使用量都会保持恒定。

```text [环形缓冲区（maxEvents: 5）]
写入事件 1–5 → [1, 2, 3, 4, 5]
写入事件  6  → [2, 3, 4, 5, 6]  （1 被丢弃）
写入事件 7–8 → [4, 5, 6, 7, 8]
```

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

当 worker/进程重启时，内存缓冲区会丢失。若需要持久化存储，请使用 [文件系统适配器](https://evlog.zhcndoc.com/integrate/adapters/self-hosted/fs)（基于 Node 的运行时）或 [NuxtHub](https://evlog.zhcndoc.com/integrate/adapters/self-hosted/nuxthub)。
:::

## 直接 API 用法

对于高级用例，可以直接调用底层辅助函数：

```typescript [src/index.ts]
import { writeToMemory, readMemoryLogs, clearMemoryLogs, parseReadMemoryLogsQuery } from 'evlog/memory'

// 直接写入事件（跳过 drain 管道）
writeToMemory([event], { store: 'default', maxEvents: 1000 })

// 读取当前缓冲区
const events = readMemoryLogs()

// 将 HTTP 查询参数解析为 ReadMemoryLogsOptions
const opts = parseReadMemoryLogsQuery({ level: 'error', limit: '50' })
// → { level: 'error', limit: 50 }

// 用于测试时重置
clearMemoryLogs()
```

### `parseReadMemoryLogsQuery` 类型转换规则

<table>
<thead>
  <tr>
    <th>
      查询参数
    </th>
    
    <th>
      <code>
        ReadMemoryLogsOptions
      </code>
      
       中的类型
    </th>
    
    <th>
      说明
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        store
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      原样传递
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        since
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      ISO 8601 字符串——由 <code>
        readMemoryLogs
      </code>
      
       解析
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        until
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      ISO 8601 字符串——由 <code>
        readMemoryLogs
      </code>
      
       解析
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        level
      </code>
    </td>
    
    <td>
      <code>
        LogLevel | LogLevel[]
      </code>
    </td>
    
    <td>
      逗号分隔（<code>
        error,warn
      </code>
      
      ）或重复数组；无效值会被丢弃
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        limit
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        parseInt
      </code>
      
      ；NaN → 省略
    </td>
  </tr>
</tbody>
</table>

## 下一步

- [文件系统适配器](https://evlog.zhcndoc.com/integrate/adapters/self-hosted/fs) - 适用于基于 Node 的运行时的持久化本地日志
- [NuxtHub 适配器](https://evlog.zhcndoc.com/integrate/adapters/self-hosted/nuxthub) - 面向 Cloudflare D1 的数据库后端存储
- [流水线](https://evlog.zhcndoc.com/extend/drain-pipeline) - 为任何 drain 添加批处理和重试
- [自定义适配器](https://evlog.zhcndoc.com/extend/custom-drains) - 构建你自己的适配器。

</callout>

---

- [文件系统适配器](https://evlog.zhcndoc.com/integrate/adapters/self-hosted/fs)
- [Hono 集成](https://evlog.zhcndoc.com/integrate/frameworks/hono)


## Sitemap

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