fix(agent): 审计写入失败不再静默
审计日志是证据而不是控制手段:写失败不能让请求失败,否则磁盘写满会变成一次远程 拒绝服务——已经执行成功的指令会因为日志追加不上而开始报错,那是坏错了地方。 但它也不能消失。静默丢审计条目是那种"运维以为自己有记录、其实没有"的失败,而这份 日志的全部意义就是事后能回答"谁在什么时候要求了什么"。现在写失败会进 agent 自己的 日志,磁盘写满表现为一行反复出现,而不是一片空白。 顺带把 Close 之后的语义写清楚:Close 会丢掉 sink,此后的写入返回 nil——这是刻意的, 因为一个正在关闭的进程与在途请求赛跑时,为每一个恰好到达的请求报一次失败是噪声 而不是信息。这条差别在 agent 日志里看得见。 测试用真实失败路径(写一个已关闭的文件)而不是填满文件系统,并断言 exec.record 把 它报了出来。 Go 全包、Node 112 项、Swift 101 项通过。
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -261,11 +262,21 @@ func (e *Executor) refuse(envelope wire.Envelope, inner wire.InnerRequest, class
|
||||
return &Refusal{Class: class, Path: inner.Path, Err: err}
|
||||
}
|
||||
|
||||
// record appends one audit entry, and reports a failure to the process log.
|
||||
//
|
||||
// The write error is not propagated to the caller: the audit log is evidence, not a control, and a
|
||||
// full disk must not become a remote denial of service — a request that already succeeded would start
|
||||
// failing because the log could not be appended to, which is the wrong thing to break.
|
||||
//
|
||||
// But it must not vanish either. Silently losing audit entries is the failure mode where an operator
|
||||
// believes they have a record and does not, and the whole point of the log is to be able to answer
|
||||
// "who asked for what" afterwards. So the error goes to the agent's own log, where a full disk shows
|
||||
// up as a repeating line rather than as an absence.
|
||||
func (e *Executor) record(envelope wire.Envelope, method, path string, status int, outcome string) {
|
||||
if e.audit == nil {
|
||||
return
|
||||
}
|
||||
e.audit.Write(Entry{
|
||||
if err := e.audit.Write(Entry{
|
||||
At: e.clock(),
|
||||
Node: envelope.Node,
|
||||
Req: envelope.Req,
|
||||
@@ -274,7 +285,9 @@ func (e *Executor) record(envelope wire.Envelope, method, path string, status in
|
||||
Path: path,
|
||||
Status: status,
|
||||
Outcome: outcome,
|
||||
})
|
||||
}); err != nil {
|
||||
slog.Warn("audit entry could not be written", "error", err, "req", envelope.Req, "outcome", outcome)
|
||||
}
|
||||
}
|
||||
|
||||
// rateLimiter is a sliding-window counter over the dangerous writes this node has performed.
|
||||
|
||||
Reference in New Issue
Block a user