Files
logs/internal/ingest/syslog_parse.go
2026-04-27 19:26:57 +08:00

144 lines
3.2 KiB
Go

package ingest
import (
"fmt"
"regexp"
"strconv"
"strings"
)
var rePri = regexp.MustCompile(`^<(\d{1,3})>`)
type ParsedSyslog struct {
Priority int
Hostname string
Tag string
Message string
RawLine string
}
func parseSyslogPayload(payload []byte) ParsedSyslog {
line := strings.TrimSpace(string(payload))
p := ParsedSyslog{RawLine: line, Message: line}
if line == "" {
return p
}
rest := line
if m := rePri.FindStringSubmatch(line); len(m) == 2 {
if pri, err := strconv.Atoi(m[1]); err == nil {
p.Priority = pri
}
rest = line[len(m[0]):]
}
rest = strings.TrimSpace(rest)
fields := strings.SplitN(rest, " ", 6)
if len(fields) >= 2 && len(fields[0]) == 1 && fields[0][0] >= '1' && fields[0][0] <= '9' {
if len(fields) >= 4 {
p.Hostname = fields[2]
if len(fields) >= 6 {
p.Message = fields[5]
} else if len(fields) == 5 {
p.Message = fields[4]
}
}
return p
}
tokens := strings.SplitN(rest, " ", 3)
if len(tokens) >= 2 {
if len(tokens) >= 3 && isMonthAbbr(tokens[0]) {
parts := strings.Fields(rest)
if len(parts) >= 4 && isDayOfMonth(parts[1]) && isHHMMSS(parts[2]) {
p.Hostname = parts[3]
if len(parts) > 4 {
tagMsg := strings.Join(parts[4:], " ")
if idx := strings.Index(tagMsg, ": "); idx > 0 {
p.Tag = tagMsg[:idx]
p.Message = strings.TrimSpace(tagMsg[idx+2:])
} else {
p.Message = tagMsg
}
}
} else if idx := strings.Index(rest, ": "); idx > 0 {
// 兼容无法严格按 RFC3164 切分的历史格式。
p.Message = strings.TrimSpace(rest[idx+2:])
}
} else {
p.Hostname = tokens[1]
if len(tokens) >= 3 {
tagMsg := tokens[2]
if idx := strings.Index(tagMsg, ": "); idx > 0 {
p.Tag = tagMsg[:idx]
p.Message = strings.TrimSpace(tagMsg[idx+2:])
} else {
p.Message = tagMsg
}
}
}
}
return p
}
func isDayOfMonth(s string) bool {
n, err := strconv.Atoi(s)
if err != nil {
return false
}
return n >= 1 && n <= 31
}
func isHHMMSS(s string) bool {
parts := strings.Split(s, ":")
if len(parts) != 3 {
return false
}
h, err1 := strconv.Atoi(parts[0])
m, err2 := strconv.Atoi(parts[1])
sec, err3 := strconv.Atoi(parts[2])
if err1 != nil || err2 != nil || err3 != nil {
return false
}
return h >= 0 && h <= 23 && m >= 0 && m <= 59 && sec >= 0 && sec <= 59
}
func isMonthAbbr(s string) bool {
if len(s) < 3 {
return false
}
mons := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
for _, m := range mons {
if strings.HasPrefix(s, m) {
return true
}
}
return false
}
func syslogPriorityToSeverity(pri int) string {
sev := pri % 8
switch sev {
case 0, 1, 2:
return "critical"
case 3:
return "major"
case 4:
return "warning"
default:
return "info"
}
}
func formatSyslogSummary(p ParsedSyslog) string {
host := p.Hostname
if host == "" {
host = "unknown-host"
}
return fmt.Sprintf("%s: %s", host, truncate(p.Message, 512))
}
func truncate(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n] + "..."
}