github.com/wozhu6104/docker@v20.10.10+incompatible/daemon/logger/loggerutils/logfile.go (about) 1 package loggerutils // import "github.com/docker/docker/daemon/logger/loggerutils" 2 3 import ( 4 "compress/gzip" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "os" 10 "runtime" 11 "strconv" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/docker/docker/daemon/logger" 17 "github.com/docker/docker/pkg/filenotify" 18 "github.com/docker/docker/pkg/pools" 19 "github.com/docker/docker/pkg/pubsub" 20 "github.com/fsnotify/fsnotify" 21 "github.com/pkg/errors" 22 "github.com/sirupsen/logrus" 23 ) 24 25 const tmpLogfileSuffix = ".tmp" 26 27 // rotateFileMetadata is a metadata of the gzip header of the compressed log file 28 type rotateFileMetadata struct { 29 LastTime time.Time `json:"lastTime,omitempty"` 30 } 31 32 // refCounter is a counter of logfile being referenced 33 type refCounter struct { 34 mu sync.Mutex 35 counter map[string]int 36 } 37 38 // Reference increase the reference counter for specified logfile 39 func (rc *refCounter) GetReference(fileName string, openRefFile func(fileName string, exists bool) (*os.File, error)) (*os.File, error) { 40 rc.mu.Lock() 41 defer rc.mu.Unlock() 42 43 var ( 44 file *os.File 45 err error 46 ) 47 _, ok := rc.counter[fileName] 48 file, err = openRefFile(fileName, ok) 49 if err != nil { 50 return nil, err 51 } 52 53 if ok { 54 rc.counter[fileName]++ 55 } else if file != nil { 56 rc.counter[file.Name()] = 1 57 } 58 59 return file, nil 60 } 61 62 // Dereference reduce the reference counter for specified logfile 63 func (rc *refCounter) Dereference(fileName string) error { 64 rc.mu.Lock() 65 defer rc.mu.Unlock() 66 67 rc.counter[fileName]-- 68 if rc.counter[fileName] <= 0 { 69 delete(rc.counter, fileName) 70 err := os.Remove(fileName) 71 if err != nil && !os.IsNotExist(err) { 72 return err 73 } 74 } 75 return nil 76 } 77 78 // LogFile is Logger implementation for default Docker logging. 79 type LogFile struct { 80 mu sync.RWMutex // protects the logfile access 81 f *os.File // store for closing 82 closed bool 83 rotateMu sync.Mutex // blocks the next rotation until the current rotation is completed 84 capacity int64 // maximum size of each file 85 currentSize int64 // current size of the latest file 86 maxFiles int // maximum number of files 87 compress bool // whether old versions of log files are compressed 88 lastTimestamp time.Time // timestamp of the last log 89 filesRefCounter refCounter // keep reference-counted of decompressed files 90 notifyReaders *pubsub.Publisher 91 marshal logger.MarshalFunc 92 createDecoder MakeDecoderFn 93 getTailReader GetTailReaderFunc 94 perms os.FileMode 95 } 96 97 // MakeDecoderFn creates a decoder 98 type MakeDecoderFn func(rdr io.Reader) Decoder 99 100 // Decoder is for reading logs 101 // It is created by the log reader by calling the `MakeDecoderFunc` 102 type Decoder interface { 103 // Reset resets the decoder 104 // Reset is called for certain events, such as log rotations 105 Reset(io.Reader) 106 // Decode decodes the next log messeage from the stream 107 Decode() (*logger.Message, error) 108 // Close signals to the decoder that it can release whatever resources it was using. 109 Close() 110 } 111 112 // SizeReaderAt defines a ReaderAt that also reports its size. 113 // This is used for tailing log files. 114 type SizeReaderAt interface { 115 io.ReaderAt 116 Size() int64 117 } 118 119 // GetTailReaderFunc is used to truncate a reader to only read as much as is required 120 // in order to get the passed in number of log lines. 121 // It returns the sectioned reader, the number of lines that the section reader 122 // contains, and any error that occurs. 123 type GetTailReaderFunc func(ctx context.Context, f SizeReaderAt, nLogLines int) (rdr io.Reader, nLines int, err error) 124 125 // NewLogFile creates new LogFile 126 func NewLogFile(logPath string, capacity int64, maxFiles int, compress bool, marshaller logger.MarshalFunc, decodeFunc MakeDecoderFn, perms os.FileMode, getTailReader GetTailReaderFunc) (*LogFile, error) { 127 log, err := openFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, perms) 128 if err != nil { 129 return nil, err 130 } 131 132 size, err := log.Seek(0, io.SeekEnd) 133 if err != nil { 134 return nil, err 135 } 136 137 return &LogFile{ 138 f: log, 139 capacity: capacity, 140 currentSize: size, 141 maxFiles: maxFiles, 142 compress: compress, 143 filesRefCounter: refCounter{counter: make(map[string]int)}, 144 notifyReaders: pubsub.NewPublisher(0, 1), 145 marshal: marshaller, 146 createDecoder: decodeFunc, 147 perms: perms, 148 getTailReader: getTailReader, 149 }, nil 150 } 151 152 // WriteLogEntry writes the provided log message to the current log file. 153 // This may trigger a rotation event if the max file/capacity limits are hit. 154 func (w *LogFile) WriteLogEntry(msg *logger.Message) error { 155 b, err := w.marshal(msg) 156 if err != nil { 157 return errors.Wrap(err, "error marshalling log message") 158 } 159 160 logger.PutMessage(msg) 161 162 w.mu.Lock() 163 if w.closed { 164 w.mu.Unlock() 165 return errors.New("cannot write because the output file was closed") 166 } 167 168 if err := w.checkCapacityAndRotate(); err != nil { 169 w.mu.Unlock() 170 return errors.Wrap(err, "error rotating log file") 171 } 172 173 n, err := w.f.Write(b) 174 if err == nil { 175 w.currentSize += int64(n) 176 w.lastTimestamp = msg.Timestamp 177 } 178 179 w.mu.Unlock() 180 return errors.Wrap(err, "error writing log entry") 181 } 182 183 func (w *LogFile) checkCapacityAndRotate() (retErr error) { 184 if w.capacity == -1 { 185 return nil 186 } 187 if w.currentSize < w.capacity { 188 return nil 189 } 190 191 w.rotateMu.Lock() 192 noCompress := w.maxFiles <= 1 || !w.compress 193 defer func() { 194 // If we aren't going to run the goroutine to compress the log file, then we need to unlock in this function. 195 // Otherwise the lock will be released in the goroutine that handles compression. 196 if retErr != nil || noCompress { 197 w.rotateMu.Unlock() 198 } 199 }() 200 201 fname := w.f.Name() 202 if err := w.f.Close(); err != nil { 203 // if there was an error during a prior rotate, the file could already be closed 204 if !errors.Is(err, os.ErrClosed) { 205 return errors.Wrap(err, "error closing file") 206 } 207 } 208 209 if err := rotate(fname, w.maxFiles, w.compress); err != nil { 210 logrus.WithError(err).Warn("Error rotating log file, log data may have been lost") 211 } else { 212 var renameErr error 213 for i := 0; i < 10; i++ { 214 if renameErr = os.Rename(fname, fname+".1"); renameErr != nil && !os.IsNotExist(renameErr) { 215 logrus.WithError(renameErr).WithField("file", fname).Debug("Error rotating current container log file, evicting readers and retrying") 216 w.notifyReaders.Publish(renameErr) 217 time.Sleep(100 * time.Millisecond) 218 continue 219 } 220 break 221 } 222 if renameErr != nil { 223 logrus.WithError(renameErr).Error("Error renaming current log file") 224 } 225 } 226 227 file, err := openFile(fname, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, w.perms) 228 if err != nil { 229 return err 230 } 231 w.f = file 232 w.currentSize = 0 233 234 w.notifyReaders.Publish(struct{}{}) 235 236 if noCompress { 237 return nil 238 } 239 240 ts := w.lastTimestamp 241 242 go func() { 243 if err := compressFile(fname+".1", ts); err != nil { 244 logrus.WithError(err).Error("Error compressing log file after rotation") 245 } 246 w.rotateMu.Unlock() 247 }() 248 249 return nil 250 } 251 252 func rotate(name string, maxFiles int, compress bool) error { 253 if maxFiles < 2 { 254 return nil 255 } 256 257 var extension string 258 if compress { 259 extension = ".gz" 260 } 261 262 lastFile := fmt.Sprintf("%s.%d%s", name, maxFiles-1, extension) 263 err := os.Remove(lastFile) 264 if err != nil && !os.IsNotExist(err) { 265 return errors.Wrap(err, "error removing oldest log file") 266 } 267 268 for i := maxFiles - 1; i > 1; i-- { 269 toPath := name + "." + strconv.Itoa(i) + extension 270 fromPath := name + "." + strconv.Itoa(i-1) + extension 271 logrus.WithField("source", fromPath).WithField("target", toPath).Trace("Rotating log file") 272 if err := os.Rename(fromPath, toPath); err != nil && !os.IsNotExist(err) { 273 return err 274 } 275 } 276 277 return nil 278 } 279 280 func compressFile(fileName string, lastTimestamp time.Time) (retErr error) { 281 file, err := open(fileName) 282 if err != nil { 283 if os.IsNotExist(err) { 284 logrus.WithField("file", fileName).WithError(err).Debug("Could not open log file to compress") 285 return nil 286 } 287 return errors.Wrap(err, "failed to open log file") 288 } 289 defer func() { 290 file.Close() 291 if retErr == nil { 292 err := os.Remove(fileName) 293 if err != nil && !os.IsNotExist(err) { 294 retErr = errors.Wrap(err, "failed to remove source log file") 295 } 296 } 297 }() 298 299 outFile, err := openFile(fileName+".gz", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0640) 300 if err != nil { 301 return errors.Wrap(err, "failed to open or create gzip log file") 302 } 303 defer func() { 304 outFile.Close() 305 if retErr != nil { 306 if err := os.Remove(fileName + ".gz"); err != nil && !os.IsExist(err) { 307 logrus.WithError(err).Error("Error cleaning up after failed log compression") 308 } 309 } 310 }() 311 312 compressWriter := gzip.NewWriter(outFile) 313 defer compressWriter.Close() 314 315 // Add the last log entry timestamp to the gzip header 316 extra := rotateFileMetadata{} 317 extra.LastTime = lastTimestamp 318 compressWriter.Header.Extra, err = json.Marshal(&extra) 319 if err != nil { 320 // Here log the error only and don't return since this is just an optimization. 321 logrus.Warningf("Failed to marshal gzip header as JSON: %v", err) 322 } 323 324 _, err = pools.Copy(compressWriter, file) 325 if err != nil { 326 return errors.Wrapf(err, "error compressing log file %s", fileName) 327 } 328 329 return nil 330 } 331 332 // MaxFiles return maximum number of files 333 func (w *LogFile) MaxFiles() int { 334 return w.maxFiles 335 } 336 337 // Close closes underlying file and signals all readers to stop. 338 func (w *LogFile) Close() error { 339 w.mu.Lock() 340 defer w.mu.Unlock() 341 if w.closed { 342 return nil 343 } 344 if err := w.f.Close(); err != nil && !errors.Is(err, os.ErrClosed) { 345 return err 346 } 347 w.closed = true 348 return nil 349 } 350 351 // ReadLogs decodes entries from log files and sends them the passed in watcher 352 // 353 // Note: Using the follow option can become inconsistent in cases with very frequent rotations and max log files is 1. 354 // TODO: Consider a different implementation which can effectively follow logs under frequent rotations. 355 func (w *LogFile) ReadLogs(config logger.ReadConfig, watcher *logger.LogWatcher) { 356 w.mu.RLock() 357 currentFile, err := open(w.f.Name()) 358 if err != nil { 359 w.mu.RUnlock() 360 watcher.Err <- err 361 return 362 } 363 defer currentFile.Close() 364 365 dec := w.createDecoder(nil) 366 defer dec.Close() 367 368 currentChunk, err := newSectionReader(currentFile) 369 if err != nil { 370 w.mu.RUnlock() 371 watcher.Err <- err 372 return 373 } 374 375 notifyEvict := w.notifyReaders.SubscribeTopicWithBuffer(func(i interface{}) bool { 376 _, ok := i.(error) 377 return ok 378 }, 1) 379 defer w.notifyReaders.Evict(notifyEvict) 380 381 if config.Tail != 0 { 382 // TODO(@cpuguy83): Instead of opening every file, only get the files which 383 // are needed to tail. 384 // This is especially costly when compression is enabled. 385 files, err := w.openRotatedFiles(config) 386 w.mu.RUnlock() 387 if err != nil { 388 watcher.Err <- err 389 return 390 } 391 392 closeFiles := func() { 393 for _, f := range files { 394 f.Close() 395 fileName := f.Name() 396 if strings.HasSuffix(fileName, tmpLogfileSuffix) { 397 err := w.filesRefCounter.Dereference(fileName) 398 if err != nil { 399 logrus.WithError(err).WithField("file", fileName).Error("Failed to dereference the log file") 400 } 401 } 402 } 403 } 404 405 readers := make([]SizeReaderAt, 0, len(files)+1) 406 for _, f := range files { 407 stat, err := f.Stat() 408 if err != nil { 409 watcher.Err <- errors.Wrap(err, "error reading size of rotated file") 410 closeFiles() 411 return 412 } 413 readers = append(readers, io.NewSectionReader(f, 0, stat.Size())) 414 } 415 if currentChunk.Size() > 0 { 416 readers = append(readers, currentChunk) 417 } 418 419 ok := tailFiles(readers, watcher, dec, w.getTailReader, config, notifyEvict) 420 closeFiles() 421 if !ok { 422 return 423 } 424 w.mu.RLock() 425 } 426 427 if !config.Follow || w.closed { 428 w.mu.RUnlock() 429 return 430 } 431 w.mu.RUnlock() 432 433 notifyRotate := w.notifyReaders.SubscribeTopic(func(i interface{}) bool { 434 _, ok := i.(struct{}) 435 return ok 436 }) 437 defer w.notifyReaders.Evict(notifyRotate) 438 439 followLogs(currentFile, watcher, notifyRotate, notifyEvict, dec, config.Since, config.Until) 440 } 441 442 func (w *LogFile) openRotatedFiles(config logger.ReadConfig) (files []*os.File, err error) { 443 w.rotateMu.Lock() 444 defer w.rotateMu.Unlock() 445 446 defer func() { 447 if err == nil { 448 return 449 } 450 for _, f := range files { 451 f.Close() 452 if strings.HasSuffix(f.Name(), tmpLogfileSuffix) { 453 err := os.Remove(f.Name()) 454 if err != nil && !os.IsNotExist(err) { 455 logrus.Warnf("Failed to remove logfile: %v", err) 456 } 457 } 458 } 459 }() 460 461 for i := w.maxFiles; i > 1; i-- { 462 f, err := open(fmt.Sprintf("%s.%d", w.f.Name(), i-1)) 463 if err != nil { 464 if !os.IsNotExist(err) { 465 return nil, errors.Wrap(err, "error opening rotated log file") 466 } 467 468 fileName := fmt.Sprintf("%s.%d.gz", w.f.Name(), i-1) 469 decompressedFileName := fileName + tmpLogfileSuffix 470 tmpFile, err := w.filesRefCounter.GetReference(decompressedFileName, func(refFileName string, exists bool) (*os.File, error) { 471 if exists { 472 return open(refFileName) 473 } 474 return decompressfile(fileName, refFileName, config.Since) 475 }) 476 477 if err != nil { 478 if !errors.Is(err, os.ErrNotExist) { 479 return nil, errors.Wrap(err, "error getting reference to decompressed log file") 480 } 481 continue 482 } 483 if tmpFile == nil { 484 // The log before `config.Since` does not need to read 485 break 486 } 487 488 files = append(files, tmpFile) 489 continue 490 } 491 files = append(files, f) 492 } 493 494 return files, nil 495 } 496 497 func decompressfile(fileName, destFileName string, since time.Time) (*os.File, error) { 498 cf, err := open(fileName) 499 if err != nil { 500 return nil, errors.Wrap(err, "error opening file for decompression") 501 } 502 defer cf.Close() 503 504 rc, err := gzip.NewReader(cf) 505 if err != nil { 506 return nil, errors.Wrap(err, "error making gzip reader for compressed log file") 507 } 508 defer rc.Close() 509 510 // Extract the last log entry timestramp from the gzip header 511 extra := &rotateFileMetadata{} 512 err = json.Unmarshal(rc.Header.Extra, extra) 513 if err == nil && extra.LastTime.Before(since) { 514 return nil, nil 515 } 516 517 rs, err := openFile(destFileName, os.O_CREATE|os.O_RDWR, 0640) 518 if err != nil { 519 return nil, errors.Wrap(err, "error creating file for copying decompressed log stream") 520 } 521 522 _, err = pools.Copy(rs, rc) 523 if err != nil { 524 rs.Close() 525 rErr := os.Remove(rs.Name()) 526 if rErr != nil && !os.IsNotExist(rErr) { 527 logrus.Errorf("Failed to remove logfile: %v", rErr) 528 } 529 return nil, errors.Wrap(err, "error while copying decompressed log stream to file") 530 } 531 532 return rs, nil 533 } 534 535 func newSectionReader(f *os.File) (*io.SectionReader, error) { 536 // seek to the end to get the size 537 // we'll leave this at the end of the file since section reader does not advance the reader 538 size, err := f.Seek(0, io.SeekEnd) 539 if err != nil { 540 return nil, errors.Wrap(err, "error getting current file size") 541 } 542 return io.NewSectionReader(f, 0, size), nil 543 } 544 545 func tailFiles(files []SizeReaderAt, watcher *logger.LogWatcher, dec Decoder, getTailReader GetTailReaderFunc, config logger.ReadConfig, notifyEvict <-chan interface{}) (cont bool) { 546 nLines := config.Tail 547 548 ctx, cancel := context.WithCancel(context.Background()) 549 defer cancel() 550 551 cont = true 552 // TODO(@cpuguy83): we should plumb a context through instead of dealing with `WatchClose()` here. 553 go func() { 554 select { 555 case err := <-notifyEvict: 556 if err != nil { 557 watcher.Err <- err.(error) 558 cont = false 559 cancel() 560 } 561 case <-ctx.Done(): 562 case <-watcher.WatchConsumerGone(): 563 cont = false 564 cancel() 565 } 566 }() 567 568 readers := make([]io.Reader, 0, len(files)) 569 570 if config.Tail > 0 { 571 for i := len(files) - 1; i >= 0 && nLines > 0; i-- { 572 tail, n, err := getTailReader(ctx, files[i], nLines) 573 if err != nil { 574 watcher.Err <- errors.Wrap(err, "error finding file position to start log tailing") 575 return 576 } 577 nLines -= n 578 readers = append([]io.Reader{tail}, readers...) 579 } 580 } else { 581 for _, r := range files { 582 readers = append(readers, &wrappedReaderAt{ReaderAt: r}) 583 } 584 } 585 586 rdr := io.MultiReader(readers...) 587 dec.Reset(rdr) 588 589 for { 590 msg, err := dec.Decode() 591 if err != nil { 592 if !errors.Is(err, io.EOF) { 593 watcher.Err <- err 594 } 595 return 596 } 597 if !config.Since.IsZero() && msg.Timestamp.Before(config.Since) { 598 continue 599 } 600 if !config.Until.IsZero() && msg.Timestamp.After(config.Until) { 601 return 602 } 603 select { 604 case <-ctx.Done(): 605 return 606 case watcher.Msg <- msg: 607 } 608 } 609 } 610 611 func followLogs(f *os.File, logWatcher *logger.LogWatcher, notifyRotate, notifyEvict chan interface{}, dec Decoder, since, until time.Time) { 612 dec.Reset(f) 613 614 name := f.Name() 615 fileWatcher, err := watchFile(name) 616 if err != nil { 617 logWatcher.Err <- err 618 return 619 } 620 defer func() { 621 f.Close() 622 dec.Close() 623 fileWatcher.Close() 624 }() 625 626 var retries int 627 handleRotate := func() error { 628 f.Close() 629 fileWatcher.Remove(name) 630 631 // retry when the file doesn't exist 632 for retries := 0; retries <= 5; retries++ { 633 f, err = open(name) 634 if err == nil || !os.IsNotExist(err) { 635 break 636 } 637 } 638 if err != nil { 639 return err 640 } 641 if err := fileWatcher.Add(name); err != nil { 642 return err 643 } 644 dec.Reset(f) 645 return nil 646 } 647 648 errRetry := errors.New("retry") 649 errDone := errors.New("done") 650 651 handleMustClose := func(evictErr error) { 652 f.Close() 653 dec.Close() 654 logWatcher.Err <- errors.Wrap(err, "log reader evicted due to errors") 655 logrus.WithField("file", f.Name()).Error("Log reader notified that it must re-open log file, some log data may not be streamed to the client.") 656 } 657 658 waitRead := func() error { 659 select { 660 case e := <-notifyEvict: 661 if e != nil { 662 err := e.(error) 663 handleMustClose(err) 664 } 665 return errDone 666 case e := <-fileWatcher.Events(): 667 switch e.Op { 668 case fsnotify.Write: 669 dec.Reset(f) 670 return nil 671 case fsnotify.Rename, fsnotify.Remove: 672 select { 673 case <-notifyRotate: 674 case <-logWatcher.WatchProducerGone(): 675 return errDone 676 case <-logWatcher.WatchConsumerGone(): 677 return errDone 678 } 679 if err := handleRotate(); err != nil { 680 return err 681 } 682 return nil 683 } 684 return errRetry 685 case err := <-fileWatcher.Errors(): 686 logrus.Debugf("logger got error watching file: %v", err) 687 // Something happened, let's try and stay alive and create a new watcher 688 if retries <= 5 { 689 fileWatcher.Close() 690 fileWatcher, err = watchFile(name) 691 if err != nil { 692 return err 693 } 694 retries++ 695 return errRetry 696 } 697 return err 698 case <-logWatcher.WatchProducerGone(): 699 return errDone 700 case <-logWatcher.WatchConsumerGone(): 701 return errDone 702 } 703 } 704 705 oldSize := int64(-1) 706 handleDecodeErr := func(err error) error { 707 if !errors.Is(err, io.EOF) { 708 return err 709 } 710 711 // Handle special case (#39235): max-file=1 and file was truncated 712 st, stErr := f.Stat() 713 if stErr == nil { 714 size := st.Size() 715 defer func() { oldSize = size }() 716 if size < oldSize { // truncated 717 f.Seek(0, 0) 718 return nil 719 } 720 } else { 721 logrus.WithError(stErr).Warn("logger: stat error") 722 } 723 724 for { 725 err := waitRead() 726 if err == nil { 727 break 728 } 729 if err == errRetry { 730 continue 731 } 732 return err 733 } 734 return nil 735 } 736 737 // main loop 738 for { 739 select { 740 case err := <-notifyEvict: 741 if err != nil { 742 handleMustClose(err.(error)) 743 } 744 return 745 default: 746 } 747 msg, err := dec.Decode() 748 if err != nil { 749 if err := handleDecodeErr(err); err != nil { 750 if err == errDone { 751 return 752 } 753 // we got an unrecoverable error, so return 754 logWatcher.Err <- err 755 return 756 } 757 // ready to try again 758 continue 759 } 760 761 retries = 0 // reset retries since we've succeeded 762 if !since.IsZero() && msg.Timestamp.Before(since) { 763 continue 764 } 765 if !until.IsZero() && msg.Timestamp.After(until) { 766 return 767 } 768 // send the message, unless the consumer is gone 769 select { 770 case e := <-notifyEvict: 771 if e != nil { 772 err := e.(error) 773 logrus.WithError(err).Debug("Reader evicted while sending log message") 774 logWatcher.Err <- err 775 } 776 return 777 case logWatcher.Msg <- msg: 778 case <-logWatcher.WatchConsumerGone(): 779 return 780 } 781 } 782 } 783 784 func watchFile(name string) (filenotify.FileWatcher, error) { 785 var fileWatcher filenotify.FileWatcher 786 787 if runtime.GOOS == "windows" { 788 // FileWatcher on Windows files is based on the syscall notifications which has an issue because of file caching. 789 // It is based on ReadDirectoryChangesW() which doesn't detect writes to the cache. It detects writes to disk only. 790 // Because of the OS lazy writing, we don't get notifications for file writes and thereby the watcher 791 // doesn't work. Hence for Windows we will use poll based notifier. 792 fileWatcher = filenotify.NewPollingWatcher() 793 } else { 794 var err error 795 fileWatcher, err = filenotify.New() 796 if err != nil { 797 return nil, err 798 } 799 } 800 801 logger := logrus.WithFields(logrus.Fields{ 802 "module": "logger", 803 "file": name, 804 }) 805 806 if err := fileWatcher.Add(name); err != nil { 807 // we will retry using file poller. 808 logger.WithError(err).Warnf("falling back to file poller") 809 fileWatcher.Close() 810 fileWatcher = filenotify.NewPollingWatcher() 811 812 if err := fileWatcher.Add(name); err != nil { 813 fileWatcher.Close() 814 logger.WithError(err).Debugf("error watching log file for modifications") 815 return nil, err 816 } 817 } 818 819 return fileWatcher, nil 820 } 821 822 type wrappedReaderAt struct { 823 io.ReaderAt 824 pos int64 825 } 826 827 func (r *wrappedReaderAt) Read(p []byte) (int, error) { 828 n, err := r.ReaderAt.ReadAt(p, r.pos) 829 r.pos += int64(n) 830 return n, err 831 }