package monitor import ( "fmt" "github.com/nxadm/tail" "go.uber.org/zap" "regexp" ) type Option func(monitor *LogMonitor) // WithTailFollow Continue looking for new lines (tail -f) func WithTailFollow(follow bool) Option { return func(monitor *LogMonitor) { monitor.follow = follow } } // WithReOpen Reopen recreated files (tail -F) func WithReOpen(reopen bool) Option { return func(monitor *LogMonitor) { monitor.reopen = reopen } } type Monitor interface { Close() error Watch(filename string, filter *regexp.Regexp, lineChan chan<- string) error } func New(opts ...Option) *LogMonitor { l := &LogMonitor{ follow: true, reopen: true, cancel: make(chan interface{}), } for _, opt := range opts { opt(l) } return l } type LogMonitor struct { follow bool reopen bool cancel chan interface{} } func (l *LogMonitor) Close() error { close(l.cancel) return nil } func (l *LogMonitor) Watch(filename string, filter *regexp.Regexp, lineChan chan<- string) error { t, err := tail.TailFile(filename, tail.Config{Follow: l.follow, ReOpen: l.reopen, Logger: tail.DiscardingLogger}) if err != nil { return fmt.Errorf("unable to begin to tail file '%s': %w", filename, err) } for { select { case line := <-t.Lines: if line == nil { // EOF t.Cleanup() return nil } if line.Err != nil { zap.S().Errorf("tail error: %v", err) continue } if filter.MatchString(line.Text) { lineChan <- line.Text } case <-l.cancel: t.Stop() t.Cleanup() return nil } } }