github.com/admpub/tail@v1.1.0/tail.go (about) 1 // Copyright (c) 2015 HPE Software Inc. All rights reserved. 2 // Copyright (c) 2013 ActiveState Software Inc. All rights reserved. 3 4 package tail 5 6 import ( 7 "bufio" 8 "bytes" 9 "errors" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "log" 14 "os" 15 "strings" 16 "sync" 17 "time" 18 19 "github.com/admpub/tail/ratelimiter" 20 "github.com/admpub/tail/util" 21 "github.com/admpub/tail/watch" 22 tomb "gopkg.in/tomb.v1" 23 ) 24 25 var ( 26 ErrStop = errors.New("tail should now stop") 27 ) 28 29 type Line struct { 30 Text string 31 Time time.Time 32 Err error // Error from tail 33 } 34 35 // NewLine returns a Line with present time. 36 func NewLine(text string) *Line { 37 return &Line{text, time.Now(), nil} 38 } 39 40 // SeekInfo represents arguments to `os.Seek` 41 type SeekInfo struct { 42 Offset int64 43 Whence int // os.SEEK_* 44 } 45 46 type logger interface { 47 Fatal(v ...interface{}) 48 Fatalf(format string, v ...interface{}) 49 Fatalln(v ...interface{}) 50 Panic(v ...interface{}) 51 Panicf(format string, v ...interface{}) 52 Panicln(v ...interface{}) 53 Print(v ...interface{}) 54 Printf(format string, v ...interface{}) 55 Println(v ...interface{}) 56 } 57 58 // Config is used to specify how a file must be tailed. 59 type Config struct { 60 // File-specifc 61 Location *SeekInfo // Seek to this location before tailing 62 ReOpen bool // Reopen recreated files (tail -F) 63 MustExist bool // Fail early if the file does not exist 64 Poll bool // Poll for file changes instead of using inotify 65 Pipe bool // Is a named pipe (mkfifo) 66 RateLimiter *ratelimiter.LeakyBucket 67 68 LastLines int // Output the last NUM lines (tail -n) 69 FromLine int // Output starting with line NUM (tail -n) 70 PageSize int // Buffer size for seek line. Default 4096 71 SeekOnReOpen bool // Start from line on reopen 72 73 // Generic IO 74 Follow bool // Continue looking for new lines (tail -f) 75 MaxLineSize int // If non-zero, split longer lines into multiple lines 76 77 // Logger, when nil, is set to tail.DefaultLogger 78 // To disable logging: set field to tail.DiscardingLogger 79 Logger logger 80 } 81 82 type Tail struct { 83 Filename string 84 Lines chan *Line 85 Config 86 87 file *os.File 88 reader *bufio.Reader 89 90 watcher watch.FileWatcher 91 changes *watch.FileChanges 92 93 tomb.Tomb // provides: Done, Kill, Dying 94 95 lk sync.Mutex 96 } 97 98 var ( 99 // DefaultLogger is used when Config.Logger == nil 100 DefaultLogger = log.New(os.Stderr, "", log.LstdFlags) 101 // DiscardingLogger can be used to disable logging output 102 DiscardingLogger = log.New(ioutil.Discard, "", 0) 103 ) 104 105 // TailFile begins tailing the file. Output stream is made available 106 // via the `Tail.Lines` channel. To handle errors during tailing, 107 // invoke the `Wait` or `Err` method after finishing reading from the 108 // `Lines` channel. 109 func TailFile(filename string, config Config) (*Tail, error) { 110 if config.ReOpen && !config.Follow { 111 util.Fatal("cannot set ReOpen without Follow.") 112 } 113 114 t := &Tail{ 115 Filename: filename, 116 Lines: make(chan *Line), 117 Config: config, 118 } 119 120 // when Logger was not specified in config, use default logger 121 if t.Logger == nil { 122 t.Logger = log.New(os.Stderr, "", log.LstdFlags) 123 } 124 125 if t.Poll { 126 t.watcher = watch.NewPollingFileWatcher(filename) 127 } else { 128 t.watcher = watch.NewInotifyFileWatcher(filename) 129 } 130 131 if t.MustExist { 132 var err error 133 t.file, err = OpenFile(t.Filename) 134 if err != nil { 135 return nil, err 136 } 137 } 138 139 go t.tailFileSync() 140 141 return t, nil 142 } 143 144 // Update tail.Config.Location corresponding to tail.Config.LastLines. 145 // It goes with step defined in PageSize or 4096 by default 146 // Or set to 0 if tail.Config.LastLines more that lines in file 147 func (tail *Tail) readLast() { 148 149 count := 0 150 nLines := tail.Config.LastLines 151 152 seek := &SeekInfo{ 153 Offset: 0, 154 Whence: 2, // io.SeekEnd 155 } 156 157 pos, err := tail.file.Seek(seek.Offset, seek.Whence) 158 159 if err != nil && err != io.EOF { 160 fmt.Printf("Seek error on %s: %s\n", tail.Filename, err) 161 } 162 163 pageSize := 4096 164 if tail.Config.PageSize != 0 { 165 pageSize = tail.Config.PageSize 166 } 167 168 // Skip сlosing \n if exist 169 b1 := make([]byte, 1) 170 tail.file.ReadAt(b1, pos-1) 171 172 if '\n' == b1[0] { 173 pos = pos - 1 174 } 175 176 for { 177 readFrom := pos - int64(pageSize) 178 lines := make([]int64, 0) 179 180 if readFrom <= 0 { 181 pageSize += int(readFrom) 182 readFrom = 0 183 lines = append(lines, 0) 184 count++ 185 } 186 187 b := make([]byte, pageSize) 188 189 _, err := tail.file.ReadAt(b, readFrom) 190 191 if err != nil && err != io.EOF { 192 fmt.Printf("Read error on %s: %s\n", tail.Filename, err) 193 } 194 195 i := 0 196 197 // Find newline symbols location in buffer and put it in to slice 198 for { 199 bufPos := bytes.IndexByte(b[i:], '\n') 200 if bufPos == -1 { 201 break 202 } 203 i = i + bufPos + 1 204 205 lines = append(lines, int64(i)) 206 count++ 207 } 208 209 var firstLinePos int64 210 211 // If no lines found in buf set firstLinePos to 0 212 // Its needed to handle lines bigger than PageSize 213 if len(lines) == 0 { 214 firstLinePos = 0 215 } else { 216 firstLinePos = lines[0] 217 } 218 219 if count == nLines { 220 tail.Config.Location = &SeekInfo{ 221 Offset: firstLinePos + readFrom, 222 Whence: 0, // io.SeekStart 223 } 224 return 225 } 226 227 if count > nLines { 228 linesLeft := count - nLines 229 targetPos := lines[linesLeft] 230 tail.Config.Location = &SeekInfo{ 231 Offset: targetPos + readFrom, 232 Whence: 0, // io.SeekStart 233 } 234 return 235 } 236 237 if readFrom == 0 { 238 tail.Config.Location = &SeekInfo{ 239 Offset: 0, 240 Whence: 0, // io.SeekStart 241 } 242 return 243 } 244 pos = firstLinePos + readFrom - 1 245 } 246 } 247 248 // Update tail.Config.Location corresponding to tail.Config.FromLine 249 // Or set to end of the file if FromLine more that lines in file 250 // it goes with step defined in PageSize or 4096 by default 251 func (tail *Tail) skipLines() { 252 253 fileStat, err := tail.file.Stat() 254 if err != nil { 255 fmt.Println(err) 256 } 257 fileSize := fileStat.Size() 258 259 count := 1 260 nLines := tail.Config.FromLine 261 262 pageSize := 4096 263 if tail.Config.PageSize != 0 { 264 pageSize = tail.Config.PageSize 265 } 266 267 // Skip сlosing \n if exist 268 b1 := make([]byte, 1) 269 tail.file.ReadAt(b1, fileSize-1) 270 271 if '\n' == b1[0] { 272 fileSize = fileSize - 1 273 } 274 275 var pos int64 276 pos = 0 277 278 for { 279 280 b := make([]byte, pageSize) 281 282 _, err := tail.file.ReadAt(b, pos) 283 if err != nil && err != io.EOF { 284 fmt.Printf("Read error on %s: %s\n", tail.Filename, err) 285 } 286 287 i := 0 288 289 // Find newline symbols location in buffer and put it in to slice 290 for { 291 bufPos := bytes.IndexByte(b[i:], '\n') 292 if bufPos == -1 { 293 break 294 } 295 296 i = i + bufPos + 1 297 298 count++ 299 if count == nLines { 300 tail.Config.Location = &SeekInfo{ 301 Offset: int64(i) + pos, 302 Whence: 0, // io.SeekStart 303 } 304 return 305 } 306 } 307 308 pos = pos + int64(pageSize) 309 310 if pos >= fileSize { 311 tail.Config.Location = &SeekInfo{ 312 Offset: 0, 313 Whence: 2, // io.SeekEnd 314 } 315 return 316 } 317 } 318 } 319 320 // Tell Return the file's current position, like stdio's ftell(). 321 // But this value is not very accurate. 322 // it may readed one line in the chan(tail.Lines), 323 // so it may lost one line. 324 func (tail *Tail) Tell() (offset int64, err error) { 325 if tail.file == nil { 326 return 327 } 328 offset, err = tail.file.Seek(0, os.SEEK_CUR) 329 if err != nil { 330 return 331 } 332 333 tail.lk.Lock() 334 defer tail.lk.Unlock() 335 if tail.reader == nil { 336 return 337 } 338 339 offset -= int64(tail.reader.Buffered()) 340 return 341 } 342 343 // Stop stops the tailing activity. 344 func (tail *Tail) Stop() error { 345 tail.Kill(nil) 346 return tail.Wait() 347 } 348 349 // StopAtEOF stops tailing as soon as the end of the file is reached. 350 func (tail *Tail) StopAtEOF() error { 351 tail.Kill(errStopAtEOF) 352 return tail.Wait() 353 } 354 355 var errStopAtEOF = errors.New("tail: stop at eof") 356 357 func (tail *Tail) close() { 358 close(tail.Lines) 359 tail.closeFile() 360 } 361 362 func (tail *Tail) closeFile() { 363 if tail.file != nil { 364 tail.file.Close() 365 tail.file = nil 366 } 367 } 368 369 func (tail *Tail) reopen() error { 370 tail.closeFile() 371 for { 372 var err error 373 tail.file, err = OpenFile(tail.Filename) 374 if err != nil { 375 if os.IsNotExist(err) { 376 tail.Logger.Printf("Waiting for %s to appear...", tail.Filename) 377 if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil { 378 if err == tomb.ErrDying { 379 return err 380 } 381 return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err) 382 } 383 continue 384 } 385 return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err) 386 } 387 break 388 } 389 return nil 390 } 391 392 func (tail *Tail) readLine() (string, error) { 393 tail.lk.Lock() 394 line, err := tail.reader.ReadString('\n') 395 tail.lk.Unlock() 396 if err != nil { 397 // Note ReadString "returns the data read before the error" in 398 // case of an error, including EOF, so we return it as is. The 399 // caller is expected to process it if err is EOF. 400 return line, err 401 } 402 403 line = strings.TrimRight(line, "\n") 404 405 return line, err 406 } 407 408 func (tail *Tail) findLine() { 409 410 if tail.Config.FromLine > 0 { 411 tail.skipLines() 412 } else if tail.Config.LastLines > 0 { 413 tail.readLast() 414 } 415 // Seek to requested location on first open of the file. 416 if tail.Location != nil { 417 _, err := tail.file.Seek(tail.Location.Offset, tail.Location.Whence) 418 //tail.Logger.Printf("Seeked %s - %+v\n", tail.Filename, tail.Location) 419 if err != nil { 420 tail.Killf("Seek error on %s: %s", tail.Filename, err) 421 return 422 } 423 } 424 } 425 426 func (tail *Tail) tailFileSync() { 427 defer tail.Done() 428 defer tail.close() 429 430 if !tail.MustExist { 431 // deferred first open. 432 err := tail.reopen() 433 if err != nil { 434 if err != tomb.ErrDying { 435 tail.Kill(err) 436 } 437 return 438 } 439 } 440 441 tail.findLine() 442 tail.openReader() 443 444 var offset int64 445 var err error 446 447 // Read line by line. 448 for { 449 // do not seek in named pipes 450 if !tail.Pipe { 451 // grab the position in case we need to back up in the event of a half-line 452 offset, err = tail.Tell() 453 if err != nil { 454 tail.Kill(err) 455 return 456 } 457 } 458 459 line, err := tail.readLine() 460 461 // Process `line` even if err is EOF. 462 if err == nil { 463 cooloff := !tail.sendLine(line) 464 if cooloff { 465 // Wait a second before seeking till the end of 466 // file when rate limit is reached. 467 msg := ("Too much log activity; waiting a second " + 468 "before resuming tailing") 469 tail.Lines <- &Line{msg, time.Now(), errors.New(msg)} 470 timer := time.NewTimer(time.Second) 471 defer timer.Stop() 472 select { 473 case <-timer.C: 474 case <-tail.Dying(): 475 return 476 } 477 if err := tail.seekEnd(); err != nil { 478 tail.Kill(err) 479 return 480 } 481 } 482 } else if err == io.EOF { 483 if !tail.Follow { 484 if line != "" { 485 tail.sendLine(line) 486 } 487 return 488 } 489 490 if tail.Follow && line != "" { 491 // this has the potential to never return the last line if 492 // it's not followed by a newline; seems a fair trade here 493 err := tail.seekTo(SeekInfo{Offset: offset, Whence: 0}) 494 if err != nil { 495 tail.Kill(err) 496 return 497 } 498 } 499 500 // When EOF is reached, wait for more data to become 501 // available. Wait strategy is based on the `tail.watcher` 502 // implementation (inotify or polling). 503 err := tail.waitForChanges() 504 if err != nil { 505 if err != ErrStop { 506 tail.Kill(err) 507 } 508 return 509 } 510 } else { 511 // non-EOF error 512 tail.Killf("Error reading %s: %s", tail.Filename, err) 513 return 514 } 515 516 select { 517 case <-tail.Dying(): 518 if tail.Err() == errStopAtEOF { 519 continue 520 } 521 return 522 default: 523 } 524 } 525 } 526 527 // waitForChanges waits until the file has been appended, deleted, 528 // moved or truncated. When moved or deleted - the file will be 529 // reopened if ReOpen is true. Truncated files are always reopened. 530 func (tail *Tail) waitForChanges() error { 531 if tail.changes == nil { 532 pos, err := tail.file.Seek(0, os.SEEK_CUR) 533 if err != nil { 534 return err 535 } 536 tail.changes, err = tail.watcher.ChangeEvents(&tail.Tomb, pos) 537 if err != nil { 538 return err 539 } 540 } 541 542 select { 543 case <-tail.changes.Modified: 544 return nil 545 case <-tail.changes.Deleted: 546 tail.changes = nil 547 if tail.ReOpen { 548 // XXX: we must not log from a library. 549 tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename) 550 if err := tail.reopen(); err != nil { 551 return err 552 } 553 tail.Logger.Printf("Successfully reopened %s", tail.Filename) 554 555 if tail.Config.SeekOnReOpen { 556 tail.findLine() 557 } 558 tail.openReader() 559 return nil 560 } else { 561 tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename) 562 return ErrStop 563 } 564 case <-tail.changes.Truncated: 565 // Always reopen truncated files (Follow is true) 566 tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename) 567 if err := tail.reopen(); err != nil { 568 return err 569 } 570 tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename) 571 tail.openReader() 572 return nil 573 case <-tail.Dying(): 574 return ErrStop 575 } 576 panic("unreachable") 577 } 578 579 func (tail *Tail) openReader() { 580 if tail.MaxLineSize > 0 { 581 // add 2 to account for newline characters 582 tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize+2) 583 } else { 584 tail.reader = bufio.NewReader(tail.file) 585 } 586 } 587 588 func (tail *Tail) seekEnd() error { 589 return tail.seekTo(SeekInfo{Offset: 0, Whence: os.SEEK_END}) 590 } 591 592 func (tail *Tail) seekTo(pos SeekInfo) error { 593 _, err := tail.file.Seek(pos.Offset, pos.Whence) 594 if err != nil { 595 return fmt.Errorf("Seek error on %s: %s", tail.Filename, err) 596 } 597 // Reset the read buffer whenever the file is re-seek'ed 598 tail.reader.Reset(tail.file) 599 return nil 600 } 601 602 // sendLine sends the line(s) to Lines channel, splitting longer lines 603 // if necessary. Return false if rate limit is reached. 604 func (tail *Tail) sendLine(line string) bool { 605 now := time.Now() 606 lines := []string{line} 607 608 // Split longer lines 609 if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize { 610 lines = util.PartitionString(line, tail.MaxLineSize) 611 } 612 613 for _, line := range lines { 614 tail.Lines <- &Line{line, now, nil} 615 } 616 617 if tail.Config.RateLimiter != nil { 618 ok := tail.Config.RateLimiter.Pour(uint16(len(lines))) 619 if !ok { 620 tail.Logger.Printf("Leaky bucket full (%v); entering 1s cooloff period.\n", 621 tail.Filename) 622 return false 623 } 624 } 625 626 return true 627 } 628 629 // Cleanup removes inotify watches added by the tail package. This function is 630 // meant to be invoked from a process's exit handler. Linux kernel may not 631 // automatically remove inotify watches after the process exits. 632 func (tail *Tail) Cleanup() { 633 watch.Cleanup(tail.Filename) 634 }