A Go library for tailing and parsing Linux audit logs (/var/log/audit/audit.log).
- Event grouping: automatically groups multi-record audit events by kernel event ID
- Multiple subscribers: broadcast events to multiple independent consumers
- Filtering: glob-based filtering across any record field with AND-combined criteria
- Non-blocking: slow subscribers don't block fast ones
- Type-safe models: strongly-typed interfaces for specific event types (SYSCALL, PATH, etc.)
go get github.com/penguinpowernz/auditorpackage main
import (
"context"
"fmt"
"time"
"github.com/penguinpowernz/auditor"
)
func main() {
// Create a tailer
t := auditor.NewTailer("/var/log/audit/audit.log", auditor.Options{
FlushTimeout: 50 * time.Millisecond,
PollInterval: 100 * time.Millisecond,
SeekToEnd: true, // start from current position (live tail)
})
// Subscribe to receive events
events := t.Subscribe()
ctx := context.Background()
go t.Run(ctx)
// Process events
for ev := range events {
fmt.Printf("Event %d at %s: %d records\n",
ev.ID, ev.Timestamp, len(ev.Records))
}
}// Create filtered subscriber - only root execve calls
filtered := t.Subscribe()
matches := auditor.NewFilter().
Where(auditor.FieldFilter{
RecordType: auditor.RecordTypeSyscall,
Field: "euid",
Pattern: "0", // root
}).
Where(auditor.FieldFilter{
RecordType: auditor.RecordTypeSyscall,
Field: "syscall",
Pattern: "59", // execve
}).
Apply(filtered)
for m := range matches {
syscall := m.Event.RecordsByType(auditor.RecordTypeSyscall)[0]
exe := syscall.Fields["exe"]
fmt.Printf("Root executed: %s\n", exe)
}Instead of working with map[string]string fields directly, you can use typed models:
for ev := range events {
for _, rec := range ev.Records {
if rec.Type == auditor.RecordTypeSyscall {
// Get a strongly-typed model for SYSCALL records
syscall := rec.EventModel().(auditor.SyscallModel)
fmt.Printf("UID: %s, GID: %s, Exe: %s\n",
syscall.UID(), syscall.GID(), syscall.Exe())
}
}
}Common interfaces for field groups:
// Any record type with user fields
type UserGetter interface {
UID() string
GID() string
EUID() string
EGID() string
}
// Any record type with process fields
type ProcessGetter interface {
PID() string
PPID() string
Exe() string
Comm() string
}
// Any record type with path fields
type PathGetter interface {
Name() string
Nametype() string
}An audit Event consists of multiple Records grouped by a kernel-assigned event ID:
Event 12345 (timestamp: 2026-04-09 10:30:00)
├─ SYSCALL record: syscall=59 (execve), uid=1000, exe="/usr/bin/ls"
├─ EXECVE record: argv=["ls", "-la"]
├─ CWD record: cwd="/home/user"
└─ PATH record: name="/usr/bin/ls"
Each record is a single line in the audit log. The tailer automatically groups these by event ID.
The tailer uses a broadcast pattern:
- Each call to
Subscribe()returns a new channel - Every event is sent to every subscriber's channel
- Subscribers are independent - a slow subscriber can't block others
- If a subscriber's buffer fills, events are dropped for that subscriber only
type Options struct {
// How long to wait after first record before flushing the event
// Default: 50ms
FlushTimeout time.Duration
// How often to poll the file for new data
// Default: 100ms
PollInterval time.Duration
// Start reading from end of file (tail -f behavior)
// Default: false (read from beginning)
SeekToEnd bool
// Buffer size for each subscriber's channel
// Default: 64
SubscriberBuffer int
}The library includes comprehensive field documentation. Access it via:
doc := auditor.FieldDoc["uid"]
// "User ID of the process that triggered the event"Common fields by record type:
SYSCALL records:
syscall: syscall numberuid,gid: user/group IDeuid,egid: effective user/group IDexe: executable pathpid,ppid: process ID, parent process IDsuccess: "yes" or "no"
PATH records:
name: filesystem pathnametype: NORMAL, CREATE, DELETE, etc.mode: file permissionsogid,ouid: owner group/user ID
EXECVE records:
a0,a1,a2, ...: command-line arguments
CWD records:
cwd: current working directory
See the examples directory for complete working examples:
- cmd/example/main.go - Basic tailing with filters
- cmd/example-models/main.go - Using strongly-typed event models
- Tailer now continues reading after EOF: Fixed an issue where the tailer would stop processing events after reaching EOF. The tailer now correctly continues reading when new events are appended to the log file (issue #1).
- EventModel interface: Added strongly-typed interfaces for accessing record fields, making it easier to work with specific event types without manual map lookups (issue #3).
MIT