github.com/nxadm/tail@v1.4.12-0.20231010141446-ba755e4d73b6/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/nxadm/tail/ratelimiter" 25 "github.com/nxadm/tail/util" 26 "github.com/nxadm/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 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 CompleteLines 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) 85 RateLimiter *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 89 Logger logger 90 } 91 92 type Tail struct { 93 Filename string // The filename 94 Lines chan *Line // A consumable channel of *Line 95 Config // Tail.Configuration 96 97 file *os.File 98 reader *bufio.Reader 99 lineNum int 100 101 lineBuf *strings.Builder 102 103 watcher watch.FileWatcher 104 changes *watch.FileChanges 105 106 tomb.Tomb // provides: Done, Kill, Dying 107 108 lk sync.Mutex 109 } 110 111 var ( 112 // DefaultLogger logs to os.Stderr and it is used when Config.Logger == nil 113 DefaultLogger = log.New(os.Stderr, "", log.LstdFlags) 114 // DiscardingLogger can be used to disable logging output 115 DiscardingLogger = 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. 123 func TailFile(filename string, config Config) (*Tail, error) { 124 if config.ReOpen && !config.Follow { 125 util.Fatal("cannot set ReOpen without Follow.") 126 } 127 128 t := &Tail{ 129 Filename: filename, 130 Lines: make(chan *Line), 131 Config: config, 132 } 133 134 if config.CompleteLines { 135 t.lineBuf = new(strings.Builder) 136 } 137 138 // when Logger was not specified in config, use default logger 139 if t.Logger == nil { 140 t.Logger = DefaultLogger 141 } 142 143 if t.Poll { 144 t.watcher = watch.NewPollingFileWatcher(filename) 145 } else { 146 t.watcher = watch.NewInotifyFileWatcher(filename) 147 } 148 149 if t.MustExist { 150 var err error 151 t.file, err = OpenFile(t.Filename) 152 if err != nil { 153 return nil, err 154 } 155 } 156 157 go t.tailFileSync() 158 159 return 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. 165 func (tail *Tail) Tell() (offset int64, err error) { 166 if tail.file == nil { 167 return 168 } 169 offset, err = tail.file.Seek(0, io.SeekCurrent) 170 if err != nil { 171 return 172 } 173 174 tail.lk.Lock() 175 defer tail.lk.Unlock() 176 if tail.reader == nil { 177 return 178 } 179 180 offset -= int64(tail.reader.Buffered()) 181 return 182 } 183 184 // Stop stops the tailing activity. 185 func (tail *Tail) Stop() error { 186 tail.Kill(nil) 187 return tail.Wait() 188 } 189 190 // StopAtEOF stops tailing as soon as the end of the file is reached. The function 191 // returns an error, 192 func (tail *Tail) StopAtEOF() error { 193 tail.Kill(errStopAtEOF) 194 return tail.Wait() 195 } 196 197 var errStopAtEOF = errors.New("tail: stop at eof") 198 199 func (tail *Tail) close() { 200 close(tail.Lines) 201 tail.closeFile() 202 } 203 204 func (tail *Tail) closeFile() { 205 if tail.file != nil { 206 tail.file.Close() 207 tail.file = nil 208 } 209 } 210 211 func (tail *Tail) reopen() error { 212 if tail.lineBuf != nil { 213 tail.lineBuf.Reset() 214 } 215 tail.closeFile() 216 tail.lineNum = 0 217 for { 218 var err error 219 tail.file, err = OpenFile(tail.Filename) 220 if err != nil { 221 if os.IsNotExist(err) { 222 tail.Logger.Printf("Waiting for %s to appear...", tail.Filename) 223 if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil { 224 if err == tomb.ErrDying { 225 return err 226 } 227 return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err) 228 } 229 continue 230 } 231 return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err) 232 } 233 break 234 } 235 return nil 236 } 237 238 func (tail *Tail) readLine() (string, error) { 239 tail.lk.Lock() 240 line, err := tail.reader.ReadString('\n') 241 tail.lk.Unlock() 242 243 newlineEnding := strings.HasSuffix(line, "\n") 244 line = strings.TrimRight(line, "\n") 245 246 // if we don't have to handle incomplete lines, we can return the line as-is 247 if !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. 251 return line, err 252 } 253 254 if _, err := tail.lineBuf.WriteString(line); err != nil { 255 return line, err 256 } 257 258 if newlineEnding { 259 line = tail.lineBuf.String() 260 tail.lineBuf.Reset() 261 return line, nil 262 } else { 263 if tail.Config.Follow { 264 line = "" 265 } 266 return line, io.EOF 267 } 268 } 269 270 func (tail *Tail) tailFileSync() { 271 defer tail.Done() 272 defer tail.close() 273 274 if !tail.MustExist { 275 // deferred first open. 276 err := tail.reopen() 277 if err != nil { 278 if err != tomb.ErrDying { 279 tail.Kill(err) 280 } 281 return 282 } 283 } 284 285 // Seek to requested location on first open of the file. 286 if tail.Location != nil { 287 _, err := tail.file.Seek(tail.Location.Offset, tail.Location.Whence) 288 if err != nil { 289 tail.Killf("Seek error on %s: %s", tail.Filename, err) 290 return 291 } 292 } 293 294 tail.openReader() 295 296 // Read line by line. 297 for { 298 // do not seek in named pipes 299 if !tail.Pipe { 300 // grab the position in case we need to back up in the event of a half-line 301 if _, err := tail.Tell(); err != nil { 302 tail.Kill(err) 303 return 304 } 305 } 306 307 line, err := tail.readLine() 308 309 // Process `line` even if err is EOF. 310 if err == nil { 311 cooloff := !tail.sendLine(line) 312 if cooloff { 313 // Wait a second before seeking till the end of 314 // file when rate limit is reached. 315 msg := ("Too much log activity; waiting a second before resuming tailing") 316 offset, _ := tail.Tell() 317 tail.Lines <- &Line{msg, tail.lineNum, SeekInfo{Offset: offset}, time.Now(), errors.New(msg)} 318 select { 319 case <-time.After(time.Second): 320 case <-tail.Dying(): 321 return 322 } 323 if err := tail.seekEnd(); err != nil { 324 tail.Kill(err) 325 return 326 } 327 } 328 } else if err == io.EOF { 329 if !tail.Follow { 330 if line != "" { 331 tail.sendLine(line) 332 } 333 return 334 } 335 336 if tail.Follow && line != "" { 337 tail.sendLine(line) 338 if err := tail.seekEnd(); err != nil { 339 tail.Kill(err) 340 return 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). 347 err := tail.waitForChanges() 348 if err != nil { 349 if err != ErrStop { 350 tail.Kill(err) 351 } 352 return 353 } 354 } else { 355 // non-EOF error 356 tail.Killf("Error reading %s: %s", tail.Filename, err) 357 return 358 } 359 360 select { 361 case <-tail.Dying(): 362 if tail.Err() == errStopAtEOF { 363 continue 364 } 365 return 366 default: 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. 374 func (tail *Tail) waitForChanges() error { 375 if tail.changes == nil { 376 pos, err := tail.file.Seek(0, io.SeekCurrent) 377 if err != nil { 378 return err 379 } 380 tail.changes, err = tail.watcher.ChangeEvents(&tail.Tomb, pos) 381 if err != nil { 382 return err 383 } 384 } 385 386 select { 387 case <-tail.changes.Modified: 388 return nil 389 case <-tail.changes.Deleted: 390 tail.changes = nil 391 if tail.ReOpen { 392 // XXX: we must not log from a library. 393 tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename) 394 if err := tail.reopen(); err != nil { 395 return err 396 } 397 tail.Logger.Printf("Successfully reopened %s", tail.Filename) 398 tail.openReader() 399 return nil 400 } 401 tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename) 402 return ErrStop 403 case <-tail.changes.Truncated: 404 // Always reopen truncated files (Follow is true) 405 tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename) 406 if err := tail.reopen(); err != nil { 407 return err 408 } 409 tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename) 410 tail.openReader() 411 return nil 412 case <-tail.Dying(): 413 return ErrStop 414 } 415 } 416 417 func (tail *Tail) openReader() { 418 tail.lk.Lock() 419 if tail.MaxLineSize > 0 { 420 // add 2 to account for newline characters 421 tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize+2) 422 } else { 423 tail.reader = bufio.NewReader(tail.file) 424 } 425 tail.lk.Unlock() 426 } 427 428 func (tail *Tail) seekEnd() error { 429 return tail.seekTo(SeekInfo{Offset: 0, Whence: io.SeekEnd}) 430 } 431 432 func (tail *Tail) seekTo(pos SeekInfo) error { 433 _, err := tail.file.Seek(pos.Offset, pos.Whence) 434 if err != nil { 435 return fmt.Errorf("Seek error on %s: %s", tail.Filename, err) 436 } 437 // Reset the read buffer whenever the file is re-seek'ed 438 tail.reader.Reset(tail.file) 439 return 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. 444 func (tail *Tail) sendLine(line string) bool { 445 now := time.Now() 446 lines := []string{line} 447 448 // Split longer lines 449 if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize { 450 lines = util.PartitionString(line, tail.MaxLineSize) 451 } 452 453 for _, line := range lines { 454 tail.lineNum++ 455 offset, _ := tail.Tell() 456 select { 457 case tail.Lines <- &Line{line, tail.lineNum, SeekInfo{Offset: offset}, now, nil}: 458 case <-tail.Dying(): 459 return true 460 } 461 } 462 463 if tail.Config.RateLimiter != nil { 464 ok := tail.Config.RateLimiter.Pour(uint16(len(lines))) 465 if !ok { 466 tail.Logger.Printf("Leaky bucket full (%v); entering 1s cooloff period.", 467 tail.Filename) 468 return false 469 } 470 } 471 472 return 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. 479 func (tail *Tail) Cleanup() { 480 watch.Cleanup(tail.Filename) 481 }