Implementing more advanced feature: audit and more
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
// Package audit records system-affecting actions (signal writes by users or the
|
||||
// control-logic engine, interface and control-logic mutations) to an append-only
|
||||
// SQLite log that audit staff can query later. It is enabled via [audit] in the
|
||||
// config; when disabled a no-op Recorder is used so call sites need no guards.
|
||||
package audit
|
||||
|
||||
import "time"
|
||||
|
||||
// Actor types distinguish human-initiated actions from automated ones.
|
||||
const (
|
||||
ActorUser = "user" // action attributed to an authenticated end-user
|
||||
ActorSystem = "system" // action performed by the control-logic engine
|
||||
)
|
||||
|
||||
// Outcomes record whether the action succeeded.
|
||||
const (
|
||||
OutcomeOK = "ok"
|
||||
OutcomeError = "error"
|
||||
)
|
||||
|
||||
// Event is a single audit record. Optional fields are omitted from the database
|
||||
// when empty. Time defaults to the current time when zero.
|
||||
type Event struct {
|
||||
Time time.Time `json:"time"`
|
||||
Actor string `json:"actor"` // username, or graph name for system actions
|
||||
ActorType string `json:"actorType"` // ActorUser | ActorSystem
|
||||
Action string `json:"action"` // e.g. "signal.write", "interface.update"
|
||||
DS string `json:"ds,omitempty"` // data source (signal writes)
|
||||
Signal string `json:"signal,omitempty"` // signal / target name
|
||||
Value string `json:"value,omitempty"` // serialised written value
|
||||
Detail string `json:"detail,omitempty"` // free-form context (id, graph, trigger)
|
||||
IP string `json:"ip,omitempty"` // client address (user actions)
|
||||
Outcome string `json:"outcome"` // OutcomeOK | OutcomeError
|
||||
Error string `json:"error,omitempty"` // message when Outcome==OutcomeError
|
||||
}
|
||||
|
||||
// Filter selects a subset of events for Query. Zero-valued fields are ignored.
|
||||
type Filter struct {
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Actor string
|
||||
Action string
|
||||
DS string
|
||||
Signal string
|
||||
Limit int // <=0 means a default cap is applied
|
||||
}
|
||||
|
||||
// Recorder appends events and answers queries. Implementations must be safe for
|
||||
// concurrent use and Record must never block the caller for long.
|
||||
type Recorder interface {
|
||||
Record(Event)
|
||||
Query(Filter) ([]Event, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// nopRecorder is used when auditing is disabled. It discards every event.
|
||||
type nopRecorder struct{}
|
||||
|
||||
func (nopRecorder) Record(Event) {}
|
||||
func (nopRecorder) Query(Filter) ([]Event, error) { return []Event{}, nil }
|
||||
func (nopRecorder) Close() error { return nil }
|
||||
|
||||
// Nop returns a Recorder that discards everything. Call sites can hold a Recorder
|
||||
// unconditionally and call Record without checking whether auditing is enabled.
|
||||
func Nop() Recorder { return nopRecorder{} }
|
||||
Reference in New Issue
Block a user