github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/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 { 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 notifyRotate *pubsub.Publisher 91 marshal logger.MarshalFunc 92 createDecoder makeDecoderFunc 93 getTailReader GetTailReaderFunc 94 perms os.FileMode 95 } 96 97 type makeDecoderFunc func(rdr io.Reader) func() (*logger.Message, error) 98 99 // SizeReaderAt defines a ReaderAt that also reports its size. 100 // This is used for tailing log files. 101 type SizeReaderAt interface { 102 io.ReaderAt 103 Size() int64 104 } 105 106 // GetTailReaderFunc is used to truncate a reader to only read as much as is required 107 // in order to get the passed in number of log lines. 108 // It returns the sectioned reader, the number of lines that the section reader 109 // contains, and any error that occurs. 110 type GetTailReaderFunc func(ctx context.Context, f SizeReaderAt, nLogLines int) (rdr io.Reader, nLines int, err error) 111 112 // NewLogFile creates new LogFile 113 func NewLogFile(logPath string, capacity int64, maxFiles int, compress bool, marshaller logger.MarshalFunc, decodeFunc makeDecoderFunc, perms os.FileMode, getTailReader GetTailReaderFunc) (*LogFile, error) { 114 log, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, perms) 115 if err != nil { 116 return nil, err 117 } 118 119 size, err := log.Seek(0, os.SEEK_END) 120 if err != nil { 121 return nil, err 122 } 123 124 return &LogFile{ 125 f: log, 126 capacity: capacity, 127 currentSize: size, 128 maxFiles: maxFiles, 129 compress: compress, 130 filesRefCounter: refCounter{counter: make(map[string]int)}, 131 notifyRotate: pubsub.NewPublisher(0, 1), 132 marshal: marshaller, 133 createDecoder: decodeFunc, 134 perms: perms, 135 getTailReader: getTailReader, 136 }, nil 137 } 138 139 // WriteLogEntry writes the provided log message to the current log file. 140 // This may trigger a rotation event if the max file/capacity limits are hit. 141 func (w *LogFile) WriteLogEntry(msg *logger.Message) error { 142 b, err := w.marshal(msg) 143 if err != nil { 144 return errors.Wrap(err, "error marshalling log message") 145 } 146 147 logger.PutMessage(msg) 148 149 w.mu.Lock() 150 if w.closed { 151 w.mu.Unlock() 152 return errors.New("cannot write because the output file was closed") 153 } 154 155 if err := w.checkCapacityAndRotate(); err != nil { 156 w.mu.Unlock() 157 return err 158 } 159 160 n, err := w.f.Write(b) 161 if err == nil { 162 w.currentSize += int64(n) 163 w.lastTimestamp = msg.Timestamp 164 } 165 w.mu.Unlock() 166 return err 167 } 168 169 func (w *LogFile) checkCapacityAndRotate() error { 170 if w.capacity == -1 { 171 return nil 172 } 173 174 if w.currentSize >= w.capacity { 175 w.rotateMu.Lock() 176 fname := w.f.Name() 177 if err := w.f.Close(); err != nil { 178 w.rotateMu.Unlock() 179 return errors.Wrap(err, "error closing file") 180 } 181 if err := rotate(fname, w.maxFiles, w.compress); err != nil { 182 w.rotateMu.Unlock() 183 return err 184 } 185 file, err := os.OpenFile(fname, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, w.perms) 186 if err != nil { 187 w.rotateMu.Unlock() 188 return err 189 } 190 w.f = file 191 w.currentSize = 0 192 w.notifyRotate.Publish(struct{}{}) 193 194 if w.maxFiles <= 1 || !w.compress { 195 w.rotateMu.Unlock() 196 return nil 197 } 198 199 go func() { 200 compressFile(fname+".1", w.lastTimestamp) 201 w.rotateMu.Unlock() 202 }() 203 } 204 205 return nil 206 } 207 208 func rotate(name string, maxFiles int, compress bool) error { 209 if maxFiles < 2 { 210 return nil 211 } 212 213 var extension string 214 if compress { 215 extension = ".gz" 216 } 217 218 lastFile := fmt.Sprintf("%s.%d%s", name, maxFiles-1, extension) 219 err := os.Remove(lastFile) 220 if err != nil && !os.IsNotExist(err) { 221 return errors.Wrap(err, "error removing oldest log file") 222 } 223 224 for i := maxFiles - 1; i > 1; i-- { 225 toPath := name + "." + strconv.Itoa(i) + extension 226 fromPath := name + "." + strconv.Itoa(i-1) + extension 227 if err := os.Rename(fromPath, toPath); err != nil && !os.IsNotExist(err) { 228 return err 229 } 230 } 231 232 if err := os.Rename(name, name+".1"); err != nil && !os.IsNotExist(err) { 233 return err 234 } 235 236 return nil 237 } 238 239 func compressFile(fileName string, lastTimestamp time.Time) { 240 file, err := os.Open(fileName) 241 if err != nil { 242 logrus.Errorf("Failed to open log file: %v", err) 243 return 244 } 245 defer func() { 246 file.Close() 247 err := os.Remove(fileName) 248 if err != nil { 249 logrus.Errorf("Failed to remove source log file: %v", err) 250 } 251 }() 252 253 outFile, err := os.OpenFile(fileName+".gz", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0640) 254 if err != nil { 255 logrus.Errorf("Failed to open or create gzip log file: %v", err) 256 return 257 } 258 defer func() { 259 outFile.Close() 260 if err != nil { 261 os.Remove(fileName + ".gz") 262 } 263 }() 264 265 compressWriter := gzip.NewWriter(outFile) 266 defer compressWriter.Close() 267 268 // Add the last log entry timestramp to the gzip header 269 extra := rotateFileMetadata{} 270 extra.LastTime = lastTimestamp 271 compressWriter.Header.Extra, err = json.Marshal(&extra) 272 if err != nil { 273 // Here log the error only and don't return since this is just an optimization. 274 logrus.Warningf("Failed to marshal gzip header as JSON: %v", err) 275 } 276 277 _, err = pools.Copy(compressWriter, file) 278 if err != nil { 279 logrus.WithError(err).WithField("module", "container.logs").WithField("file", fileName).Error("Error compressing log file") 280 return 281 } 282 } 283 284 // MaxFiles return maximum number of files 285 func (w *LogFile) MaxFiles() int { 286 return w.maxFiles 287 } 288 289 // Close closes underlying file and signals all readers to stop. 290 func (w *LogFile) Close() error { 291 w.mu.Lock() 292 defer w.mu.Unlock() 293 if w.closed { 294 return nil 295 } 296 if err := w.f.Close(); err != nil { 297 return err 298 } 299 w.closed = true 300 return nil 301 } 302 303 // ReadLogs decodes entries from log files and sends them the passed in watcher 304 // 305 // Note: Using the follow option can become inconsistent in cases with very frequent rotations and max log files is 1. 306 // TODO: Consider a different implementation which can effectively follow logs under frequent rotations. 307 func (w *LogFile) ReadLogs(config logger.ReadConfig, watcher *logger.LogWatcher) { 308 w.mu.RLock() 309 currentFile, err := os.Open(w.f.Name()) 310 if err != nil { 311 w.mu.RUnlock() 312 watcher.Err <- err 313 return 314 } 315 defer currentFile.Close() 316 317 currentChunk, err := newSectionReader(currentFile) 318 if err != nil { 319 w.mu.RUnlock() 320 watcher.Err <- err 321 return 322 } 323 324 if config.Tail != 0 { 325 // TODO(@cpuguy83): Instead of opening every file, only get the files which 326 // are needed to tail. 327 // This is especially costly when compression is enabled. 328 files, err := w.openRotatedFiles(config) 329 w.mu.RUnlock() 330 if err != nil { 331 watcher.Err <- err 332 return 333 } 334 335 closeFiles := func() { 336 for _, f := range files { 337 f.Close() 338 fileName := f.Name() 339 if strings.HasSuffix(fileName, tmpLogfileSuffix) { 340 err := w.filesRefCounter.Dereference(fileName) 341 if err != nil { 342 logrus.Errorf("Failed to dereference the log file %q: %v", fileName, err) 343 } 344 } 345 } 346 } 347 348 readers := make([]SizeReaderAt, 0, len(files)+1) 349 for _, f := range files { 350 stat, err := f.Stat() 351 if err != nil { 352 watcher.Err <- errors.Wrap(err, "error reading size of rotated file") 353 closeFiles() 354 return 355 } 356 readers = append(readers, io.NewSectionReader(f, 0, stat.Size())) 357 } 358 if currentChunk.Size() > 0 { 359 readers = append(readers, currentChunk) 360 } 361 362 tailFiles(readers, watcher, w.createDecoder, w.getTailReader, config) 363 closeFiles() 364 365 w.mu.RLock() 366 } 367 368 if !config.Follow || w.closed { 369 w.mu.RUnlock() 370 return 371 } 372 w.mu.RUnlock() 373 374 notifyRotate := w.notifyRotate.Subscribe() 375 defer w.notifyRotate.Evict(notifyRotate) 376 followLogs(currentFile, watcher, notifyRotate, w.createDecoder, config.Since, config.Until) 377 } 378 379 func (w *LogFile) openRotatedFiles(config logger.ReadConfig) (files []*os.File, err error) { 380 w.rotateMu.Lock() 381 defer w.rotateMu.Unlock() 382 383 defer func() { 384 if err == nil { 385 return 386 } 387 for _, f := range files { 388 f.Close() 389 if strings.HasSuffix(f.Name(), tmpLogfileSuffix) { 390 err := os.Remove(f.Name()) 391 if err != nil && !os.IsNotExist(err) { 392 logrus.Warnf("Failed to remove logfile: %v", err) 393 } 394 } 395 } 396 }() 397 398 for i := w.maxFiles; i > 1; i-- { 399 f, err := os.Open(fmt.Sprintf("%s.%d", w.f.Name(), i-1)) 400 if err != nil { 401 if !os.IsNotExist(err) { 402 return nil, errors.Wrap(err, "error opening rotated log file") 403 } 404 405 fileName := fmt.Sprintf("%s.%d.gz", w.f.Name(), i-1) 406 decompressedFileName := fileName + tmpLogfileSuffix 407 tmpFile, err := w.filesRefCounter.GetReference(decompressedFileName, func(refFileName string, exists bool) (*os.File, error) { 408 if exists { 409 return os.Open(refFileName) 410 } 411 return decompressfile(fileName, refFileName, config.Since) 412 }) 413 414 if err != nil { 415 if !os.IsNotExist(errors.Cause(err)) { 416 return nil, errors.Wrap(err, "error getting reference to decompressed log file") 417 } 418 continue 419 } 420 if tmpFile == nil { 421 // The log before `config.Since` does not need to read 422 break 423 } 424 425 files = append(files, tmpFile) 426 continue 427 } 428 files = append(files, f) 429 } 430 431 return files, nil 432 } 433 434 func decompressfile(fileName, destFileName string, since time.Time) (*os.File, error) { 435 cf, err := os.Open(fileName) 436 if err != nil { 437 return nil, errors.Wrap(err, "error opening file for decompression") 438 } 439 defer cf.Close() 440 441 rc, err := gzip.NewReader(cf) 442 if err != nil { 443 return nil, errors.Wrap(err, "error making gzip reader for compressed log file") 444 } 445 defer rc.Close() 446 447 // Extract the last log entry timestramp from the gzip header 448 extra := &rotateFileMetadata{} 449 err = json.Unmarshal(rc.Header.Extra, extra) 450 if err == nil && extra.LastTime.Before(since) { 451 return nil, nil 452 } 453 454 rs, err := os.OpenFile(destFileName, os.O_CREATE|os.O_RDWR, 0640) 455 if err != nil { 456 return nil, errors.Wrap(err, "error creating file for copying decompressed log stream") 457 } 458 459 _, err = pools.Copy(rs, rc) 460 if err != nil { 461 rs.Close() 462 rErr := os.Remove(rs.Name()) 463 if rErr != nil && !os.IsNotExist(rErr) { 464 logrus.Errorf("Failed to remove logfile: %v", rErr) 465 } 466 return nil, errors.Wrap(err, "error while copying decompressed log stream to file") 467 } 468 469 return rs, nil 470 } 471 472 func newSectionReader(f *os.File) (*io.SectionReader, error) { 473 // seek to the end to get the size 474 // we'll leave this at the end of the file since section reader does not advance the reader 475 size, err := f.Seek(0, os.SEEK_END) 476 if err != nil { 477 return nil, errors.Wrap(err, "error getting current file size") 478 } 479 return io.NewSectionReader(f, 0, size), nil 480 } 481 482 func tailFiles(files []SizeReaderAt, watcher *logger.LogWatcher, createDecoder makeDecoderFunc, getTailReader GetTailReaderFunc, config logger.ReadConfig) { 483 nLines := config.Tail 484 485 ctx, cancel := context.WithCancel(context.Background()) 486 defer cancel() 487 // TODO(@cpuguy83): we should plumb a context through instead of dealing with `WatchClose()` here. 488 go func() { 489 select { 490 case <-ctx.Done(): 491 case <-watcher.WatchConsumerGone(): 492 cancel() 493 } 494 }() 495 496 readers := make([]io.Reader, 0, len(files)) 497 498 if config.Tail > 0 { 499 for i := len(files) - 1; i >= 0 && nLines > 0; i-- { 500 tail, n, err := getTailReader(ctx, files[i], nLines) 501 if err != nil { 502 watcher.Err <- errors.Wrap(err, "error finding file position to start log tailing") 503 return 504 } 505 nLines -= n 506 readers = append([]io.Reader{tail}, readers...) 507 } 508 } else { 509 for _, r := range files { 510 readers = append(readers, &wrappedReaderAt{ReaderAt: r}) 511 } 512 } 513 514 rdr := io.MultiReader(readers...) 515 decodeLogLine := createDecoder(rdr) 516 for { 517 msg, err := decodeLogLine() 518 if err != nil { 519 if errors.Cause(err) != io.EOF { 520 watcher.Err <- err 521 } 522 return 523 } 524 if !config.Since.IsZero() && msg.Timestamp.Before(config.Since) { 525 continue 526 } 527 if !config.Until.IsZero() && msg.Timestamp.After(config.Until) { 528 return 529 } 530 select { 531 case <-ctx.Done(): 532 return 533 case watcher.Msg <- msg: 534 } 535 } 536 } 537 538 func followLogs(f *os.File, logWatcher *logger.LogWatcher, notifyRotate chan interface{}, createDecoder makeDecoderFunc, since, until time.Time) { 539 decodeLogLine := createDecoder(f) 540 541 name := f.Name() 542 fileWatcher, err := watchFile(name) 543 if err != nil { 544 logWatcher.Err <- err 545 return 546 } 547 defer func() { 548 f.Close() 549 fileWatcher.Close() 550 }() 551 552 var retries int 553 handleRotate := func() error { 554 f.Close() 555 fileWatcher.Remove(name) 556 557 // retry when the file doesn't exist 558 for retries := 0; retries <= 5; retries++ { 559 f, err = os.Open(name) 560 if err == nil || !os.IsNotExist(err) { 561 break 562 } 563 } 564 if err != nil { 565 return err 566 } 567 if err := fileWatcher.Add(name); err != nil { 568 return err 569 } 570 decodeLogLine = createDecoder(f) 571 return nil 572 } 573 574 errRetry := errors.New("retry") 575 errDone := errors.New("done") 576 waitRead := func() error { 577 select { 578 case e := <-fileWatcher.Events(): 579 switch e.Op { 580 case fsnotify.Write: 581 decodeLogLine = createDecoder(f) 582 return nil 583 case fsnotify.Rename, fsnotify.Remove: 584 select { 585 case <-notifyRotate: 586 case <-logWatcher.WatchProducerGone(): 587 return errDone 588 case <-logWatcher.WatchConsumerGone(): 589 return errDone 590 } 591 if err := handleRotate(); err != nil { 592 return err 593 } 594 return nil 595 } 596 return errRetry 597 case err := <-fileWatcher.Errors(): 598 logrus.Debugf("logger got error watching file: %v", err) 599 // Something happened, let's try and stay alive and create a new watcher 600 if retries <= 5 { 601 fileWatcher.Close() 602 fileWatcher, err = watchFile(name) 603 if err != nil { 604 return err 605 } 606 retries++ 607 return errRetry 608 } 609 return err 610 case <-logWatcher.WatchProducerGone(): 611 return errDone 612 case <-logWatcher.WatchConsumerGone(): 613 return errDone 614 } 615 } 616 617 handleDecodeErr := func(err error) error { 618 if errors.Cause(err) != io.EOF { 619 return err 620 } 621 622 for { 623 err := waitRead() 624 if err == nil { 625 break 626 } 627 if err == errRetry { 628 continue 629 } 630 return err 631 } 632 return nil 633 } 634 635 // main loop 636 for { 637 msg, err := decodeLogLine() 638 if err != nil { 639 if err := handleDecodeErr(err); err != nil { 640 if err == errDone { 641 return 642 } 643 // we got an unrecoverable error, so return 644 logWatcher.Err <- err 645 return 646 } 647 // ready to try again 648 continue 649 } 650 651 retries = 0 // reset retries since we've succeeded 652 if !since.IsZero() && msg.Timestamp.Before(since) { 653 continue 654 } 655 if !until.IsZero() && msg.Timestamp.After(until) { 656 return 657 } 658 // send the message, unless the consumer is gone 659 select { 660 case logWatcher.Msg <- msg: 661 case <-logWatcher.WatchConsumerGone(): 662 return 663 } 664 } 665 } 666 667 func watchFile(name string) (filenotify.FileWatcher, error) { 668 var fileWatcher filenotify.FileWatcher 669 670 if runtime.GOOS == "windows" { 671 // FileWatcher on Windows files is based on the syscall notifications which has an issue becuase of file caching. 672 // It is based on ReadDirectoryChangesW() which doesn't detect writes to the cache. It detects writes to disk only. 673 // Becuase of the OS lazy writing, we don't get notifications for file writes and thereby the watcher 674 // doesn't work. Hence for Windows we will use poll based notifier. 675 fileWatcher = filenotify.NewPollingWatcher() 676 } else { 677 var err error 678 fileWatcher, err = filenotify.New() 679 if err != nil { 680 return nil, err 681 } 682 } 683 684 logger := logrus.WithFields(logrus.Fields{ 685 "module": "logger", 686 "file": name, 687 }) 688 689 if err := fileWatcher.Add(name); err != nil { 690 // we will retry using file poller. 691 logger.WithError(err).Warnf("falling back to file poller") 692 fileWatcher.Close() 693 fileWatcher = filenotify.NewPollingWatcher() 694 695 if err := fileWatcher.Add(name); err != nil { 696 fileWatcher.Close() 697 logger.WithError(err).Debugf("error watching log file for modifications") 698 return nil, err 699 } 700 } 701 702 return fileWatcher, nil 703 } 704 705 type wrappedReaderAt struct { 706 io.ReaderAt 707 pos int64 708 } 709 710 func (r *wrappedReaderAt) Read(p []byte) (int, error) { 711 n, err := r.ReaderAt.ReadAt(p, r.pos) 712 r.pos += int64(n) 713 return n, err 714 }