package main import ( "context" "io" "log" "github.com/alecthomas/kingpin" "github.com/nxadm/tail" ) // A FileLogSource can read lines from a file. type FileLogSource struct { tailer *tail.Tail } // NewFileLogSource creates a new log source, tailing the given file. func NewFileLogSource(path string) (*FileLogSource, error) { tailer, err := tail.TailFile(path, tail.Config{ ReOpen: true, // reopen the file if it's rotated MustExist: true, // fail immediately if the file is missing or has incorrect permissions Follow: true, // run in follow mode Location: &tail.SeekInfo{Whence: io.SeekEnd}, // seek to end of file Logger: tail.DiscardingLogger, }) if err != nil { return nil, err } return &FileLogSource{tailer}, nil } func (s *FileLogSource) Close() error { defer s.tailer.Cleanup() go func() { // Stop() waits for the tailer goroutine to shut down, but it // can be blocking on sending on the Lines channel... for range s.tailer.Lines { } }() return s.tailer.Stop() } func (s *FileLogSource) Path() string { return s.tailer.Filename } func (s *FileLogSource) Read(ctx context.Context) (string, error) { select { case line, ok := <-s.tailer.Lines: if !ok { return "", io.EOF } return line.Text, nil case <-ctx.Done(): return "", ctx.Err() } } // A fileLogSourceFactory is a factory than can create log sources // from command line flags. // // Because this factory is enabled by default, it must always be // registered last. type fileLogSourceFactory struct { path string } func (f *fileLogSourceFactory) Init(app *kingpin.Application) { app.Flag("postfix.logfile_path", "Path where Postfix writes log entries.").Default("/var/log/mail.log").StringVar(&f.path) } func (f *fileLogSourceFactory) New(ctx context.Context) (LogSourceCloser, error) { if f.path == "" { return nil, nil } log.Printf("Reading log events from %s", f.path) return NewFileLogSource(f.path) }