watch-tool/file_monitor.go

110 lines
2.4 KiB
Go

package main
import (
"context"
"fmt"
"log/slog"
"regexp"
"strings"
"watch-tool/models"
"watch-tool/parser"
"watch-tool/patterns"
"github.com/hpcloud/tail"
)
type FileMonitor struct {
config ToolConfig
parser parser.Parser
hostname string
}
func NewFileMonitor(config ToolConfig, hostname string) *FileMonitor {
var logParser parser.Parser
if config.Format.Pattern != "" {
compiledRegex, err := regexp.Compile(config.Format.Pattern)
if err != nil {
slog.Error("Invalid regex pattern in tool config", "tool", config.Name, "error", err)
logParser = parser.NewGenericParser(config.Name, hostname)
} else {
gp := parser.NewGenericParser(config.Name, hostname)
customExtractor := patterns.CompiledExtractor{
Name: "config_custom_pattern",
Pattern: compiledRegex,
Fields: config.Format.Fields,
}
gp.Extractors = append(gp.Extractors, customExtractor)
logParser = gp
}
} else {
var err error
logParser, err = parser.New(config.Name, "custom", hostname)
if err != nil {
slog.Error("Cannot get tool specific parser from factory", "error", err)
logParser = parser.NewGenericParser(config.Name, hostname)
}
}
return &FileMonitor{
config: config,
parser: logParser,
hostname: hostname,
}
}
func (fm *FileMonitor) Start(ctx context.Context, out chan<- models.LogMessage) error {
t, err := tail.TailFile(fm.config.LogFile, tail.Config{
Follow: true,
ReOpen: true,
MustExist: false,
Poll: true,
Location: &tail.SeekInfo{Offset: 0, Whence: 2},
})
if err != nil {
return fmt.Errorf("tail.TailFile: %w", err)
}
defer t.Stop()
slog.Debug("Started tailing file", "file", fm.config.LogFile, "tool", fm.config.Name)
for {
select {
case <-ctx.Done():
slog.Debug("File monitor stopped", "tool", fm.config.Name)
return nil
case line, ok := <-t.Lines:
if !ok {
return nil
}
if line.Err != nil {
slog.Error("Error reading log file", "tool", fm.config.Name, "error", line.Err)
continue
}
if strings.TrimSpace(line.Text) == "" {
continue
}
entry, err := fm.parser.Parse(line.Text)
if err != nil {
slog.Error("Error parsing log line", "tool", fm.config.Name, "error", err)
} else {
if entry.Tool == "" {
entry.Tool = fm.config.Name
}
}
select {
case out <- entry:
case <-ctx.Done():
return nil
default:
slog.Warn("Log-Channel is full, entry dropped", "tool", fm.config.Name)
}
}
}
}