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