mirror-to-gitea/vendor/github.com/nxadm/tail/tail.go
vendor/github.com/nxadm/tail/tail.goRaw
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}