69 lines
2.5 KiB
Go
69 lines
2.5 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
|
|
"nearle/models"
|
|
"nearle/repositories"
|
|
"nearle/services/tools"
|
|
)
|
|
|
|
// The database audit sink.
|
|
//
|
|
// `tools.LogAudit` writes a line and nothing else, which is enough to tail
|
|
// during a rollout and useless for the question this trail exists to answer:
|
|
// did anything ever try to read a shop it should not have? That needs rows you
|
|
// can query.
|
|
//
|
|
// ── Why it also logs ────────────────────────────────────────────────────────
|
|
//
|
|
// Both, not either. A database sink that silently stopped writing would take the
|
|
// trail with it and nothing would look wrong; the log line is the thing that
|
|
// keeps working when the table does not, and it costs one line per assistant
|
|
// call — a rate set by people typing questions, not by traffic.
|
|
//
|
|
// ── Why a write failure is swallowed ────────────────────────────────────────
|
|
//
|
|
// `AuditSink.Write` returns nothing, deliberately, so a full disk cannot take
|
|
// the assistant down. A lost row is worse in theory and better in practice than
|
|
// a merchant unable to ask where their orders are because logging broke. The
|
|
// failure is logged loudly, which is the most that can be done without giving
|
|
// the sink a veto over the product.
|
|
type DBAudit struct {
|
|
repo repositories.AssistantAuditRepository
|
|
// The log sink underneath. Kept as the fallback rather than reimplemented.
|
|
line tools.LogAudit
|
|
}
|
|
|
|
func NewDBAudit(repo repositories.AssistantAuditRepository) *DBAudit {
|
|
return &DBAudit{repo: repo}
|
|
}
|
|
|
|
func (a *DBAudit) Write(_ context.Context, entry tools.AuditEntry) {
|
|
a.line.Write(context.Background(), entry)
|
|
|
|
if a.repo == nil {
|
|
return
|
|
}
|
|
err := a.repo.Record(models.AssistantAudit{
|
|
At: entry.At,
|
|
Agent: entry.Agent,
|
|
Tool: entry.Tool,
|
|
Scope: entry.Scope,
|
|
Userid: entry.Userid,
|
|
Tenantid: entry.Tenantid,
|
|
Args: repositories.EncodeAuditArgs(entry.Args),
|
|
Outcome: entry.Outcome,
|
|
Detail: entry.Detail,
|
|
Rows: entry.Rows,
|
|
Tookms: repositories.AuditDuration(entry.Took),
|
|
})
|
|
if err != nil {
|
|
// Loud, because a trail that has quietly stopped recording is worse
|
|
// than no trail: somebody will read the empty table as "nothing
|
|
// happened" rather than as "nothing was written".
|
|
log.Printf("assistant: AUDIT ROW LOST (%s/%s %s): %v", entry.Agent, entry.Tool, entry.Outcome, err)
|
|
}
|
|
}
|