---
title: "文件系统适配器"
description: "将宽事件以 NDJSON 格式写入本地文件系统，用于本地调试、AI 代理集成和生产备份。"
canonical_url: "https://evlog.zhcndoc.com/integrate/adapters/self-hosted/fs"
---
# 文件系统适配器

> 将宽事件以 NDJSON 格式写入本地文件系统，用于本地调试、AI 代理集成和生产备份。

文件系统适配器将宽事件写入本地 NDJSON 文件（每行一个 JSON 对象，每天一个文件）。这使得：

- **AI 代理集成** - 将技能指向 `.evlog/logs/` 以解析结构化日志，用于调试和模式分析
- **本地开发调试** - 无需滚动终端即可获得持久的日志历史记录（`tail -f .evlog/logs/2026-03-14.jsonl`）
- **生产备份** - 与网络排放（Axiom、OTLP）结合，实现本地回退

<prompt :actions="["copy","cursor","claude"]" description="添加文件系统排放适配器" icon="i-lucide-hard-drive">

添加文件系统排放适配器，将 evlog 宽事件以 NDJSON 文件形式本地写入。

1. 确定我正在使用的框架并遵循其 evlog 集成模式
2. 如果尚未安装 evlog，请进行安装
3. 从 'evlog/fs' 导入 createFsDrain
4. 将 createFsDrain() 接入我框架的排放配置
5. 日志默认写入 .evlog/logs/（每天一个文件，自动 .gitignore）
6. 可选配置 dir、maxFiles、maxSizePerFile 或 pretty 选项
7. 通过触发请求进行测试，并检查 .evlog/logs/*.jsonl

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

</prompt>

## 安装

文件系统适配器随 evlog 一起打包提供：

```typescript [src/index.ts]
import { createFsDrain } from 'evlog/fs'
```

## 快速开始

无需凭据或环境变量。只需将排放接入你的框架：

<framework-tabs>

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

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

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

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

```typescript [Next.js]lib/evlog.ts
// Node.js routes only; keep evlog/fs out of root instrumentation.ts
import { createEvlog } from 'evlog/next'
import { createFsDrain } from 'evlog/fs'

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

```typescript [Standalone]
import { createFsDrain } from 'evlog/fs'

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

</framework-tabs>

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

FS 适配器需要 Node.js（`node:fs`）。在 Edge 运行时中，或其目录不可写时，它会记录一次性的 `[evlog/fs]` 警告并跳过写入，因此在无服务器主机上附加它是安全的，但没有实际意义，因为那里只有临时目录可写，且其内容不会在实例生命周期结束后保留。对于这些场景，请使用 `evlog/memory` 或 HTTP 适配器。

</callout>

日志会立即出现在 `.evlog/logs/` 中。

## 文件存放位置

```text [.evlog/logs 目录布局]
.evlog/
  logs/
    2026-03-14.jsonl    ← 每天一个文件
    2026-03-13.jsonl
    2026-03-12.jsonl
```

每个 `.jsonl` 文件包含每行一个 JSON 对象（NDJSON 格式），使其易于解析、搜索和流式处理。

<callout color="success" icon="i-lucide-git-branch">

在首次写入时会自动在 `.evlog/` 祖先目录内（如果存在）或配置的 `dir` 中创建 `.gitignore`。日志文件永远不会提交到版本控制中。

</callout>

## 配置

### 选项

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

<tbody>
  <tr>
    <td>
      <code>
        dir
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '.evlog/logs'
      </code>
    </td>
    
    <td>
      日志文件目录
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        maxFiles
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      保留的最大文件数（自动删除最旧的）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        maxSizePerFile
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      旋转前的文件最大字节数
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pretty
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      美化打印 JSON（多行，可读）
    </td>
  </tr>
</tbody>
</table>

### 示例

```typescript
// Keep only the last 7 days of logs
createFsDrain({ maxFiles: 7 })

// 在 10MB 处旋转文件，保留 30 个文件
createFsDrain({
  maxSizePerFile: 10 * 1024 * 1024,
  maxFiles: 30,
})

// 美化打印以便人工阅读
createFsDrain({ pretty: true })

// 自定义目录
createFsDrain({ dir: '/var/log/myapp' })
```

### 按大小或按天旋转

默认情况下，每天创建一个新文件（`2026-03-14.jsonl`）。当设置了 `maxSizePerFile` 时，适配器会在当前文件超过限制时创建带后缀的文件：

```text [旋转后的日志文件]
.evlog/logs/
  2026-03-14.jsonl      ← 基础文件（完整）
  2026-03-14.1.jsonl    ← 第一次旋转
  2026-03-14.2.jsonl    ← 第二次旋转
```

### 按计划删除旧文件

当设置了 `maxFiles` 时，适配器会在每次写入后自动删除最旧的 `.jsonl` 文件，仅保留最新的文件。

## 写入磁盘并发送到服务

将 FS 适配器与网络排放结合使用以实现本地备份：

<framework-tabs>

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

const fs = createFsDrain({ maxFiles: 7 })
const axiom = createAxiomDrain()

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

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

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

const fs = createFsDrain({ maxFiles: 7 })
const axiom = createAxiomDrain()

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

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

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

const fs = createFsDrain({ maxFiles: 7 })
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  await Promise.allSettled([fs(ctx), 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 { createFsDrain } from 'evlog/fs'
import { createAxiomDrain } from 'evlog/axiom'

const fs = createFsDrain({ maxFiles: 7 })
const axiom = createAxiomDrain()

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

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

const fs = createFsDrain({ maxFiles: 7 })
const axiom = createAxiomDrain()

const drain = async (ctx) => {
  await Promise.allSettled([fs(ctx), 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 { createFsDrain } from 'evlog/fs'
import { createAxiomDrain } from 'evlog/axiom'

const fs = createFsDrain({ maxFiles: 7 })
const axiom = createAxiomDrain()

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

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

const fs = createFsDrain({ maxFiles: 7 })
const axiom = createAxiomDrain()

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

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

const fs = createFsDrain({ maxFiles: 7 })
const axiom = createAxiomDrain()

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

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

const fs = createFsDrain({ maxFiles: 7 })
const axiom = createAxiomDrain()

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

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

const fs = createFsDrain({ maxFiles: 7 })
const axiom = createAxiomDrain()

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

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

const fs = createFsDrain({ maxFiles: 7 })
const axiom = createAxiomDrain()

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

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

const fs = createFsDrain({ maxFiles: 7 })
const axiom = createAxiomDrain()

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

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

const fs = createFsDrain({ maxFiles: 7 })
const axiom = createAxiomDrain()

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

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

const fs = createFsDrain({ maxFiles: 7 })
const axiom = createAxiomDrain()

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

</framework-tabs>

## 读取已写入的内容

### 实时流式传输

```bash [终端]
tail -f .evlog/logs/2026-03-14.jsonl
```

### 使用 jq 搜索

```bash [终端]
# 查找错误
cat .evlog/logs/2026-03-14.jsonl | jq 'select(.level == "error")'

# 慢请求（超过 1 秒）
cat .evlog/logs/2026-03-14.jsonl | jq 'select(.durationMs > 1000)'

# 按路径请求
cat .evlog/logs/2026-03-14.jsonl | jq 'select(.path == "/api/checkout")'
```

### 使用 grep 搜索

```bash [终端]
# 查找所有错误
grep '"level":"error"' .evlog/logs/2026-03-14.jsonl

# 按请求 ID 查找
grep 'req_abc123' .evlog/logs/*.jsonl
```

## 不使用框架调用排放

对于高级用例，使用更低级别的写入函数：

```typescript [src/index.ts]
import { writeToFs, writeBatchToFs } from 'evlog/fs'

await writeToFs(event, {
  dir: '.evlog/logs',
  pretty: false,
})

await writeBatchToFs(events, {
  dir: '.evlog/logs',
  pretty: false,
})
```

## 将文件交给代理

文件系统排放与 [`analyze-logs` 代理技能](https://evlog.zhcndoc.com/reference/agent-skills) 配合使用。安装后，你的 AI 助手可以直接读取 NDJSON 日志，用于调试错误、跟踪请求以及调查性能问题，而无需任何外部工具。

## 下一步

- [代理技能](https://evlog.zhcndoc.com/reference/agent-skills) - 让 AI 分析你的日志
- [Axiom 适配器](https://evlog.zhcndoc.com/integrate/adapters/cloud/axiom) - 将日志发送到 Axiom 以便查询和仪表板展示
- [管道](https://evlog.zhcndoc.com/extend/drain-pipeline) - 为任何排放添加批处理和重试
- [自定义适配器](https://evlog.zhcndoc.com/extend/custom-drains) - 构建你自己的适配器。

---

- [NDJSON 格式](https://github.com/ndjson/ndjson-spec)
- [Axiom 适配器](https://evlog.zhcndoc.com/integrate/adapters/cloud/axiom)


## Sitemap

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