Memory 适配器会将宽事件存储在模块级环形缓冲区中。与 文件系统适配器 不同,它没有运行时依赖,并且可以在任何地方运行——包括 Cloudflare Workers(workerd)、Deno Deploy,以及其他不提供 Node fs 模块的边缘运行时。
其主要使用场景是本地开发中的代理访问:在开发期间接入 drain,暴露一个轻量级 HTTP 端点,然后让你的 AI 代理通过 HTTP 获取结构化日志,而无需任何外部工具。
添加内存 drain 适配器
安装
Memory 适配器随 evlog 一起提供:
import { createMemoryDrain, readMemoryLogs } from 'evlog/memory'
快速开始
// 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())
})
// server/plugins/evlog-drain.ts
import { createMemoryDrain } from 'evlog/memory'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createMemoryDrain())
})
// 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(),
})
import { evlog } from 'evlog/express'
import { createMemoryDrain } from 'evlog/memory'
app.use(evlog({ drain: createMemoryDrain() }))
import { evlog } from 'evlog/fastify'
import { createMemoryDrain } from 'evlog/memory'
await app.register(evlog, { drain: createMemoryDrain() })
import { evlog } from 'evlog/elysia'
import { createMemoryDrain } from 'evlog/memory'
app.use(evlog({ drain: createMemoryDrain() }))
import { createMemoryDrain } from 'evlog/memory'
EvlogModule.forRoot({ drain: createMemoryDrain() })
import { createMemoryDrain } from 'evlog/memory'
initLogger({ drain: createMemoryDrain() })
通过 HTTP 访问代理
暴露一个路由,让代理在本地开发会话期间检索结构化日志。使用 parseReadMemoryLogsQuery 让代理可以直接通过查询字符串传递过滤参数:
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 对象的 JSON 数组——与其他所有 evlog 适配器使用的形状相同。
配置
选项
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
maxEvents | number | 1000 | 在环形缓冲区中保留的最大事件数(最旧的会被丢弃) |
store | string | 'default' | 命名缓冲区键——共享同一键的多个 drain 会共享同一个缓冲区 |
// 仅保留最近的 500 个事件
createMemoryDrain({ maxEvents: 500 })
// 使用命名存储以实现隔离
createMemoryDrain({ store: 'my-service' })
环境变量
| 变量 | Nuxt 别名 | 描述 |
|---|---|---|
EVLOG_MEMORY_STORE | NUXT_EVLOG_MEMORY_STORE | 命名缓冲区键(默认:'default') |
EVLOG_MEMORY_MAX_EVENTS | NUXT_EVLOG_MEMORY_MAX_EVENTS | 环形缓冲区大小(默认:1000) |
配置优先级与其他适配器一致:覆盖项 → runtimeConfig.evlog.memory → 环境变量。
命名存储
使用命名存储来隔离来自不同服务的事件,或用于测试:
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 相同的过滤选项:
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 => typeof e.duration === 'string' && e.duration.endsWith('s'),
})
// 最近的 50 个事件
const latest = readMemoryLogs({ limit: 50 })
readMemoryLogs 选项
| 选项 | 类型 | 描述 |
|---|---|---|
store | string | 要读取的命名存储(默认:'default') |
since | Date | string | 仅返回 timestamp >= since 的事件 |
until | Date | string | 仅返回 timestamp <= until 的事件 |
level | LogLevel | LogLevel[] | 按级别过滤 |
filter | (event) => boolean | 自定义谓词 |
limit | number | 最多返回 N 个最近匹配的事件 |
与网络 drain 结合使用
在本地使用内存适配器,同时在生产环境中发送到可观测性平台:
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)
}
}
环形缓冲区行为
该缓冲区是有界的:一旦达到 maxEvents,最旧的事件就会被丢弃,以便为新事件腾出空间。这意味着无论服务运行多久,内存使用量都会保持恒定。
写入事件 1–5 → [1, 2, 3, 4, 5]
写入事件 6 → [2, 3, 4, 5, 6] (1 被丢弃)
写入事件 7–8 → [4, 5, 6, 7, 8]
直接 API 用法
对于高级用例,可以直接调用底层辅助函数:
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 类型转换规则
| 查询参数 | ReadMemoryLogsOptions 中的类型 | 说明 |
|---|---|---|
store | string | 原样传递 |
since | string | ISO 8601 字符串——由 readMemoryLogs 解析 |
until | string | ISO 8601 字符串——由 readMemoryLogs 解析 |
level | LogLevel | LogLevel[] | 逗号分隔(error,warn)或重复数组;无效值会被丢弃 |
limit | number | parseInt;NaN → 省略 |
下一步
- 文件系统适配器 - 适用于基于 Node 的运行时的持久化本地日志
- NuxtHub 适配器 - 面向 Cloudflare D1 的数据库后端存储
- 流水线 - 为任何 drain 添加批处理和重试
- 自定义适配器 - 构建你自己的适配器