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>
146 lines
3.3 KiB
Go
146 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
)
|
|
|
|
// syslogConfig is saved in /data/syslog.json and edited in Settings > Sources.
|
|
type syslogConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
UDP bool `json:"udp"`
|
|
TCP bool `json:"tcp"`
|
|
}
|
|
|
|
// SyslogServer owns the UDP and TCP listeners and opens or closes them when
|
|
// the configuration changes, without restarting Logstream.
|
|
type SyslogServer struct {
|
|
parent context.Context
|
|
addr string // listening address inside the container, e.g. ":5514"
|
|
publicPort string // port published on the host by docker-compose (display only)
|
|
sink func(*Entry)
|
|
cfgPath string
|
|
|
|
mu sync.Mutex
|
|
cfg syslogConfig
|
|
cancel context.CancelFunc
|
|
pc net.PacketConn
|
|
ln net.Listener
|
|
errUDP string
|
|
errTCP string
|
|
}
|
|
|
|
func NewSyslogServer(ctx context.Context, addr, publicPort, dataDir string, sink func(*Entry)) *SyslogServer {
|
|
s := &SyslogServer{
|
|
parent: ctx,
|
|
addr: addr,
|
|
publicPort: publicPort,
|
|
sink: sink,
|
|
cfgPath: filepath.Join(dataDir, "syslog.json"),
|
|
cfg: syslogConfig{Enabled: true, UDP: true, TCP: true},
|
|
}
|
|
if b, err := os.ReadFile(s.cfgPath); err == nil {
|
|
var cfg syslogConfig
|
|
if err := json.Unmarshal(b, &cfg); err == nil {
|
|
s.cfg = cfg
|
|
}
|
|
}
|
|
s.apply()
|
|
go func() {
|
|
<-ctx.Done()
|
|
s.mu.Lock()
|
|
s.closeLocked()
|
|
s.mu.Unlock()
|
|
}()
|
|
return s
|
|
}
|
|
|
|
// closeLocked stops the listeners synchronously, so that the port is free
|
|
// before it is opened again; the caller holds the lock.
|
|
func (s *SyslogServer) closeLocked() {
|
|
if s.cancel != nil {
|
|
s.cancel() // also closes the open TCP connections
|
|
s.cancel = nil
|
|
}
|
|
if s.pc != nil {
|
|
s.pc.Close()
|
|
s.pc = nil
|
|
}
|
|
if s.ln != nil {
|
|
s.ln.Close()
|
|
s.ln = nil
|
|
}
|
|
}
|
|
|
|
// apply opens the listeners required by the configuration.
|
|
func (s *SyslogServer) apply() {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.closeLocked()
|
|
s.errUDP, s.errTCP = "", ""
|
|
if !s.cfg.Enabled || s.parent.Err() != nil {
|
|
log.Printf("syslog: reception disabled")
|
|
return
|
|
}
|
|
ctx, cancel := context.WithCancel(s.parent)
|
|
s.cancel = cancel
|
|
var open []string
|
|
if s.cfg.UDP {
|
|
pc, err := net.ListenPacket("udp", s.addr)
|
|
if err != nil {
|
|
s.errUDP = err.Error()
|
|
logEvent("syslog", 3, "UDP listener failed: %v", err)
|
|
} else {
|
|
if uc, ok := pc.(*net.UDPConn); ok {
|
|
_ = uc.SetReadBuffer(8 << 20)
|
|
}
|
|
s.pc = pc
|
|
go serveUDP(ctx, pc, s.sink)
|
|
open = append(open, "udp")
|
|
}
|
|
}
|
|
if s.cfg.TCP {
|
|
ln, err := net.Listen("tcp", s.addr)
|
|
if err != nil {
|
|
s.errTCP = err.Error()
|
|
logEvent("syslog", 3, "TCP listener failed: %v", err)
|
|
} else {
|
|
s.ln = ln
|
|
go serveTCP(ctx, ln, s.sink)
|
|
open = append(open, "tcp")
|
|
}
|
|
}
|
|
log.Printf("syslog: listening on %s %v", s.addr, open)
|
|
}
|
|
|
|
// Configure saves and applies a new configuration.
|
|
func (s *SyslogServer) Configure(cfg syslogConfig) error {
|
|
s.mu.Lock()
|
|
s.cfg = cfg
|
|
s.mu.Unlock()
|
|
s.apply()
|
|
return writeJSONFile(s.cfgPath, cfg)
|
|
}
|
|
|
|
// Status is the state shown in Settings > Sources.
|
|
func (s *SyslogServer) Status() map[string]any {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return map[string]any{
|
|
"enabled": s.cfg.Enabled,
|
|
"udp": s.cfg.UDP,
|
|
"tcp": s.cfg.TCP,
|
|
"udpListening": s.pc != nil,
|
|
"tcpListening": s.ln != nil,
|
|
"udpError": s.errUDP,
|
|
"tcpError": s.errTCP,
|
|
"port": s.publicPort,
|
|
"internalAddr": s.addr,
|
|
}
|
|
}
|