mirror of
https://github.com/amir20/dozzle.git
synced 2026-06-23 04:10:12 +00:00
fix(level-guesser): recognize Zigbee2MQTT-style log levels
Detect ` <level>:` (e.g. `[ts] info: msg`) and `:<level> ` (e.g. `Zigbee2MQTT:info msg`) so containers with these formats get a real level instead of falling through to "unknown". Also collapse the per-level pattern struct into a single combined regex per level (6 patterns total instead of 30) and drop the unreachable map[string]any/map[string]string panic branches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,15 +23,20 @@ var logLevels = [][]string{
|
||||
// aliasToCanonical maps every alias to its canonical level name.
|
||||
var aliasToCanonical = map[string]string{}
|
||||
|
||||
type levelPatterns struct {
|
||||
plain *regexp.Regexp // e.g. ^error[^a-z]
|
||||
bracket *regexp.Regexp // e.g. [ error ]
|
||||
separator *regexp.Regexp // e.g. " error/"
|
||||
quoted *regexp.Regexp // e.g. "ERROR"
|
||||
spaced *regexp.Regexp // e.g. " ERROR "
|
||||
}
|
||||
|
||||
var levelRegexes = map[string]levelPatterns{}
|
||||
// levelRegexes holds one combined regex per canonical level. Each regex is an
|
||||
// alternation of all the shapes a level can take in a log line:
|
||||
//
|
||||
// (?i:^<alt>[^a-z] // plain prefix: "error: ..."
|
||||
// |\[ ?<alt> ?\] // bracketed: "[ERROR]" / "[ error ]"
|
||||
// | <alt>[/|:-] // separator: " error|", " info:" (z2m)
|
||||
// |:<alt>\s) // colon prefix: "Tag:info " (z2m)
|
||||
// |"<UPPER>" // quoted: "\"ERROR\""
|
||||
// |\s<UPPER>\s // spaced: " ERROR "
|
||||
//
|
||||
// The case-insensitive group covers the boundary-anchored forms; the trailing
|
||||
// uppercase-only branches catch mid-line `ERROR` tokens without false-firing on
|
||||
// the word "error" in prose.
|
||||
var levelRegexes = map[string]*regexp.Regexp{}
|
||||
|
||||
// singleLetterBracket matches single-letter levels in brackets, e.g. [I], [E], [W]
|
||||
var singleLetterBracket = regexp.MustCompile(`\[([EWIDFTV])\]`)
|
||||
@@ -51,19 +56,16 @@ func init() {
|
||||
}
|
||||
|
||||
alt := "(?:" + strings.Join(group, "|") + ")"
|
||||
upperAlt := make([]string, len(group))
|
||||
for i, l := range group {
|
||||
upperAlt[i] = strings.ToUpper(l)
|
||||
}
|
||||
upperGroup := "(?:" + strings.Join(upperAlt, "|") + ")"
|
||||
upper := strings.ToUpper(alt)
|
||||
|
||||
levelRegexes[canonical] = levelPatterns{
|
||||
plain: regexp.MustCompile("(?i)^" + alt + "[^a-z]"),
|
||||
bracket: regexp.MustCompile("(?i)\\[ ?" + alt + " ?\\]"),
|
||||
separator: regexp.MustCompile("(?i) " + alt + "[/|-]"),
|
||||
quoted: regexp.MustCompile("\"" + upperGroup + "\""),
|
||||
spaced: regexp.MustCompile(" " + upperGroup + " "),
|
||||
}
|
||||
levelRegexes[canonical] = regexp.MustCompile(
|
||||
`(?i:^` + alt + `[^a-z]` +
|
||||
`|\[ ?` + alt + ` ?\]` +
|
||||
`| ` + alt + `[/|:-]` +
|
||||
`|:` + alt + `\s)` +
|
||||
`|"` + upper + `"` +
|
||||
`|\s` + upper + `\s`,
|
||||
)
|
||||
}
|
||||
SupportedLogLevels["unknown"] = struct{}{}
|
||||
}
|
||||
@@ -95,12 +97,6 @@ func guessLogLevel(logEvent *LogEvent) string {
|
||||
}
|
||||
}
|
||||
|
||||
case map[string]any:
|
||||
panic("not implemented")
|
||||
|
||||
case map[string]string:
|
||||
panic("not implemented")
|
||||
|
||||
default:
|
||||
log.Debug().Type("type", value).Msg("unknown logEvent type")
|
||||
}
|
||||
@@ -122,8 +118,7 @@ func guessFromString(value string) string {
|
||||
value = StripANSI(value)
|
||||
value = timestampRegex.ReplaceAllString(value, "")
|
||||
for _, group := range logLevels {
|
||||
p := levelRegexes[group[0]]
|
||||
if p.plain.MatchString(value) || p.bracket.MatchString(value) || p.separator.MatchString(value) || p.quoted.MatchString(value) || p.spaced.MatchString(value) {
|
||||
if levelRegexes[group[0]].MatchString(value) {
|
||||
return group[0]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,15 @@ func TestGuessLogLevel(t *testing.T) {
|
||||
orderedmap.Pair[string, string]{Key: "@t", Value: "2024-01-01T00:00:00Z"},
|
||||
),
|
||||
), "error"},
|
||||
// Zigbee2MQTT-style: bracketed timestamp + " <level>:" inside the line.
|
||||
{"[2025-12-22 12:00:00] info: z2m: started", "info"},
|
||||
{"[2025-12-22 12:00:00] warn: z2m: queue full", "warn"},
|
||||
{"[2025-12-22 12:00:00] error: z2m: connection failed", "error"},
|
||||
{"[2025-12-22 12:00:00] debug: z2m: handling message", "debug"},
|
||||
// "<tag>:<level> " style (no space before the colon).
|
||||
{"Zigbee2MQTT:info 2025-12-22 12:00:00: started", "info"},
|
||||
{"Zigbee2MQTT:warn 2025-12-22 12:00:00: queue full", "warn"},
|
||||
{"Zigbee2MQTT:error 2025-12-22 12:00:00: failure", "error"},
|
||||
// Pipe-delimited
|
||||
{"2024-01-01 12:00:00 | ERROR | something went wrong", "error"},
|
||||
{"2024-01-01 12:00:00 | INFO | starting up", "info"},
|
||||
|
||||
Reference in New Issue
Block a user