---
title: "参考配方与 API 参考"
description: "用于审计日志的文件系统、Axiom 和 Postgres 配方，以及用于测试的 mockAudit 和完整的 API 参考。"
canonical_url: "https://evlog.zhcndoc.com/use-cases/audit/recipes"
---
# 参考配方与 API 参考

> 用于审计日志的文件系统、Axiom 和 Postgres 配方，以及用于测试的 mockAudit 和完整的 API 参考。

选择与你的 drain 匹配的配方，将其放入，你就拥有了一个可检测篡改的审计日志。每个配方都在不同的 drain 上组合相同的基本组件（`auditOnly`、`signed`、可选的 `await: true`）。

## 磁盘上的审计日志

<code-group>

```typescript [输入 — server/plugins/evlog.ts]
import { auditOnly, signed } from 'evlog'
import { createFsDrain } from 'evlog/fs'

nitro.hooks.hook('evlog:drain', auditOnly(
  signed(createFsDrain({ dir: '.audit', maxFiles: 30 }), { strategy: 'hash-chain' }),
  { await: true },
))
```

```jsonl [Output — .audit/2026-04-24.ndjson]
{"audit":{"action":"invoice.refund","actor":{"type":"user","id":"usr_42"},"target":{"type":"invoice","id":"inv_889"},"outcome":"success","version":1,"idempotencyKey":"ak_8f3c4b2a1e5d6f7c","prevHash":null,"hash":"3f2c8e1a..."}}
{"audit":{"action":"user.update","actor":{"type":"user","id":"usr_42"},"target":{"type":"user","id":"usr_99"},"outcome":"success","version":1,"idempotencyKey":"ak_5e7d8f9a0b1c2d3e","prevHash":"3f2c8e1a...","hash":"9a1b4d7c..."}}
```

</code-group>

每一行的 `prevHash` 都与上一行的 `hash` 匹配。篡改任何一行都会破坏该位置之后的链，验证器会重新计算哈希并报告第一个不匹配项。

## 写入专用 Axiom 数据集的审计日志

<code-group>

```typescript [输入 — server/plugins/evlog.ts]
import { auditOnly } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'

nitro.hooks.hook('evlog:drain', createAxiomDrain({ dataset: 'logs' }))
nitro.hooks.hook('evlog:drain', auditOnly(
  createAxiomDrain({ dataset: 'audit', apiKey: process.env.AXIOM_AUDIT_API_KEY }),
))
```

```kusto [输出 — Axiom 查询]
['audit']
| where audit.action == "invoice.refund"
| summarize count() by audit.outcome, bin(_time, 1h)
```

```kusto [输出 — 按 actor 统计的拒绝]
['audit']
| where audit.outcome == "denied"
| summarize count() by audit.actor.id, audit.action
| order by count_ desc
```

</code-group>

拆分数据集意味着审计数据集可以拥有更长的保留期（7y）、更严格的访问控制和独立的计费项目，而不会影响管道的其余部分。

## Postgres 中的审计日志

<code-group>

```typescript [输入 — server/plugins/evlog.ts]
import { auditOnly } from 'evlog'
import type { DrainContext } from 'evlog'

const postgresAudit = async (ctx: DrainContext) => {
  await db.insert(auditEvents).values({
    id: ctx.event.audit!.idempotencyKey,
    timestamp: new Date(ctx.event.timestamp),
    payload: ctx.event,
  }).onConflictDoNothing()
}

nitro.hooks.hook('evlog:drain', auditOnly(postgresAudit, { await: true }))
```

```sql [输出 — audit_events 行]
SELECT id, timestamp, payload->'audit'->>'action' AS action,
       payload->'audit'->>'outcome' AS outcome
FROM audit_events
WHERE id = 'ak_8f3c4b2a1e5d6f7c';

--          id          |       timestamp       |     action      | outcome
-- ---------------------+-----------------------+-----------------+---------
--  ak_8f3c4b2a1e5d6f7c | 2026-04-24 10:23:45.6 | invoice.refund  | success
```

</code-group>

确定性的 `idempotencyKey` 使重试安全：重复插入会通过 `ON CONFLICT DO NOTHING` 合并。如果没有它，重试期间短暂的网络故障就会创建重复的审计行，而这正是你不希望发生的情况。

## 测试你记录的内容

`mockAudit()` 会在发出时捕获每个审计事件，包括来自独立 `audit()`、`log.audit()` 和 `log.set({ audit })` 的事件：

```typescript
import { mockAudit } from 'evlog'

it('退款并记录审计', async () => {
  const captured = mockAudit()

  await refundInvoice({ id: 'inv_889' }, { actor: { type: 'user', id: 'u1' } })

  captured.assertAudit({
    action: 'invoice.refund',
    target: { type: 'invoice', id: 'inv_889' },
    outcome: 'success',
  })

  captured.restore()
})
```

当缺失审计应当以可读消息使测试失败时，优先使用 `assertAudit()`。当你需要在 `expect(...)` 中使用布尔值时，使用 `toIncludeAuditOf()`。

始终在 `afterEach` 中调用 `captured.restore()`（或用 fixture 包裹），这样失败的断言就不会泄漏到下一个测试中。

## 每个审计导出

<table>
<thead>
  <tr>
    <th>
      符号
    </th>
    
    <th>
      类型
    </th>
    
    <th>
      说明
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        AuditFields
      </code>
    </td>
    
    <td>
      类型
    </td>
    
    <td>
      宽事件上的保留字段
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        defineAuditAction(name, opts?)
      </code>
    </td>
    
    <td>
      工厂
    </td>
    
    <td>
      类型化的动作注册表，推断 target 形状
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        defineAuditCatalog(prefix, map)
      </code>
    </td>
    
    <td>
      工厂
    </td>
    
    <td>
      共享前缀的类型化审计动作集合
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        log.audit(fields)
      </code>
    </td>
    
    <td>
      方法
    </td>
    
    <td>
      <code>
        log.set({ audit })
      </code>
      
       的语法糖 + 强制保留
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        log.audit.deny(reason, fields)
      </code>
    </td>
    
    <td>
      方法
    </td>
    
    <td>
      记录被拒绝的动作
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        audit(fields)
      </code>
    </td>
    
    <td>
      函数
    </td>
    
    <td>
      供脚本 / 作业独立使用
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        withAudit({ action, target })(fn)
      </code>
    </td>
    
    <td>
      包装器
    </td>
    
    <td>
      自动发出 success / failure / denied
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        auditDiff(before, after)
      </code>
    </td>
    
    <td>
      助手
    </td>
    
    <td>
      面向 redact 的 JSON Patch，用于 <code>
        changes
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        mockAudit()
      </code>
    </td>
    
    <td>
      测试工具
    </td>
    
    <td>
      在发出时捕获审计；使用 <code>
        assertAudit()
      </code>
      
       或 <code>
        toIncludeAuditOf()
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        auditEnricher(opts?)
      </code>
    </td>
    
    <td>
      enrichr
    </td>
    
    <td>
      自动填充请求 / 运行时 / 租户上下文
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        auditOnly(drain, { await? })
      </code>
    </td>
    
    <td>
      包装器
    </td>
    
    <td>
      仅路由包含 <code>
        audit
      </code>
      
       字段的事件
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        signed(drain, opts)
      </code>
    </td>
    
    <td>
      包装器
    </td>
    
    <td>
      通用完整性包装器（hmac / hash-chain）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        auditRedactPreset
      </code>
    </td>
    
    <td>
      配置
    </td>
    
    <td>
      审计事件的严格 PII 处理
    </td>
  </tr>
</tbody>
</table>

所有内容都从主 `evlog` 入口导出。

---

- [合规](https://evlog.zhcndoc.com/use-cases/audit/compliance)
- [适配器](https://evlog.zhcndoc.com/integrate/adapters/overview)


## Sitemap

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