审计记录是否可信,首先取决于三个问题:在事实形成前还是形成后捕获,租户与主体来自哪条可信链路,以及究竟采集了哪些数据。BitzOrcas 不把所有来源硬塞进一套字段,而是先构造类型化记录,再规范化成存储中性的 AuditLogEntry。
1. 生产者与捕获时机
| 生产者 | Category | 捕获时机 | 租户与主体来源 |
|---|---|---|---|
RequestAuditMiddleware | Activity/HTTP | 整个后续 HTTP 管线的 finally | 有效租户上下文 + CurrentUser |
ActivityAuditPipelineBehavior | Activity | Handler 正常返回 Result 后 | CurrentUser;委派用户另存 DelegatedUserId |
| EF Core SaveChanges 拦截器 | EntityChange | 保存前取快照,事务提交后发布 | 实体 TenantId + CurrentUser |
| SqlSugar 写语句审计器 | EntityChange | SQL 成功执行后暂存,事务提交后发布 | Persistence Context |
| Authorization 决策服务 | Security | 策略合并并得出允许/拒绝后 | 授权主体与资源上下文 |
ExternalRequestLoggingHandler | ExternalRequest | 外呼成功或抛出后进入 finally | Effective Tenant + CurrentUser |
| CAP 订阅过滤器 | CapConsumer | 消费成功或异常回调 | 系统主体 cap-consumer、平台租户 0 |
| Application/Quartz 作业审计器 | BackgroundJob | 作业返回或抛出后 | 系统主体、平台租户 0 |
| 全局异常处理与显式 Exception Sink | Exception | 异常边界确定后 | 调用方或 Effective Tenant 上下文 |
同一业务动作可能留下多层证据,例如 HTTP 请求、Mediator Command、实体变更和授权决策。这些记录回答的问题不同,不能简单按 CorrelationId 去重成一条。
2. HTTP 请求审计
RequestAuditMiddleware 位于租户解析之后、授权之前。它在调用下游前给 OTel Activity 写入租户、调用者类型、CorrelationId 和 enduser.id,在 finally 中记录:
- HTTP 方法与经
RequestEndpointClassifier归类的路径; - 最终状态码和耗时;
ActorKey、有效租户与 CorrelationId;- 500 异常结果,同时保持原异常继续传播。
它不读取请求或响应 Body,也不保存 Query 值与 Header。客户端断开后仍以 CancellationToken.None 尝试入队;审计写入失败只记诊断 Warning,不改写原业务响应。
3. Mediator Activity
Activity 管线只在 Handler 正常返回后检查 IResult:成功记录成功状态,业务失败记录稳定错误码。消息是否忽略由 DI Source Generator 把 [AuditIgnore] 投影到 MessageAuditPolicyRegistry;运行时不反射扫描 Attribute,未登记类型默认参与审计。
这里有三条边界:
- Handler 抛异常时不会产生该条 Activity,由 HTTP/Exception 证据补位;
- Sink 故障被捕获,不能让已经完成的业务结果回滚;
- Activity 的 TenantId 是认证主体的所有者租户。委派会额外记录有效用户,但不会把管理面所有权改成目标用户或目标租户。
类型名会随重构变化。监管报表或长期业务分析若需要稳定动作码,应在业务边界记录明确 Activity,而不是把 C# Command 名当永久合同。
4. EF Core 实体变更
EF Core 拦截器在 SaveChanges 前读取映射标量属性,保存成功后先进入事务缓冲区;外层事务真正提交后才发布。保存失败、取消或回滚会丢弃记录,并恢复预增的并发版本。
当前快照规则偏向最小暴露:
- Insert 保存全部映射属性的新值,Update 只保存修改属性的前后值,Delete 保存原值;
- 加密列、名称命中 password/token/phone/email/address 等敏感片段的属性、字节数组与复杂类型统一写
***; - 字符串默认遮蔽,仅 Id、TenantId、状态、类型、版本、币种、区域等结构化字段允许明文;
- TenantId 优先取实体自身,Actor 必须能解析成稳定
ActorKey,否则失败关闭。
CAP 版工作单元把快照批次写入同一事务的 Outbox。非 CAP EF 工作单元则在数据库提交后投递进程内 Sink;后者不会记录未提交变更,但提交后到入队之间仍存在不可恢复窗口。
5. SqlSugar 实体变更
SqlSugar AOP 无法可靠提供字段级前后快照,因此当前实现只从已执行 SQL 的开头解析 INSERT、UPDATE、DELETE 和目标表名:
- 不记录 SQL 文本、参数、OldValues 或 NewValues;
EntityId使用unavailable,不伪造字段级 Diff;- 排除审计、CAP、迁移与 Quartz 等基础设施表;
- 显式事务内先放进 AsyncLocal 缓冲,回滚时丢弃;
- CAP SqlSugar 工作单元在提交前把缓冲批次写进事务 Outbox。
未处于显式事务时,同步 AOP 要求配置 ISynchronousEntityChangeAuditSink;队列已满或关闭会明确失败,不会悄悄丢弃记录。
AuditIgnoreEntityChangeAttribute 目前只有声明,没有被 EF 或 SqlSugar 捕获器读取。Schema 与 Seed 使用的是显式 AuditEmissionScope 抑制,业务代码不要把该 Attribute 当作已经生效的排除机制。
6. 授权、异常与告警
授权记录包含 Resource、Action、CallerType、ClientId、Reason、Trace 和允许结果。SqlSugar 把 Security 与 Exception 都存进 SysSpecialLog,但写入时设置不同 Level,读侧和保留侧也按 Level 分开,不再把 Exception 错投影为 Security。
异常记录的消息、原因和正文仍经过统一脱敏与限长。Fatal/Error 可以触发审计告警;告警是独立通知信号,不是原审计记录的补偿副本,告警投递失败也不得覆盖原异常。
7. 出站 HttpClient
ConfigureHttpClientDefaults 为 IHttpClientFactory 创建的客户端加入 ExternalRequestLoggingHandler。当前处理器刻意不采集请求体和响应体,也不会为了审计重建流内容。它只记录:
- 方法、状态码、耗时与 CorrelationId;
- 去掉 user-info、Query 和 Fragment 后的 URL,最长 2,048 个字符;
- Effective Tenant 与当前 Actor;
- 传输异常以状态码
0和稳定错误摘要表示。
外呼已经是不可逆事实,因此审计收尾使用独立的 5 秒上限,并捕获其失败。即使审计队列长期背压,也不会无限占用外呼连接或把一个已成功的第三方请求伪装成业务失败。
8. CAP 消费与后台作业
CAP 可观测过滤器以 MessageName 作为 Topic,以 ExecutionInstanceId 或 MessageId 计算耗时。成功和失败都会生成 CapConsumerRecord;失败只保存异常类型,不复制异常正文。链路追踪与审计分别尝试,任一路径失败都不改变 CAP 已经确定的消费结果。
后台作业使用独立 BackgroundJobRecord。SqlSugar 虽然把两类记录共存于 SysCommunicationLog,但 RunPars 保存明确类别;Mongo 则使用两个集合。查询与保留两侧都会按这一标记区分。
9. 统一规范化安全边界
AuditBatchIntegrity.NormalizeForPersistence 是 SqlSugar 与 Mongo 共享的最后一道门:
- TenantId、AuditId、Actor、CorrelationId 等标识必须规范且有长度上限;
- 事件时间必须落在共同支持的 SQL Server 时间范围,耗时不能为负;
- 路径、模块、动作和错误消息分别限长;-正文类字段最多 16,384 个字符,先在有界窗口内脱敏,再以
…[truncated]明确标记截断; - JSON 的 password/secret/token/api-key/cookie、Bearer Token、连接串/表单 Secret、URI 密码与支付卡号会被遮蔽;
- 同一
AuditId若承载不同证据会失败关闭,而不是任选一条。
脱敏并不等于允许采集。最安全的顺序仍是:生产者不收集无关敏感数据,规范化做二次防护,读侧按最小字段投影,导出和告警再做一次目的限定。
10. 捕获层验收清单
先用源码检索确认每类事实仍由预期生产者发出,尤其要防止请求正文或 SQL 参数重新进入审计:
# HTTP、Mediator 与外呼捕获点。rg -n "RequestAuditMiddleware|ActivityAuditPipelineBehavior|ExternalRequestLoggingHandler" \ src/Framework src/Hosts -g '*.cs'再锁定事务提交后的实体变更交付与最终规范化入口;这两条链应分别评审,不能只看统一 DTO:
# 双 ORM 事务缓冲、CAP Outbox 与持久化前规范化。rg -n "EntityChangeAudit|CapEntityChangeAuditPublisher|NormalizeForPersistence" \ src/Framework -g '*.cs'- HTTP 200/400/401/403/429/500、异常重抛和客户端断开;
- Activity 成功、业务 Failure、Handler Exception 与编译期
[AuditIgnore]; - EF 无事务/显式事务/CAP 事务的提交、回滚、提交结果未知与敏感字段遮蔽;
- SqlSugar INSERT/UPDATE/DELETE、方言/CTE 无法识别、基础设施表排除和事务缓冲;
- 外呼成功、4xx/5xx、DNS/超时/取消、Query Secret 去除和审计写入超时;
- CAP 成功/异常、重复执行键、Duration、Correlation 和观测故障隔离;
- 非法 TenantId/AuditId、超长正文、冲突重放和嵌套 Secret 脱敏。