Linux audit (auditd) source and LogStream internal logs
Audit source (source_type=audit), off until enabled in Settings > Sources: - follows /var/log/audit/audit.log (path configurable, AUDIT_LOG), with rotation (end of audit.log.1 read first) and an offset checkpoint in /data/audit-state.json; last AUDIT_BACKFILL (1h) read on first enable - groups the records of an event by msg=audit(time:serial), closed on EOE, after 2 s without a record, or once the log has moved 2 s past it - fields audit_type, audit_serial, auid, user (enriched name, host /etc/passwd or uid), exe, res, key, audit_raw; app = audit_type, host = node= / AUDIT_HOST / /host/etc/hostname; summary "who: what [key] → result"; warning when res=failed / success=no, info otherwise - hexadecimal values (proctitle, cmd, exec arguments…) decoded Internal logs (source_type=logstream, host=logstream, app=component): - logEvent() for VictoriaLogs unreachable / back, lost batches, spooled batches refused, syslog listener errors, Docker API and container streams lost, failing sources, start and stop - on by default from warning; switch and minimum level in Settings > Sources (/data/selflog.json) - bounded queue, never waits for the Store, same event at most once per 30 s with a count of the repeats: a failing Store cannot loop UI: violet (audit) and turquoise (LogStream) left edge and tint, AUDIT / LS badge before the app, raw audit record under the summary (folded in compact density until the row is opened), Audit and LogStream in the source filter, FR/EN translations. docker-compose: optional /var/log/audit, /etc/passwd and /etc/hostname mounts. README (EN/FR): host prerequisites (log_group, ACL for UID 10001). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f3373e1f66
commit
076c01aac4
20 files changed
+1948
-36
No files matched your search
+102
@@ -0,0 +1,102 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSelfLogEmit(t *testing.T) {
|
||||
now := time.Unix(1759572000, 0)
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg selfLogConfig
|
||||
events []int // severities, one second apart, same message format
|
||||
want int // entries queued
|
||||
suffix string
|
||||
}{
|
||||
{"warning and above", selfLogConfig{Enabled: true, MinLevel: "warning"}, []int{3}, 1, ""},
|
||||
{"below the level", selfLogConfig{Enabled: true, MinLevel: "warning"}, []int{5, 6}, 0, ""},
|
||||
{"info level", selfLogConfig{Enabled: true, MinLevel: "info"}, []int{6}, 1, ""},
|
||||
{"disabled", selfLogConfig{Enabled: false, MinLevel: "debug"}, []int{0}, 0, ""},
|
||||
{"bad level falls back to warning", normSelfLogConfig(selfLogConfig{Enabled: true, MinLevel: "loud"}), []int{4, 5}, 1, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := NewSelfLog(t.TempDir(), func(*Entry) {})
|
||||
s.cfg = tt.cfg
|
||||
for i, sev := range tt.events {
|
||||
s.Emit("store", sev, "fmt %d", "msg", now.Add(time.Duration(i)*time.Second))
|
||||
}
|
||||
if len(s.queue) != tt.want {
|
||||
t.Fatalf("%d queued, want %d", len(s.queue), tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Same component and format: once per interval, then a count of the others.
|
||||
s := NewSelfLog(t.TempDir(), func(*Entry) {})
|
||||
for i := 0; i < 5; i++ {
|
||||
s.Emit("store", 3, "%d messages lost", "x", now.Add(time.Duration(i)*time.Second))
|
||||
}
|
||||
s.Emit("syslog", 3, "%d messages lost", "y", now) // another component
|
||||
s.Emit("store", 3, "%d messages lost", "z", now.Add(s.interval))
|
||||
if len(s.queue) != 3 {
|
||||
t.Fatalf("%d queued", len(s.queue))
|
||||
}
|
||||
<-s.queue
|
||||
if e := <-s.queue; e.App != "syslog" || e.Host != "logstream" || e.SourceType != "logstream" || e.Wait {
|
||||
t.Fatalf("entry %+v", e)
|
||||
}
|
||||
if e := <-s.queue; e.Message != "z (4 similar events not shown)" || e.Severity != "err" {
|
||||
t.Fatalf("message %q", e.Message)
|
||||
}
|
||||
if st := s.Status(); st["throttled"] != uint64(4) {
|
||||
t.Fatalf("status %v", st)
|
||||
}
|
||||
|
||||
// A full queue drops events instead of blocking.
|
||||
s = NewSelfLog(t.TempDir(), func(*Entry) {})
|
||||
for i := 0; i < cap(s.queue)+10; i++ {
|
||||
s.Emit("store", 3, "event %d", "x", now.Add(time.Duration(i)*s.interval))
|
||||
}
|
||||
if st := s.Status(); st["dropped"] != uint64(10) {
|
||||
t.Fatalf("status %v", st)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelfLogRunAndConfigure(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
sink := &entrySink{}
|
||||
s := NewSelfLog(dir, sink.add)
|
||||
if err := s.Configure(selfLogConfig{Enabled: true, MinLevel: "notice"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if b, _ := os.ReadFile(filepath.Join(dir, "selflog.json")); !strings.Contains(string(b), `"notice"`) {
|
||||
t.Fatalf("saved %s", b)
|
||||
}
|
||||
if s2 := NewSelfLog(dir, nil); s2.cfg.MinLevel != "notice" {
|
||||
t.Fatalf("reloaded %+v", s2.cfg)
|
||||
}
|
||||
old := selfLog
|
||||
selfLog = s
|
||||
defer func() { selfLog = old }()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan struct{})
|
||||
go func() { s.Run(ctx); close(done) }()
|
||||
logEvent("main", 5, "LogStream started")
|
||||
logEvent("main", 6, "too verbose")
|
||||
cancel()
|
||||
<-done
|
||||
logEvent("main", 5, "LogStream stopping") // queued after Run: not lost if Run drains it
|
||||
sink.mu.Lock()
|
||||
n := len(sink.es)
|
||||
sink.mu.Unlock()
|
||||
if n != 1 || s.Status()["emitted"] != uint64(1) {
|
||||
t.Fatalf("%d entries, status %v", n, s.Status())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user