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