|
18 | 18 | package log |
19 | 19 |
|
20 | 20 | import ( |
| 21 | + "compress/gzip" |
21 | 22 | "errors" |
22 | 23 | "fmt" |
23 | 24 | "io" |
| 25 | + "io/ioutil" |
24 | 26 | "os" |
25 | 27 | "path/filepath" |
26 | 28 | "runtime/debug" |
| 29 | + "strconv" |
27 | 30 | "strings" |
28 | 31 | "sync" |
29 | 32 | "time" |
@@ -73,6 +76,9 @@ type Logger struct { |
73 | 76 | reopenChan chan struct{} |
74 | 77 | closeChan chan struct{} |
75 | 78 | writeBufferChan chan LogBuffer |
| 79 | + size int64 |
| 80 | + millCh chan struct{} |
| 81 | + mu sync.Mutex |
76 | 82 | } |
77 | 83 |
|
78 | 84 | type LoggerInfo struct { |
@@ -181,13 +187,36 @@ func (l *Logger) start() error { |
181 | 187 | } |
182 | 188 | l.writer = writer |
183 | 189 | } else { // write to file |
| 190 | + fmt.Println(l.output) |
184 | 191 | if err := os.MkdirAll(filepath.Dir(l.output), 0755); err != nil { |
185 | 192 | return err |
186 | 193 | } |
187 | 194 | file, err := os.OpenFile(l.output, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) |
188 | 195 | if err != nil { |
189 | 196 | return err |
190 | 197 | } |
| 198 | + |
| 199 | + if fileInfo, err := os.Stat(l.output); err == nil { |
| 200 | + l.size = fileInfo.Size() |
| 201 | + } |
| 202 | + |
| 203 | + //if l.roller.MaxBackups != 0 { |
| 204 | + // fileInfoList, err := ioutil.ReadDir(filepath.Dir(l.output)) |
| 205 | + // if err != nil { |
| 206 | + // return err |
| 207 | + // } |
| 208 | + // logFilesNum := 0 |
| 209 | + // for i := range fileInfoList { |
| 210 | + // if strings.HasPrefix(fileInfoList[i].Name(), strings.TrimSuffix(path.Base(l.output), path.Ext(path.Base(l.output)))) { |
| 211 | + // logFilesNum++ |
| 212 | + // } |
| 213 | + // } |
| 214 | + // if l.roller.MaxBackups <= logFilesNum { |
| 215 | + // l.writer = file |
| 216 | + // break |
| 217 | + // } |
| 218 | + //} |
| 219 | + |
191 | 220 | if l.roller.MaxTime == 0 { |
192 | 221 | file.Close() |
193 | 222 | l.roller.Filename = l.output |
@@ -247,6 +276,7 @@ func (l *Logger) handler() { |
247 | 276 | } |
248 | 277 | } |
249 | 278 | case buf := <-l.writeBufferChan: |
| 279 | + |
250 | 280 | l.Write(buf.Bytes()) |
251 | 281 | PutLogBuffer(buf) |
252 | 282 | } |
@@ -276,6 +306,7 @@ func (l *Logger) reopen() error { |
276 | 306 | if err := closer.Close(); err != nil { |
277 | 307 | fmt.Fprintf(os.Stderr, "logger %s close error when restart, error: %v", l.output, err) |
278 | 308 | } |
| 309 | + l.mill() |
279 | 310 | return l.start() |
280 | 311 | } |
281 | 312 | return ErrReopenUnsupported |
@@ -427,7 +458,37 @@ func doRotateFunc(l *Logger, interval time.Duration) { |
427 | 458 | } |
428 | 459 |
|
429 | 460 | func (l *Logger) Write(p []byte) (n int, err error) { |
430 | | - return l.writer.Write(p) |
| 461 | + l.mu.Lock() |
| 462 | + defer l.mu.Unlock() |
| 463 | + writeLen := int64(len(p)) |
| 464 | + if writeLen > l.max() { |
| 465 | + return 0, fmt.Errorf( |
| 466 | + "write length %d exceeds maximum file size %d", writeLen, l.roller.MaxSize, |
| 467 | + ) |
| 468 | + } |
| 469 | + |
| 470 | + if l.writer == nil { |
| 471 | + if err = l.start(); err != nil { |
| 472 | + return 0, err |
| 473 | + } |
| 474 | + } |
| 475 | + |
| 476 | + if l.size+writeLen > l.max() { |
| 477 | + rollerHandler(&LoggerInfo{ |
| 478 | + LogRoller: *l.roller, |
| 479 | + FileName: l.output, |
| 480 | + CreateTime: time.Now(), |
| 481 | + }) |
| 482 | + if err := l.reopen(); err != nil { |
| 483 | + return 0, err |
| 484 | + } |
| 485 | + } |
| 486 | + |
| 487 | + n, err = l.writer.Write(p) |
| 488 | + l.size += int64(n) |
| 489 | + |
| 490 | + return n, err |
| 491 | + //return l.writer.Write(p) |
431 | 492 | } |
432 | 493 |
|
433 | 494 | func (l *Logger) Close() error { |
@@ -471,3 +532,212 @@ func parseSyslogAddress(location string) *syslogAddress { |
471 | 532 |
|
472 | 533 | return nil |
473 | 534 | } |
| 535 | + |
| 536 | +//------------------------------------------- |
| 537 | +const ( |
| 538 | + compressSuffix = ".gz" |
| 539 | + megabyte = 1024 * 1024 |
| 540 | +) |
| 541 | + |
| 542 | +// max returns the maximum size in bytes of log files before rolling. |
| 543 | +func (l *Logger) max() int64 { |
| 544 | + if l.roller.MaxSize == 0 { |
| 545 | + return int64(defaultRotateSize * megabyte) |
| 546 | + } |
| 547 | + return int64(l.roller.MaxSize) * int64(megabyte) |
| 548 | +} |
| 549 | + |
| 550 | +// millRunOnce performs compression and removal of stale log files. |
| 551 | +// Log files are compressed if enabled via configuration and old log |
| 552 | +// files are removed, keeping at most l.MaxBackups files, as long as |
| 553 | +// none of them are older than MaxAge. |
| 554 | +func (l *Logger) millRunOnce() error { |
| 555 | + if l.roller.MaxBackups == 0 && l.roller.MaxAge == 0 && !l.roller.Compress { |
| 556 | + return nil |
| 557 | + } |
| 558 | + |
| 559 | + files, err := l.oldLogFiles() |
| 560 | + if err != nil { |
| 561 | + return err |
| 562 | + } |
| 563 | + |
| 564 | + var compress, remove []LoggerInfo |
| 565 | + |
| 566 | + if l.roller.MaxBackups > 0 && l.roller.MaxBackups < len(files) { |
| 567 | + preserved := make(map[string]bool) |
| 568 | + var remaining []LoggerInfo |
| 569 | + for _, f := range files { |
| 570 | + // Only count the uncompressed log file or the |
| 571 | + // compressed log file, not both. |
| 572 | + fn := f.FileName |
| 573 | + if strings.HasSuffix(fn, compressSuffix) { |
| 574 | + fn = fn[:len(fn)-len(compressSuffix)] |
| 575 | + } |
| 576 | + preserved[fn] = true |
| 577 | + |
| 578 | + if len(preserved) > l.roller.MaxBackups { |
| 579 | + remove = append(remove, f) |
| 580 | + } else { |
| 581 | + remaining = append(remaining, f) |
| 582 | + } |
| 583 | + } |
| 584 | + files = remaining |
| 585 | + } |
| 586 | + if l.roller.MaxAge > 0 { |
| 587 | + diff := time.Duration(int64(24*time.Hour) * int64(l.roller.MaxAge)) |
| 588 | + cutoff := time.Now().Add(-1 * diff) |
| 589 | + |
| 590 | + var remaining []LoggerInfo |
| 591 | + for _, f := range files { |
| 592 | + if f.CreateTime.Before(cutoff) { |
| 593 | + remove = append(remove, f) |
| 594 | + } else { |
| 595 | + remaining = append(remaining, f) |
| 596 | + } |
| 597 | + } |
| 598 | + files = remaining |
| 599 | + } |
| 600 | + |
| 601 | + if l.roller.Compress { |
| 602 | + for _, f := range files { |
| 603 | + if !strings.HasSuffix(f.FileName, compressSuffix) { |
| 604 | + compress = append(compress, f) |
| 605 | + } |
| 606 | + } |
| 607 | + } |
| 608 | + |
| 609 | + for _, f := range remove { |
| 610 | + errRemove := os.Remove(filepath.Join(l.dir(), f.FileName)) |
| 611 | + if err == nil && errRemove != nil { |
| 612 | + err = errRemove |
| 613 | + } |
| 614 | + } |
| 615 | + for _, f := range compress { |
| 616 | + fn := filepath.Join(l.dir(), f.FileName) |
| 617 | + fnCompress := compressSuffixFile(fn + compressSuffix) |
| 618 | + errCompress := compressLogFile(fn, fnCompress) |
| 619 | + if err == nil && errCompress != nil { |
| 620 | + err = errCompress |
| 621 | + } |
| 622 | + } |
| 623 | + |
| 624 | + return err |
| 625 | +} |
| 626 | + |
| 627 | +func compressSuffixFile(fileName string) string { |
| 628 | + var num int |
| 629 | + for { |
| 630 | + if _, err := os.Stat(fileName); !os.IsNotExist(err) { |
| 631 | + return fileName |
| 632 | + } |
| 633 | + fileName = fileName + "." + strconv.Itoa(num) |
| 634 | + num++ |
| 635 | + } |
| 636 | +} |
| 637 | + |
| 638 | +// mill performs post-rotation compression and removal of stale log files, |
| 639 | +// starting the mill goroutine if necessary. |
| 640 | +func (l *Logger) mill() { |
| 641 | + l.millRunOnce() |
| 642 | +} |
| 643 | + |
| 644 | +// oldLogFiles returns the list of backup log files stored in the same |
| 645 | +// directory as the current log file, sorted by ModTime |
| 646 | +func (l *Logger) oldLogFiles() ([]LoggerInfo, error) { |
| 647 | + files, err := ioutil.ReadDir(l.dir()) |
| 648 | + if err != nil { |
| 649 | + return nil, fmt.Errorf("can't read log file directory: %s", err) |
| 650 | + } |
| 651 | + logFiles := []LoggerInfo{} |
| 652 | + |
| 653 | + for _, f := range files { |
| 654 | + if f.IsDir() { |
| 655 | + continue |
| 656 | + } |
| 657 | + if !strings.HasPrefix(f.Name(), filepath.Base(l.output)+".") { |
| 658 | + continue |
| 659 | + } |
| 660 | + //use modTime replace createTime |
| 661 | + logFiles = append(logFiles, LoggerInfo{*l.roller, f.Name(), f.ModTime()}) |
| 662 | + } |
| 663 | + |
| 664 | + return logFiles, nil |
| 665 | +} |
| 666 | + |
| 667 | +// timeFromName extracts the formatted time from the filename by stripping off |
| 668 | +// the filename's prefix and extension. This prevents someone's filename from |
| 669 | +// confusing time.parse. |
| 670 | +func (l *Logger) timeFromName(filename, prefix, ext string) (time.Time, error) { |
| 671 | + if !strings.HasPrefix(filename, prefix) { |
| 672 | + return time.Time{}, errors.New("mismatched prefix") |
| 673 | + } |
| 674 | + if !strings.HasSuffix(filename, ext) { |
| 675 | + return time.Time{}, errors.New("mismatched extension") |
| 676 | + } |
| 677 | + return time.Now(), nil |
| 678 | +} |
| 679 | + |
| 680 | +// prefixAndExt returns the filename part and extension part from the Logger's |
| 681 | +// filename. |
| 682 | +func (l *Logger) prefixAndExt() (prefix, ext string) { |
| 683 | + filename := filepath.Base(l.output) |
| 684 | + |
| 685 | + prefix = filename + "." |
| 686 | + |
| 687 | + return prefix, ext |
| 688 | +} |
| 689 | + |
| 690 | +// dir returns the directory for the current filename. |
| 691 | +func (l *Logger) dir() string { |
| 692 | + return filepath.Dir(l.output) |
| 693 | +} |
| 694 | + |
| 695 | +// compressLogFile compresses the given log file, removing the |
| 696 | +// uncompressed log file if successful. |
| 697 | +func compressLogFile(src, dst string) (err error) { |
| 698 | + f, err := os.Open(src) |
| 699 | + if err != nil { |
| 700 | + return fmt.Errorf("failed to open log file: %v", err) |
| 701 | + } |
| 702 | + defer f.Close() |
| 703 | + |
| 704 | + fi, err := os.Stat(src) |
| 705 | + if err != nil { |
| 706 | + return fmt.Errorf("failed to stat log file: %v", err) |
| 707 | + } |
| 708 | + // If this file already exists, we presume it was created by |
| 709 | + // a previous attempt to compress the log file. |
| 710 | + gzf, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fi.Mode()) |
| 711 | + if err != nil { |
| 712 | + return fmt.Errorf("failed to open compressed log file: %v", err) |
| 713 | + } |
| 714 | + defer gzf.Close() |
| 715 | + |
| 716 | + gz := gzip.NewWriter(gzf) |
| 717 | + |
| 718 | + defer func() { |
| 719 | + if err != nil { |
| 720 | + os.Remove(dst) |
| 721 | + err = fmt.Errorf("failed to compress log file: %v", err) |
| 722 | + } |
| 723 | + }() |
| 724 | + |
| 725 | + if _, err := io.Copy(gz, f); err != nil { |
| 726 | + return err |
| 727 | + } |
| 728 | + if err := gz.Close(); err != nil { |
| 729 | + return err |
| 730 | + } |
| 731 | + if err := gzf.Close(); err != nil { |
| 732 | + return err |
| 733 | + } |
| 734 | + |
| 735 | + if err := f.Close(); err != nil { |
| 736 | + return err |
| 737 | + } |
| 738 | + if err := os.Remove(src); err != nil { |
| 739 | + return err |
| 740 | + } |
| 741 | + |
| 742 | + return nil |
| 743 | +} |
0 commit comments