审计日志
基于 evlog 的宽事件之上的薄层,提供一等公民级别的审计日志。通过一个 enricher、一个 drain 包装器和一个 helper,为任何应用添加防篡改审计轨迹。

evlog 的审计层不是一个并行系统。审计事件是带有保留 audit 字段的宽事件。所有现有原语——drains、enrichers、redact、tail-sampling——都按原样适用。通过添加1 个 enricher + 1 个 drain 包装器 + 1 个 helper来启用审计日志。

为我的应用添加审计日志

Agent Skills

安装 evlog 技能目录,这样你的助手就可以端到端遵循 build-audit-logs:书面策略、框架接线、withAudit / log.audit、拒绝、脱敏、多租户隔离、防篡改 sink,以及基于 grep 的审查流程。如果你将文件系统 drain 用于审计或通用日志,analyze-logs 会教助手读取 .evlog/logs/ 下的 NDJSON。

Terminal
npx skills add https://www.evlog.dev

查看 Agent Skills 以获取完整列表。仓库中的技能路径:skills/build-audit-logsskills/analyze-logs

为什么需要审计日志?

合规框架(SOC2、HIPAA、GDPR、PCI)要求知道谁在什么时间、从哪里、对哪个资源、做了什么,以及结果如何。evlog 无需第二个日志库就能覆盖这一需求。

审计事件是关于意图的事实,而不是对某个操作的度量。 普通的宽事件回答的是“这个请求表现如何?”(延迟、状态、令牌)。审计事件回答的是“谁试图做什么,以及是否被允许?”同一条管道,不同的问题——这就是为什么 schema 是保留字段,并且事件会强制保留、跳过采样。
tail-sample gate· keep rate 10%
#1POST/api/checkout
random 0.42 < 0.10
dropped
#2GET/api/users
random 0.91 < 0.10
dropped
#3POST/api/refund
audit · force
kept
#4GET/api/me
random 0.07 < 0.10
kept
#5POST/api/login
audit · force
kept
#6PATCH/api/cart
random 0.55 < 0.10
dropped
#7DELETE/api/account
audit · force
kept
#8GET/api/health
random 0.83 < 0.10
dropped
audit kept0 / 3 (100%)
regular kept0 / ~10%
dropped0

快速开始

你已经在使用 evlog 了。只需做三处改动即可添加审计日志:

server/plugins/evlog.ts
import { auditEnricher, auditOnly, signed } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
import { createFsDrain } from 'evlog/fs'

export default defineNitroPlugin((nitro) => {
  nitro.hooks.hook('evlog:enrich', auditEnricher())
  nitro.hooks.hook('evlog:drain', createAxiomDrain())
  nitro.hooks.hook('evlog:drain', auditOnly(
    signed(createFsDrain({ dir: '.audit' }), { strategy: 'hash-chain' }),
    { await: true },
  ))
})
export default defineEventHandler(async (event) => {
  const log = useLogger(event)
  const user = await requireUser(event)
  const invoice = await refundInvoice(getRouterParam(event, 'id'))

  log.audit({
    action: 'invoice.refund',
    actor: { type: 'user', id: user.id, email: user.email },
    target: { type: 'invoice', id: invoice.id },
    outcome: 'success',
    reason: 'Customer requested refund',
  })

  return { ok: true }
})

就是这样。审计事件:

  • 与其余日志一起沿着相同的宽事件管道流动。
  • 会在 tail sampling 之后始终保留
  • 会同时进入你的主 drain(Axiom)以及一个专用的、已签名的、仅追加的 sink(FS journal)。
  • 通过 auditEnricher 自动携带 requestIdtraceIdipuserAgent
为什么需要两个 drains? 主 drain(Axiom、Datadog 等)让审计与其他遥测数据放在一起,从而仍然可以使用仪表板和查询。已签名的 sink 是你的保险:如果主 drain 发生故障、被清理,或者管理员悄悄删除了一条记录,FS journal 仍然保留着这条链。审计人员两者都想要——既要快速查询,又要防篡改证据。
drain pipeline· two drains, one event
active wide event
waiting for events…
main drain
createAxiomDrain()
0events ingested
audit drain
auditOnly(signed(fs))
0audits sealed
main drain · queryable audit drain · tamper-evident · long retention 0 non-audit events filtered out

组合方式

每一层都是可选接入并可替换的。除了 log.auditauditEnricher 以及 auditOnly / signed 之外,其他所有节点都与常规宽事件共享。

audit pipeline · 1 event, 2 sinks·flowing
  1. log.audit / audit / withAudit audit

    callsite

  2. set event.audit audit

    reserved field

  3. force-keep tail-sample audit

    never dropped

  4. auditEnricher() audit

    + requestId · ip · ua

  5. redact + auditRedactPreset shared

    PII scrubbed

main drain

Axiom · Datadog · Sentry · …

auditOnly(signed(fsDrain))

hash-chain · WORM · 7y retention

audit-only layer shared with regular wide events 1 event · 2 sinks · 0 duplicates

接下来去哪

Schema

AuditFields 类型、动作命名约定、actor 类型,以及幂等性。

记录事件

log.auditlog.audit.deny、独立的 audit()withAuditdefineAuditActiondefineAuditCatalogauditDiff

Drains 与完整性

auditEnricherauditOnlysigned(HMAC 和 hash-chain)drain 包装器。

合规

完整性、脱敏预设、GDPR 与仅追加、保留策略,以及常见陷阱。

配方

FS、Axiom 和 Postgres 配方——以及使用 mockAudit 的测试和 API 参考。