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