1// Copyright (c) 2019 FOSS contributors of https://github.com/nxadm/tail
2// Copyright (c) 2015 HPE Software Inc. All rights reserved.
3// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
4
5//nxadm/tail provides a Go library that emulates the features of the BSD `tail`
6//program. The library comes with full support for truncation/move detection as
7//it is designed to work with log rotation tools. The library works on all
8//operating systems supported by Go, including POSIX systems like Linux and
9//*BSD, and MS Windows. Go 1.9 is the oldest compiler release supported.
10package tail
11
12import (
13	"bufio"
14	"errors"
15	"fmt"
16	"io"
17	"io/ioutil"
18	"log"
19	"os"
20	"strings"
21	"sync"
22	"time"
23
24	"github.com/nxadm/tail/ratelimiter"
25	"github.com/nxadm/tail/util"
26	"github.com/nxadm/tail/watch"
27	"gopkg.in/tomb.v1"
28)
29
30var (
31	// ErrStop is returned when the tail of a file has been marked to be stopped.
32	ErrStop = errors.New("tail should now stop")
33)
34
35type Line struct {
36	Text     string    // The contents of the file
37	Num      int       // The line number
38	SeekInfo SeekInfo  // SeekInfo
39	Time     time.Time // Present time
40	Err      error     // Error from tail
41}
42
43// Deprecated: this function is no longer used internally and it has little of no
44// use in the API. As such, it will be removed from the API in a future major
45// release.
46//
47// NewLine returns a * pointer to a Line struct.
48func NewLine(text string, lineNum int) *Line {
49	return &Line{text, lineNum, SeekInfo{}, time.Now(), nil}
50}
51
52// SeekInfo represents arguments to io.Seek. See: https://golang.org/pkg/io/#SectionReader.Seek
53type SeekInfo struct {
54	Offset int64
55	Whence int
56}
57
58type logger interface {
59	Fatal(v ...interface{})
60	Fatalf(format string, v ...interface{})
61	Fatalln(v ...interface{})
62	Panic(v ...interface{})
63	Panicf(format string, v ...interface{})
64	Panicln(v ...interface{})
65	Print(v ...interface{})
66	Printf(format string, v ...interface{})
67	Println(v ...interface{})
68}
69
70// Config is used to specify how a file must be tailed.
71type Config struct {
72	// File-specifc
73	Location  *SeekInfo // Tail from this location. If nil, start at the beginning of the file
74	ReOpen    bool      // Reopen recreated files (tail -F)
75	MustExist bool      // Fail early if the file does not exist
76	Poll      bool      // Poll for file changes instead of using the default inotify
77	Pipe      bool      // The file is a named pipe (mkfifo)
78
79	// Generic IO
80	Follow      bool // Continue looking for new lines (tail -f)
81	MaxLineSize int  // If non-zero, split longer lines into multiple lines
82
83	// Optionally, use a ratelimiter (e.g. created by the ratelimiter/NewLeakyBucket function)
84	RateLimiter *ratelimiter.LeakyBucket
85
86	// Optionally use a Logger. When nil, the Logger is set to tail.DefaultLogger.
87	// To disable logging, set it to tail.DiscardingLogger
88	Logger logger
89}
90
91type Tail struct {
92	Filename string     // The filename
93	Lines    chan *Line // A consumable channel of *Line
94	Config              // Tail.Configuration
95
96	file    *os.File
97	reader  *bufio.Reader
98	lineNum int
99
100	watcher watch.FileWatcher
101	changes *watch.FileChanges
102
103	tomb.Tomb // provides: Done, Kill, Dying
104
105	lk sync.Mutex
106}
107
108var (
109	// DefaultLogger logs to os.Stderr and it is used when Config.Logger == nil
110	DefaultLogger = log.New(os.Stderr, "", log.LstdFlags)
111	// DiscardingLogger can be used to disable logging output
112	DiscardingLogger = log.New(ioutil.Discard, "", 0)
113)
114
115// TailFile begins tailing the file. And returns a pointer to a Tail struct
116// and an error. An output stream is made available via the Tail.Lines
117// channel (e.g. to be looped and printed). To handle errors during tailing,
118// after finishing reading from the Lines channel, invoke the `Wait` or `Err`
119// method on the returned *Tail.
120func TailFile(filename string, config Config) (*Tail, error) {
121	if config.ReOpen && !config.Follow {
122		util.Fatal("cannot set ReOpen without Follow.")
123	}
124
125	t := &Tail{
126		Filename: filename,
127		Lines:    make(chan *Line),
128		Config:   config,
129	}
130
131	// when Logger was not specified in config, use default logger
132	if t.Logger == nil {
133		t.Logger = DefaultLogger
134	}
135
136	if t.Poll {
137		t.watcher = watch.NewPollingFileWatcher(filename)
138	} else {
139		t.watcher = watch.NewInotifyFileWatcher(filename)
140	}
141
142	if t.MustExist {
143		var err error
144		t.file, err = OpenFile(t.Filename)
145		if err != nil {
146			return nil, err
147		}
148	}
149
150	go t.tailFileSync()
151
152	return t, nil
153}
154
155// Tell returns the file's current position, like stdio's ftell() and an error.
156// Beware that this value may not be completely accurate because one line from
157// the chan(tail.Lines) may have been read already.
158func (tail *Tail) Tell() (offset int64, err error) {
159	if tail.file == nil {
160		return
161	}
162	offset, err = tail.file.Seek(0, io.SeekCurrent)
163	if err != nil {
164		return
165	}
166
167	tail.lk.Lock()
168	defer tail.lk.Unlock()
169	if tail.reader == nil {
170		return
171	}
172
173	offset -= int64(tail.reader.Buffered())
174	return
175}
176
177// Stop stops the tailing activity.
178func (tail *Tail) Stop() error {
179	tail.Kill(nil)
180	return tail.Wait()
181}
182
183// StopAtEOF stops tailing as soon as the end of the file is reached. The function
184// returns an error,
185func (tail *Tail) StopAtEOF() error {
186	tail.Kill(errStopAtEOF)
187	return tail.Wait()
188}
189
190var errStopAtEOF = errors.New("tail: stop at eof")
191
192func (tail *Tail) close() {
193	close(tail.Lines)
194	tail.closeFile()
195}
196
197func (tail *Tail) closeFile() {
198	if tail.file != nil {
199		tail.file.Close()
200		tail.file = nil
201	}
202}
203
204func (tail *Tail) reopen() error {
205	tail.closeFile()
206	tail.lineNum = 0
207	for {
208		var err error
209		tail.file, err = OpenFile(tail.Filename)
210		if err != nil {
211			if os.IsNotExist(err) {
212				tail.Logger.Printf("Waiting for %s to appear...", tail.Filename)
213				if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil {
214					if err == tomb.ErrDying {
215						return err
216					}
217					return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err)
218				}
219				continue
220			}
221			return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err)
222		}
223		break
224	}
225	return nil
226}
227
228func (tail *Tail) readLine() (string, error) {
229	tail.lk.Lock()
230	line, err := tail.reader.ReadString('\n')
231	tail.lk.Unlock()
232	if err != nil {
233		// Note ReadString "returns the data read before the error" in
234		// case of an error, including EOF, so we return it as is. The
235		// caller is expected to process it if err is EOF.
236		return line, err
237	}
238
239	line = strings.TrimRight(line, "\n")
240
241	return line, err
242}
243
244func (tail *Tail) tailFileSync() {
245	defer tail.Done()
246	defer tail.close()
247
248	if !tail.MustExist {
249		// deferred first open.
250		err := tail.reopen()
251		if err != nil {
252			if err != tomb.ErrDying {
253				tail.Kill(err)
254			}
255			return
256		}
257	}
258
259	// Seek to requested location on first open of the file.
260	if tail.Location != nil {
261		_, err := tail.file.Seek(tail.Location.Offset, tail.Location.Whence)
262		if err != nil {
263			tail.Killf("Seek error on %s: %s", tail.Filename, err)
264			return
265		}
266	}
267
268	tail.openReader()
269
270	// Read line by line.
271	for {
272		// do not seek in named pipes
273		if !tail.Pipe {
274			// grab the position in case we need to back up in the event of a half-line
275			if _, err := tail.Tell(); err != nil {
276				tail.Kill(err)
277				return
278			}
279		}
280
281		line, err := tail.readLine()
282
283		// Process `line` even if err is EOF.
284		if err == nil {
285			cooloff := !tail.sendLine(line)
286			if cooloff {
287				// Wait a second before seeking till the end of
288				// file when rate limit is reached.
289				msg := ("Too much log activity; waiting a second before resuming tailing")
290				offset, _ := tail.Tell()
291				tail.Lines <- &Line{msg, tail.lineNum, SeekInfo{Offset: offset}, time.Now(), errors.New(msg)}
292				select {
293				case <-time.After(time.Second):
294				case <-tail.Dying():
295					return
296				}
297				if err := tail.seekEnd(); err != nil {
298					tail.Kill(err)
299					return
300				}
301			}
302		} else if err == io.EOF {
303			if !tail.Follow {
304				if line != "" {
305					tail.sendLine(line)
306				}
307				return
308			}
309
310			if tail.Follow && line != "" {
311				tail.sendLine(line)
312				if err := tail.seekEnd(); err != nil {
313					tail.Kill(err)
314					return
315				}
316			}
317
318			// When EOF is reached, wait for more data to become
319			// available. Wait strategy is based on the `tail.watcher`
320			// implementation (inotify or polling).
321			err := tail.waitForChanges()
322			if err != nil {
323				if err != ErrStop {
324					tail.Kill(err)
325				}
326				return
327			}
328		} else {
329			// non-EOF error
330			tail.Killf("Error reading %s: %s", tail.Filename, err)
331			return
332		}
333
334		select {
335		case <-tail.Dying():
336			if tail.Err() == errStopAtEOF {
337				continue
338			}
339			return
340		default:
341		}
342	}
343}
344
345// waitForChanges waits until the file has been appended, deleted,
346// moved or truncated. When moved or deleted - the file will be
347// reopened if ReOpen is true. Truncated files are always reopened.
348func (tail *Tail) waitForChanges() error {
349	if tail.changes == nil {
350		pos, err := tail.file.Seek(0, io.SeekCurrent)
351		if err != nil {
352			return err
353		}
354		tail.changes, err = tail.watcher.ChangeEvents(&tail.Tomb, pos)
355		if err != nil {
356			return err
357		}
358	}
359
360	select {
361	case <-tail.changes.Modified:
362		return nil
363	case <-tail.changes.Deleted:
364		tail.changes = nil
365		if tail.ReOpen {
366			// XXX: we must not log from a library.
367			tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename)
368			if err := tail.reopen(); err != nil {
369				return err
370			}
371			tail.Logger.Printf("Successfully reopened %s", tail.Filename)
372			tail.openReader()
373			return nil
374		}
375		tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename)
376		return ErrStop
377	case <-tail.changes.Truncated:
378		// Always reopen truncated files (Follow is true)
379		tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename)
380		if err := tail.reopen(); err != nil {
381			return err
382		}
383		tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename)
384		tail.openReader()
385		return nil
386	case <-tail.Dying():
387		return ErrStop
388	}
389}
390
391func (tail *Tail) openReader() {
392	tail.lk.Lock()
393	if tail.MaxLineSize > 0 {
394		// add 2 to account for newline characters
395		tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize+2)
396	} else {
397		tail.reader = bufio.NewReader(tail.file)
398	}
399	tail.lk.Unlock()
400}
401
402func (tail *Tail) seekEnd() error {
403	return tail.seekTo(SeekInfo{Offset: 0, Whence: io.SeekEnd})
404}
405
406func (tail *Tail) seekTo(pos SeekInfo) error {
407	_, err := tail.file.Seek(pos.Offset, pos.Whence)
408	if err != nil {
409		return fmt.Errorf("Seek error on %s: %s", tail.Filename, err)
410	}
411	// Reset the read buffer whenever the file is re-seek'ed
412	tail.reader.Reset(tail.file)
413	return nil
414}
415
416// sendLine sends the line(s) to Lines channel, splitting longer lines
417// if necessary. Return false if rate limit is reached.
418func (tail *Tail) sendLine(line string) bool {
419	now := time.Now()
420	lines := []string{line}
421
422	// Split longer lines
423	if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize {
424		lines = util.PartitionString(line, tail.MaxLineSize)
425	}
426
427	for _, line := range lines {
428		tail.lineNum++
429		offset, _ := tail.Tell()
430		select {
431		case tail.Lines <- &Line{line, tail.lineNum, SeekInfo{Offset: offset}, now, nil}:
432		case <-tail.Dying():
433			return true
434		}
435	}
436
437	if tail.Config.RateLimiter != nil {
438		ok := tail.Config.RateLimiter.Pour(uint16(len(lines)))
439		if !ok {
440			tail.Logger.Printf("Leaky bucket full (%v); entering 1s cooloff period.",
441				tail.Filename)
442			return false
443		}
444	}
445
446	return true
447}
448
449// Cleanup removes inotify watches added by the tail package. This function is
450// meant to be invoked from a process's exit handler. Linux kernel may not
451// automatically remove inotify watches after the process exits.
452// If you plan to re-read a file, don't call Cleanup in between.
453func (tail *Tail) Cleanup() {
454	watch.Cleanup(tail.Filename)
455}
456