Some checks failed
Go / build-and-release (push) Has been cancelled
- Reinitialize lol loggers after wrapping Writer with BufferedWriter - The lol.Main logger was initialized in init() with os.Stderr directly, bypassing the Writer variable, so we now recreate it with the wrapped Writer - Log level changes now properly affect both the buffer and syslog output Files modified: - app/config/config.go: Reinitialize loggers after BufferedWriter setup - pkg/logbuffer/writer.go: Remove unused stub function 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
140 lines
3.5 KiB
Go
140 lines
3.5 KiB
Go
package logbuffer
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// BufferedWriter wraps an io.Writer and captures log entries
|
|
type BufferedWriter struct {
|
|
original io.Writer
|
|
buffer *Buffer
|
|
lineBuf bytes.Buffer
|
|
}
|
|
|
|
// 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+(.*)$`)
|
|
|
|
// Simple format: "[level] message"
|
|
var simplePattern = regexp.MustCompile(`^\[([A-Z]{3})\]\s+(.*)$`)
|
|
|
|
// NewBufferedWriter creates a new BufferedWriter
|
|
func NewBufferedWriter(original io.Writer, buffer *Buffer) *BufferedWriter {
|
|
return &BufferedWriter{
|
|
original: original,
|
|
buffer: buffer,
|
|
}
|
|
}
|
|
|
|
// Write implements io.Writer
|
|
func (w *BufferedWriter) Write(p []byte) (n int, err error) {
|
|
// Always write to original first
|
|
n, err = w.original.Write(p)
|
|
|
|
// Store in buffer if we have one
|
|
if w.buffer != nil {
|
|
// Accumulate data in line buffer
|
|
w.lineBuf.Write(p)
|
|
|
|
// Process complete lines
|
|
for {
|
|
line, lineErr := w.lineBuf.ReadString('\n')
|
|
if lineErr != nil {
|
|
// Put back incomplete line
|
|
if len(line) > 0 {
|
|
w.lineBuf.WriteString(line)
|
|
}
|
|
break
|
|
}
|
|
|
|
// Parse and store the complete line
|
|
entry := w.parseLine(strings.TrimSuffix(line, "\n"))
|
|
if entry.Message != "" {
|
|
w.buffer.Add(entry)
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// parseLine parses a log line into a LogEntry
|
|
func (w *BufferedWriter) parseLine(line string) LogEntry {
|
|
entry := LogEntry{
|
|
Timestamp: time.Now(),
|
|
Message: line,
|
|
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"
|
|
}
|
|
|
|
return entry
|
|
}
|
|
|
|
// currentLevel tracks the current log level (string)
|
|
var currentLevel = "info"
|
|
|
|
// GetCurrentLevel returns the current log level string
|
|
func GetCurrentLevel() string {
|
|
return currentLevel
|
|
}
|
|
|
|
// SetCurrentLevel sets the current log level and returns it
|
|
func SetCurrentLevel(level string) string {
|
|
level = strings.ToLower(level)
|
|
// Validate level
|
|
switch level {
|
|
case "off", "fatal", "error", "warn", "info", "debug", "trace":
|
|
currentLevel = level
|
|
default:
|
|
currentLevel = "info"
|
|
}
|
|
return currentLevel
|
|
}
|
|
|