From 678a228fb8a8565017b0dd5112ef5c379bf0e3dc Mon Sep 17 00:00:00 2001 From: mleku Date: Thu, 25 Dec 2025 14:11:29 +0100 Subject: [PATCH] Fix log parser to match lol library format (v0.38.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lol library outputs logs in format: 1703500000000000â„šī¸ message /path/to/file.go:123 Where: - Timestamp is Unix microseconds - Level is emoji (â˜ ī¸đŸš¨âš ī¸â„šī¸đŸ”ŽđŸ‘ģ) - Message text - File:line location Updated parser to correctly parse this format. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- pkg/logbuffer/writer.go | 116 +++++++++++++++++++++++++--------------- pkg/version/version | 2 +- 2 files changed, 74 insertions(+), 44 deletions(-) diff --git a/pkg/logbuffer/writer.go b/pkg/logbuffer/writer.go index 7502305..bdbc508 100644 --- a/pkg/logbuffer/writer.go +++ b/pkg/logbuffer/writer.go @@ -17,12 +17,15 @@ type BufferedWriter struct { } // Log format regex patterns -// lol library format: "2024/01/15 10:30:45 file.go:123 [INF] message" -// or similar variations -var logPattern = regexp.MustCompile(`^(\d{4}/\d{2}/\d{2}\s+\d{2}:\d{2}:\d{2}(?:\.\d+)?)\s+([^\s:]+):(\d+)\s+\[([A-Z]{3})\]\s+(.*)$`) +// lol library format: "1703500000000000â„šī¸ message /path/to/file.go:123" +// - Unix microseconds timestamp +// - Level emoji (â˜ ī¸, 🚨, âš ī¸, â„šī¸, 🔎, đŸ‘ģ) +// - Message +// - File:line location +var lolPattern = regexp.MustCompile(`^(\d{16})([â˜ ī¸đŸš¨âš ī¸â„šī¸đŸ”ŽđŸ‘ģ]+)\s*(.*?)\s+([^\s]+:\d+)$`) -// Simple format: "[level] message" -var simplePattern = regexp.MustCompile(`^\[([A-Z]{3})\]\s+(.*)$`) +// Simpler pattern for when emoji detection fails - just capture timestamp and rest +var simplePattern = regexp.MustCompile(`^(\d{13,16})\s*(.*)$`) // NewBufferedWriter creates a new BufferedWriter func NewBufferedWriter(original io.Writer, buffer *Buffer) *BufferedWriter { @@ -64,6 +67,16 @@ func (w *BufferedWriter) Write(p []byte) (n int, err error) { return } +// emojiToLevel maps lol library level emojis to level strings +var emojiToLevel = map[string]string{ + "â˜ ī¸": "FTL", + "🚨": "ERR", + "âš ī¸": "WRN", + "â„šī¸": "INF", + "🔎": "DBG", + "đŸ‘ģ": "TRC", +} + // parseLine parses a log line into a LogEntry func (w *BufferedWriter) parseLine(line string) LogEntry { entry := LogEntry{ @@ -72,47 +85,64 @@ func (w *BufferedWriter) parseLine(line string) LogEntry { Level: "INF", } - // Try full pattern first - if matches := logPattern.FindStringSubmatch(line); matches != nil { - // Parse timestamp - if t, err := time.Parse("2006/01/02 15:04:05", matches[1]); err == nil { - entry.Timestamp = t - } else if t, err := time.Parse("2006/01/02 15:04:05.000", matches[1]); err == nil { - entry.Timestamp = t - } - - entry.File = matches[2] - if lineNum, err := strconv.Atoi(matches[3]); err == nil { - entry.Line = lineNum - } - entry.Level = matches[4] - entry.Message = matches[5] - return entry - } - - // Try simple pattern - if matches := simplePattern.FindStringSubmatch(line); matches != nil { - entry.Level = matches[1] - entry.Message = matches[2] - return entry - } - - // Detect level from common prefixes line = strings.TrimSpace(line) - if strings.HasPrefix(line, "TRC") || strings.HasPrefix(line, "[TRC]") { - entry.Level = "TRC" - } else if strings.HasPrefix(line, "DBG") || strings.HasPrefix(line, "[DBG]") { - entry.Level = "DBG" - } else if strings.HasPrefix(line, "INF") || strings.HasPrefix(line, "[INF]") { - entry.Level = "INF" - } else if strings.HasPrefix(line, "WRN") || strings.HasPrefix(line, "[WRN]") { - entry.Level = "WRN" - } else if strings.HasPrefix(line, "ERR") || strings.HasPrefix(line, "[ERR]") { - entry.Level = "ERR" - } else if strings.HasPrefix(line, "FTL") || strings.HasPrefix(line, "[FTL]") { - entry.Level = "FTL" + if line == "" { + return entry } + // Try lol pattern first: "1703500000000000â„šī¸ message /path/to/file.go:123" + if matches := lolPattern.FindStringSubmatch(line); matches != nil { + // Parse Unix microseconds timestamp + if usec, err := strconv.ParseInt(matches[1], 10, 64); err == nil { + entry.Timestamp = time.UnixMicro(usec) + } + + // Map emoji to level + if level, ok := emojiToLevel[matches[2]]; ok { + entry.Level = level + } + + entry.Message = strings.TrimSpace(matches[3]) + + // Parse file:line + loc := matches[4] + if idx := strings.LastIndex(loc, ":"); idx > 0 { + entry.File = loc[:idx] + if lineNum, err := strconv.Atoi(loc[idx+1:]); err == nil { + entry.Line = lineNum + } + } + return entry + } + + // Try simple pattern - just grab timestamp and rest as message + if matches := simplePattern.FindStringSubmatch(line); matches != nil { + if usec, err := strconv.ParseInt(matches[1], 10, 64); err == nil { + // Could be microseconds or milliseconds + if len(matches[1]) >= 16 { + entry.Timestamp = time.UnixMicro(usec) + } else { + entry.Timestamp = time.UnixMilli(usec) + } + } + rest := strings.TrimSpace(matches[2]) + + // Try to detect level from emoji in the rest + for emoji, level := range emojiToLevel { + if strings.HasPrefix(rest, emoji) { + entry.Level = level + rest = strings.TrimPrefix(rest, emoji) + rest = strings.TrimSpace(rest) + break + } + } + + entry.Message = rest + return entry + } + + // Fallback: just store the whole line as message + entry.Message = line return entry } diff --git a/pkg/version/version b/pkg/version/version index 765098d..b4a466a 100644 --- a/pkg/version/version +++ b/pkg/version/version @@ -1 +1 @@ -v0.38.0 +v0.38.1