dmesgSeverity classified a kernel log line as Critical via plain strings.Contains, without word boundaries, so common words that merely contain a keyword as a substring false-positive — "disabled by default" was flagged Critical because "default" contains "fault" (de-fault). Found in a support bundle where 140/238 event_logs entries came back Critical, most of them harmless boot messages (module load notices, "... is initialized", "disabled by default"), drowning out genuinely critical entries (Xid, AER, ECC) in the same list. Switch to the same \b-bounded regexes already used for the capture patterns above it in this file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
140 lines
3.7 KiB
Go
140 lines
3.7 KiB
Go
package collector
|
|
|
|
import (
|
|
"bee/audit/internal/schema"
|
|
"log/slog"
|
|
"os/exec"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// dmesg -T output: [Thu Jun 18 14:23:45 2026] message
|
|
// dmesg without -T: [ 123.456789] message
|
|
var dmesgTimestampRE = regexp.MustCompile(`^\[([^\]]+)\]\s*(.*)$`)
|
|
|
|
// Keywords that indicate an error or hardware problem worth capturing.
|
|
var dmesgErrorPatterns = []*regexp.Regexp{
|
|
regexp.MustCompile(`(?i)\berr(or)?\b`),
|
|
regexp.MustCompile(`(?i)\bfail(ed|ure)?\b`),
|
|
regexp.MustCompile(`(?i)\bfault\b`),
|
|
regexp.MustCompile(`(?i)\bwarn(ing)?\b`),
|
|
regexp.MustCompile(`(?i)\bAER\b`),
|
|
regexp.MustCompile(`(?i)\bXid\b`),
|
|
regexp.MustCompile(`(?i)\bNVRM\b`),
|
|
regexp.MustCompile(`(?i)\bpanic\b`),
|
|
regexp.MustCompile(`(?i)\bcorrected\b`),
|
|
regexp.MustCompile(`(?i)\buncorrect`),
|
|
regexp.MustCompile(`(?i)\bECC\b`),
|
|
regexp.MustCompile(`(?i)\btimeout\b`),
|
|
regexp.MustCompile(`(?i)\breset\b`),
|
|
regexp.MustCompile(`(?i)\bdead\b`),
|
|
regexp.MustCompile(`(?i)\bhang\b`),
|
|
regexp.MustCompile(`(?i)\bstall\b`),
|
|
regexp.MustCompile(`(?i)\bdisabled\b`),
|
|
}
|
|
|
|
// collectDmesgErrors runs `dmesg -T` (or `dmesg` without -T on failure) and
|
|
// returns only lines that match known error/warning patterns.
|
|
func collectDmesgErrors() []schema.HardwareEventLog {
|
|
out, err := exec.Command("dmesg", "-T").Output()
|
|
if err != nil || len(out) == 0 {
|
|
// Fallback: dmesg without human-readable timestamps
|
|
out, err = exec.Command("dmesg").Output()
|
|
if err != nil || len(out) == 0 {
|
|
return nil
|
|
}
|
|
}
|
|
entries := parseDmesgErrors(string(out))
|
|
if len(entries) == 0 {
|
|
return nil
|
|
}
|
|
slog.Info("dmesg: collected error entries", "count", len(entries))
|
|
return entries
|
|
}
|
|
|
|
func parseDmesgErrors(output string) []schema.HardwareEventLog {
|
|
var entries []schema.HardwareEventLog
|
|
collectedAt := time.Now().UTC().Format(time.RFC3339)
|
|
|
|
for _, line := range strings.Split(output, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
|
|
var timestamp, message string
|
|
if m := dmesgTimestampRE.FindStringSubmatch(line); m != nil {
|
|
timestamp = strings.TrimSpace(m[1])
|
|
message = strings.TrimSpace(m[2])
|
|
} else {
|
|
message = line
|
|
}
|
|
|
|
if message == "" {
|
|
continue
|
|
}
|
|
if !matchesAny(message, dmesgErrorPatterns) {
|
|
continue
|
|
}
|
|
|
|
severity := dmesgSeverity(message)
|
|
source := "dmesg"
|
|
|
|
var eventTime *string
|
|
if timestamp != "" {
|
|
t := timestamp
|
|
eventTime = &t
|
|
} else {
|
|
eventTime = &collectedAt
|
|
}
|
|
|
|
entries = append(entries, schema.HardwareEventLog{
|
|
Source: source,
|
|
EventTime: eventTime,
|
|
Severity: &severity,
|
|
Message: message,
|
|
})
|
|
}
|
|
return entries
|
|
}
|
|
|
|
func matchesAny(s string, patterns []*regexp.Regexp) bool {
|
|
for _, p := range patterns {
|
|
if p.MatchString(s) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// dmesgSeverityCriticalPatterns use \b word boundaries, unlike a plain
|
|
// strings.Contains check, so common words that merely contain a keyword as a
|
|
// substring don't false-positive — e.g. "disabled by default" contains
|
|
// "fault" (de-fault), and "undead" would contain "dead".
|
|
var dmesgSeverityCriticalPatterns = []*regexp.Regexp{
|
|
regexp.MustCompile(`(?i)\bpanic\b`),
|
|
regexp.MustCompile(`(?i)\baer\b`),
|
|
regexp.MustCompile(`(?i)\buncorrect`),
|
|
regexp.MustCompile(`(?i)\bxid\b`),
|
|
regexp.MustCompile(`(?i)\bnvrm\b`),
|
|
regexp.MustCompile(`(?i)\berror\b`),
|
|
regexp.MustCompile(`(?i)\bfault\b`),
|
|
regexp.MustCompile(`(?i)\bfail(ed|ure)?\b`),
|
|
regexp.MustCompile(`(?i)\bdead\b`),
|
|
regexp.MustCompile(`(?i)\bhang\b`),
|
|
}
|
|
|
|
func dmesgSeverity(msg string) string {
|
|
if sev, ok := XidSeverity(msg); ok {
|
|
if sev == "critical" {
|
|
return statusCritical
|
|
}
|
|
return statusWarning
|
|
}
|
|
if matchesAny(msg, dmesgSeverityCriticalPatterns) {
|
|
return statusCritical
|
|
}
|
|
return statusWarning
|
|
}
|