github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/client/driver/logging/rotator.go (about)

     1  package logging
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  )
    16  
    17  const (
    18  	bufSize  = 32768
    19  	flushDur = 100 * time.Millisecond
    20  )
    21  
    22  // FileRotator writes bytes to a rotated set of files
    23  type FileRotator struct {
    24  	MaxFiles int   // MaxFiles is the maximum number of rotated files allowed in a path
    25  	FileSize int64 // FileSize is the size a rotated file is allowed to grow
    26  
    27  	path             string // path is the path on the file system where the rotated set of files are opened
    28  	baseFileName     string // baseFileName is the base file name of the rotated files
    29  	logFileIdx       int    // logFileIdx is the current index of the rotated files
    30  	oldestLogFileIdx int    // oldestLogFileIdx is the index of the oldest log file in a path
    31  
    32  	currentFile *os.File // currentFile is the file that is currently getting written
    33  	currentWr   int64    // currentWr is the number of bytes written to the current file
    34  	bufw        *bufio.Writer
    35  	bufLock     sync.Mutex
    36  
    37  	flushTicker *time.Ticker
    38  	logger      *log.Logger
    39  	purgeCh     chan struct{}
    40  	doneCh      chan struct{}
    41  
    42  	closed     bool
    43  	closedLock sync.Mutex
    44  }
    45  
    46  // NewFileRotator returns a new file rotator
    47  func NewFileRotator(path string, baseFile string, maxFiles int,
    48  	fileSize int64, logger *log.Logger) (*FileRotator, error) {
    49  	rotator := &FileRotator{
    50  		MaxFiles: maxFiles,
    51  		FileSize: fileSize,
    52  
    53  		path:         path,
    54  		baseFileName: baseFile,
    55  
    56  		flushTicker: time.NewTicker(flushDur),
    57  		logger:      logger,
    58  		purgeCh:     make(chan struct{}, 1),
    59  		doneCh:      make(chan struct{}, 1),
    60  	}
    61  	if err := rotator.lastFile(); err != nil {
    62  		return nil, err
    63  	}
    64  	go rotator.purgeOldFiles()
    65  	go rotator.flushPeriodically()
    66  	return rotator, nil
    67  }
    68  
    69  // Write writes a byte array to a file and rotates the file if it's size becomes
    70  // equal to the maximum size the user has defined.
    71  func (f *FileRotator) Write(p []byte) (n int, err error) {
    72  	n = 0
    73  	var nw int
    74  
    75  	for n < len(p) {
    76  		// Check if we still have space in the current file, otherwise close and
    77  		// open the next file
    78  		if f.currentWr >= f.FileSize {
    79  			f.flushBuffer()
    80  			f.currentFile.Close()
    81  			if err := f.nextFile(); err != nil {
    82  				f.logger.Printf("[ERROR] driver.rotator: error creating next file: %v", err)
    83  				return 0, err
    84  			}
    85  		}
    86  		// Calculate the remaining size on this file
    87  		remainingSize := f.FileSize - f.currentWr
    88  
    89  		// Check if the number of bytes that we have to write is less than the
    90  		// remaining size of the file
    91  		if remainingSize < int64(len(p[n:])) {
    92  			// Write the number of bytes that we can write on the current file
    93  			li := int64(n) + remainingSize
    94  			nw, err = f.writeToBuffer(p[n:li])
    95  		} else {
    96  			// Write all the bytes in the current file
    97  			nw, err = f.writeToBuffer(p[n:])
    98  		}
    99  
   100  		// Increment the number of bytes written so far in this method
   101  		// invocation
   102  		n += nw
   103  
   104  		// Increment the total number of bytes in the file
   105  		f.currentWr += int64(n)
   106  		if err != nil {
   107  			f.logger.Printf("[ERROR] driver.rotator: error writing to file: %v", err)
   108  			return
   109  		}
   110  	}
   111  	return
   112  }
   113  
   114  // nextFile opens the next file and purges older files if the number of rotated
   115  // files is larger than the maximum files configured by the user
   116  func (f *FileRotator) nextFile() error {
   117  	nextFileIdx := f.logFileIdx
   118  	for {
   119  		nextFileIdx += 1
   120  		logFileName := filepath.Join(f.path, fmt.Sprintf("%s.%d", f.baseFileName, nextFileIdx))
   121  		if fi, err := os.Stat(logFileName); err == nil {
   122  			if fi.IsDir() || fi.Size() >= f.FileSize {
   123  				continue
   124  			}
   125  		}
   126  		f.logFileIdx = nextFileIdx
   127  		if err := f.createFile(); err != nil {
   128  			return err
   129  		}
   130  		break
   131  	}
   132  	// Purge old files if we have more files than MaxFiles
   133  	f.closedLock.Lock()
   134  	defer f.closedLock.Unlock()
   135  	if f.logFileIdx-f.oldestLogFileIdx >= f.MaxFiles && !f.closed {
   136  		select {
   137  		case f.purgeCh <- struct{}{}:
   138  		default:
   139  		}
   140  	}
   141  	return nil
   142  }
   143  
   144  // lastFile finds out the rotated file with the largest index in a path.
   145  func (f *FileRotator) lastFile() error {
   146  	finfos, err := ioutil.ReadDir(f.path)
   147  	if err != nil {
   148  		return err
   149  	}
   150  
   151  	prefix := fmt.Sprintf("%s.", f.baseFileName)
   152  	for _, fi := range finfos {
   153  		if fi.IsDir() {
   154  			continue
   155  		}
   156  		if strings.HasPrefix(fi.Name(), prefix) {
   157  			fileIdx := strings.TrimPrefix(fi.Name(), prefix)
   158  			n, err := strconv.Atoi(fileIdx)
   159  			if err != nil {
   160  				continue
   161  			}
   162  			if n > f.logFileIdx {
   163  				f.logFileIdx = n
   164  			}
   165  		}
   166  	}
   167  	if err := f.createFile(); err != nil {
   168  		return err
   169  	}
   170  	return nil
   171  }
   172  
   173  // createFile opens a new or existing file for writing
   174  func (f *FileRotator) createFile() error {
   175  	logFileName := filepath.Join(f.path, fmt.Sprintf("%s.%d", f.baseFileName, f.logFileIdx))
   176  	cFile, err := os.OpenFile(logFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
   177  	if err != nil {
   178  		return err
   179  	}
   180  	f.currentFile = cFile
   181  	fi, err := f.currentFile.Stat()
   182  	if err != nil {
   183  		return err
   184  	}
   185  	f.currentWr = fi.Size()
   186  	f.createOrResetBuffer()
   187  	return nil
   188  }
   189  
   190  // flushPeriodically flushes the buffered writer every 100ms to the underlying
   191  // file
   192  func (f *FileRotator) flushPeriodically() {
   193  	for _ = range f.flushTicker.C {
   194  		f.flushBuffer()
   195  	}
   196  }
   197  
   198  func (f *FileRotator) Close() {
   199  	f.closedLock.Lock()
   200  	defer f.closedLock.Unlock()
   201  
   202  	// Stop the ticker and flush for one last time
   203  	f.flushTicker.Stop()
   204  	f.flushBuffer()
   205  
   206  	// Stop the purge go routine
   207  	if !f.closed {
   208  		f.doneCh <- struct{}{}
   209  		close(f.purgeCh)
   210  		f.closed = true
   211  	}
   212  }
   213  
   214  // purgeOldFiles removes older files and keeps only the last N files rotated for
   215  // a file
   216  func (f *FileRotator) purgeOldFiles() {
   217  	for {
   218  		select {
   219  		case <-f.purgeCh:
   220  			var fIndexes []int
   221  			files, err := ioutil.ReadDir(f.path)
   222  			if err != nil {
   223  				f.logger.Printf("[ERROR] driver.rotator: error getting directory listing: %v", err)
   224  				return
   225  			}
   226  			// Inserting all the rotated files in a slice
   227  			for _, fi := range files {
   228  				if strings.HasPrefix(fi.Name(), f.baseFileName) {
   229  					fileIdx := strings.TrimPrefix(fi.Name(), fmt.Sprintf("%s.", f.baseFileName))
   230  					n, err := strconv.Atoi(fileIdx)
   231  					if err != nil {
   232  						f.logger.Printf("[ERROR] driver.rotator: error extracting file index: %v", err)
   233  						continue
   234  					}
   235  					fIndexes = append(fIndexes, n)
   236  				}
   237  			}
   238  
   239  			// Not continuing to delete files if the number of files is not more
   240  			// than MaxFiles
   241  			if len(fIndexes) <= f.MaxFiles {
   242  				continue
   243  			}
   244  
   245  			// Sorting the file indexes so that we can purge the older files and keep
   246  			// only the number of files as configured by the user
   247  			sort.Sort(sort.IntSlice(fIndexes))
   248  			toDelete := fIndexes[0 : len(fIndexes)-f.MaxFiles]
   249  			for _, fIndex := range toDelete {
   250  				fname := filepath.Join(f.path, fmt.Sprintf("%s.%d", f.baseFileName, fIndex))
   251  				err := os.RemoveAll(fname)
   252  				if err != nil {
   253  					f.logger.Printf("[ERROR] driver.rotator: error removing file: %v", err)
   254  				}
   255  			}
   256  			f.oldestLogFileIdx = fIndexes[0]
   257  		case <-f.doneCh:
   258  			return
   259  		}
   260  	}
   261  }
   262  
   263  // flushBuffer flushes the buffer
   264  func (f *FileRotator) flushBuffer() error {
   265  	f.bufLock.Lock()
   266  	defer f.bufLock.Unlock()
   267  	if f.bufw != nil {
   268  		return f.bufw.Flush()
   269  	}
   270  	return nil
   271  }
   272  
   273  // writeToBuffer writes the byte array to buffer
   274  func (f *FileRotator) writeToBuffer(p []byte) (int, error) {
   275  	f.bufLock.Lock()
   276  	defer f.bufLock.Unlock()
   277  	return f.bufw.Write(p)
   278  }
   279  
   280  // createOrResetBuffer creates a new buffer if we don't have one otherwise
   281  // resets the buffer
   282  func (f *FileRotator) createOrResetBuffer() {
   283  	f.bufLock.Lock()
   284  	defer f.bufLock.Unlock()
   285  	if f.bufw == nil {
   286  		f.bufw = bufio.NewWriterSize(f.currentFile, bufSize)
   287  	} else {
   288  		f.bufw.Reset(f.currentFile)
   289  	}
   290  }