目录
正在加载目录…
专栏文章
专栏文章
日志专栏
1. 日志体系:类型、Schema 与安全基线 2. 业务与审计日志:领域事件、AOP 与 Outbox 3. 分布式追踪:OTel、MDC 与异步传播 4. 日志平台:采集、存储与 SLO 告警

业务与审计日志:领域事件、AOP 与 Outbox

发布于 2026-08-10 14:48 · 最后编辑于 2026-08-10 14:48 · 字数 1,852 👁 32 次阅读

业务日志应表达领域事实;审计日志则需要可追溯、可验证且受控地保存操作证据。本文说明两者的边界,以及 Domain Probe 与 Spring AOP 的正确落地方式。

目录

章节说明
三类业务记录操作审计、分析事件与变更历史的边界
事件的最小字段Who、When、Target、Action、Result 之外还需什么
Domain Probe用领域语义隔离观测实现
审计日志的可靠性模型事务、Outbox、失败策略与安全
AOP 的适用边界可以自动化什么,不能自动化什么
反模式常见错误与替代方案

三类业务记录

类型目标典型存储丢失与保留策略
操作审计回答“谁改了什么、是否成功、依据是什么”独立审计库或审计事件流通常不可丢;append-only、访问隔离,期限依制度决定
分析事件漏斗、转化、功能使用消息队列/数仓可按场景采样或容忍小比例丢失
变更历史重建对象某些字段的演变历史表或事件流记录字段级 diff,避免无界全量快照

不要把分析埋点写进 ERROR,也不要用普通应用日志替代合规审计记录。

事件的最小字段

要素推荐字段说明
Whoactor.idactor.type操作者身份和角色;姓名等 PII 默认不写
When@timestamp由服务端记录,必要时保留客户端时间为单独字段
Targettarget.typetarget.id被操作资源
Actionevent.name稳定的领域事件名,如 product.price.updated
Resultevent.outcomeerror.codesuccess / failure;存错误码而非直接存敏感异常文本
Correlationtrace_idrequest_id用于关联,不承担用户身份

审计场景可额外记录来源 IP、终端类型、授权结果、变更原因和受控的 changed_fieldsbefore / after 快照必须采用字段白名单、脱敏和大小上限。

Domain Probe

Domain Probe 将“发生了什么”留在业务代码,把日志、指标、事件投递等“如何观测”集中到独立实现中。

public interface OrderProbe {
    void orderPlaced(String orderId, String userId, BigDecimal amount);
    void paymentFailed(String orderId, String errorCode);
}

@Component
public class LoggingOrderProbe implements OrderProbe {
    private static final Logger log = LoggerFactory.getLogger(LoggingOrderProbe.class);

    @Override
    public void orderPlaced(String orderId, String userId, BigDecimal amount) {
        log.info("event=order.placed biz.id={} user.id={} amount={}", orderId, userId, amount);
    }
}

它适合关键领域事件和多信号观测;测试可替换为 in-memory Probe,断言领域调用而不是脆弱的日志文本。它不应承载访问控制或事务一致性等业务职责。

审计日志的可靠性模型

审计事件必须与业务提交建立明确关系。推荐模型是:在业务事务内写入 Outbox 事件;事务提交后由可靠投递器写入审计库/事件流。这样业务失败不会留下“成功审计”,投递短暂失败也可重试。

flowchart LR
    A["业务更新"] --> B["同一数据库事务<br/>更新实体 + 写 Outbox"]
    B --> C["事务提交"]
    C --> D["可靠投递器"]
    D --> E["审计存储<br/>append-only"]

需要预先定义:

  • 审计写入失败策略:高合规操作可 fail-closed;普通后台操作通常 fail-open 并告警、重试与对账。
  • 幂等键:例如 event_id,防止重试产生重复记录。
  • 完整性与访问:独立权限、读导出审计、备份/归档;有防篡改要求时使用不可变存储或签名链。
  • 数据最小化:错误信息和快照不得绕过脱敏、加密、期限和访问审批。

AOP 的适用边界

AOP 注解可用于收集稳定的操作元数据,但不能凭空获得正确的变更快照,也不能替代鉴权和事务设计。

@OperationAudit(action = "product.price.updated", targetId = "#request.productId")
public void updatePrice(UpdatePriceRequest request) {
    productService.updatePrice(request);
}

切面应只构建审计意图并交给事务内的审计服务/Outbox;不要在 finally 里直接把 pjp 参数当成“变更后实体”保存。还要注意 Spring AOP 基于代理:同类自调用、未被代理的方法和某些 final 场景不会触发切面。对关键写操作,应配套集成测试验证审计事件确实在提交后出现。

建议审计表至少包括:event_idoccurred_atactor_idactiontarget_typetarget_idoutcometrace_idchanged_fieldspayload_ref。大快照应存受控对象存储,表中只留引用和哈希。

反模式

反模式风险改进
业务成功后再异步随手写审计审计可丢或与业务状态不一致事务内 Outbox + 可重试投递
finally 无条件写“after 快照”失败请求也可能产生错误事实以提交结果和持久化后的实体构建事件
保存完整请求、异常和实体 JSONPII/密钥泄漏、体积失控字段白名单、脱敏、限长、受控快照引用
仅记录 e.getMessage()错误不稳定且可能泄密记录稳定错误码;运行日志保留受控堆栈
用 AOP 替代鉴权或业务校验绕过路径导致不安全鉴权/校验保持在业务边界,AOP 只做横切记录

参考资料

← 返回列表

评论 (0)

暂无评论,来留下第一条吧。
登录注册 后才能发表评论