logger.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package sshlog
  2. import (
  3. "bufio"
  4. "errors"
  5. "fmt"
  6. "os"
  7. "path/filepath"
  8. "sync"
  9. "time"
  10. "github.com/cloudflare/cloudflared/logger"
  11. )
  12. const (
  13. logTimeFormat = "2006-01-02T15-04-05.000"
  14. megabyte = 1024 * 1024
  15. defaultFileSizeLimit = 100 * megabyte
  16. )
  17. // Logger will buffer and write events to disk
  18. type Logger struct {
  19. sync.Mutex
  20. filename string
  21. file *os.File
  22. writeBuffer *bufio.Writer
  23. logger logger.Service
  24. flushInterval time.Duration
  25. maxFileSize int64
  26. done chan struct{}
  27. once sync.Once
  28. }
  29. // NewLogger creates a Logger instance. A buffer is created that needs to be
  30. // drained and closed when the caller is finished, so instances should call
  31. // Close when finished with this Logger instance. Writes will be flushed to disk
  32. // every second (fsync). filename is the name of the logfile to be created. The
  33. // logger variable is a logger service that will log all i/o, filesystem error etc, that
  34. // that shouldn't end execution of the logger, but are useful to report to the
  35. // caller.
  36. func NewLogger(filename string, logger logger.Service, flushInterval time.Duration, maxFileSize int64) (*Logger, error) {
  37. if logger == nil {
  38. return nil, errors.New("logger can't be nil")
  39. }
  40. f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
  41. if err != nil {
  42. return nil, err
  43. }
  44. l := &Logger{filename: filename,
  45. file: f,
  46. writeBuffer: bufio.NewWriter(f),
  47. logger: logger,
  48. flushInterval: flushInterval,
  49. maxFileSize: maxFileSize,
  50. done: make(chan struct{}),
  51. }
  52. go l.writer()
  53. return l, nil
  54. }
  55. // Writes to a log buffer. Implements the io.Writer interface.
  56. func (l *Logger) Write(p []byte) (n int, err error) {
  57. l.Lock()
  58. defer l.Unlock()
  59. return l.writeBuffer.Write(p)
  60. }
  61. // Close drains anything left in the buffer and cleans up any resources still
  62. // in use.
  63. func (l *Logger) Close() error {
  64. l.once.Do(func() {
  65. close(l.done)
  66. })
  67. if err := l.write(); err != nil {
  68. return err
  69. }
  70. return l.file.Close()
  71. }
  72. // writer is the run loop that handles draining the write buffer and syncing
  73. // data to disk.
  74. func (l *Logger) writer() {
  75. ticker := time.NewTicker(l.flushInterval)
  76. defer ticker.Stop()
  77. for {
  78. select {
  79. case <-ticker.C:
  80. if err := l.write(); err != nil {
  81. l.logger.Errorf("%s", err)
  82. }
  83. case <-l.done:
  84. return
  85. }
  86. }
  87. }
  88. // write does the actual system write calls to disk and does a rotation if the
  89. // file size limit has been reached. Since the rotation happens at the end,
  90. // the rotation is a soft limit (aka the file can be bigger than the max limit
  91. // because of the final buffer flush)
  92. func (l *Logger) write() error {
  93. l.Lock()
  94. defer l.Unlock()
  95. if l.writeBuffer.Buffered() <= 0 {
  96. return nil
  97. }
  98. if err := l.writeBuffer.Flush(); err != nil {
  99. return err
  100. }
  101. if err := l.file.Sync(); err != nil {
  102. return err
  103. }
  104. if l.shouldRotate() {
  105. return l.rotate()
  106. }
  107. return nil
  108. }
  109. // shouldRotate checks to see if the current file should be rotated to a new
  110. // logfile.
  111. func (l *Logger) shouldRotate() bool {
  112. info, err := l.file.Stat()
  113. if err != nil {
  114. return false
  115. }
  116. return info.Size() >= l.maxFileSize
  117. }
  118. // rotate creates a new logfile with the existing filename and renames the
  119. // existing file with a current timestamp.
  120. func (l *Logger) rotate() error {
  121. if err := l.file.Close(); err != nil {
  122. return err
  123. }
  124. // move the existing file
  125. newname := rotationName(l.filename)
  126. if err := os.Rename(l.filename, newname); err != nil {
  127. return fmt.Errorf("can't rename log file: %s", err)
  128. }
  129. f, err := os.OpenFile(l.filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
  130. if err != nil {
  131. return fmt.Errorf("failed to open new logfile %s", err)
  132. }
  133. l.file = f
  134. l.writeBuffer = bufio.NewWriter(f)
  135. return nil
  136. }
  137. // rotationName creates a new filename from the given name, inserting a timestamp
  138. // between the filename and the extension.
  139. func rotationName(name string) string {
  140. dir := filepath.Dir(name)
  141. filename := filepath.Base(name)
  142. ext := filepath.Ext(filename)
  143. prefix := filename[:len(filename)-len(ext)]
  144. t := time.Now()
  145. timestamp := t.Format(logTimeFormat)
  146. return filepath.Join(dir, fmt.Sprintf("%s-%s%s", prefix, timestamp, ext))
  147. }