github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/client/driver/logs.go (about)

     1  package driver
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"log"
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  )
    14  
    15  const (
    16  	bufSize = 32 * 1024 // Max number of bytes read from a buffer
    17  )
    18  
    19  // LogRotator ingests data and writes out to a rotated set of files
    20  type LogRotator struct {
    21  	maxFiles int    // maximum number of rotated files retained by the log rotator
    22  	fileSize int64  // maximum file size of a rotated file
    23  	path     string // path where the rotated files are created
    24  	fileName string // base file name of the rotated files
    25  
    26  	logFileIdx int // index to the current file
    27  
    28  	logger *log.Logger
    29  }
    30  
    31  // NewLogRotator configures and returns a new LogRotator
    32  func NewLogRotator(path string, fileName string, maxFiles int, fileSize int64, logger *log.Logger) (*LogRotator, error) {
    33  	files, err := ioutil.ReadDir(path)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	// Finding out the log file with the largest index
    39  	logFileIdx := 0
    40  	prefix := fmt.Sprintf("%s.", fileName)
    41  	for _, f := range files {
    42  		if strings.HasPrefix(f.Name(), prefix) {
    43  			fileIdx := strings.TrimPrefix(f.Name(), prefix)
    44  			n, err := strconv.Atoi(fileIdx)
    45  			if err != nil {
    46  				continue
    47  			}
    48  			if n > logFileIdx {
    49  				logFileIdx = n
    50  			}
    51  		}
    52  	}
    53  
    54  	return &LogRotator{
    55  		maxFiles:   maxFiles,
    56  		fileSize:   fileSize,
    57  		path:       path,
    58  		fileName:   fileName,
    59  		logFileIdx: logFileIdx,
    60  		logger:     logger,
    61  	}, nil
    62  }
    63  
    64  // Start reads from a Reader and writes them to files and rotates them when the
    65  // size of the file becomes equal to the max size configured
    66  func (l *LogRotator) Start(r io.Reader) error {
    67  	buf := make([]byte, bufSize)
    68  	for {
    69  		logFileName := filepath.Join(l.path, fmt.Sprintf("%s.%d", l.fileName, l.logFileIdx))
    70  		remainingSize := l.fileSize
    71  		if f, err := os.Stat(logFileName); err == nil {
    72  			// Skipping the current file if it happens to be a directory
    73  			if f.IsDir() {
    74  				l.logFileIdx += 1
    75  				continue
    76  			}
    77  			// Calculating the remaining capacity of the log file
    78  			remainingSize = l.fileSize - f.Size()
    79  		}
    80  		f, err := os.OpenFile(logFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    81  		if err != nil {
    82  			return err
    83  		}
    84  		l.logger.Printf("[DEBUG] client.logrotator: opened a new file: %s", logFileName)
    85  
    86  		// Closing the current log file if it doesn't have any more capacity
    87  		if remainingSize <= 0 {
    88  			l.logFileIdx = l.logFileIdx + 1
    89  			f.Close()
    90  			continue
    91  		}
    92  
    93  		// Reading from the reader and writing into the current log file as long
    94  		// as it has capacity or the reader closes
    95  		for {
    96  			var nr int
    97  			var err error
    98  			if remainingSize < bufSize {
    99  				nr, err = r.Read(buf[0:remainingSize])
   100  			} else {
   101  				nr, err = r.Read(buf)
   102  			}
   103  			if err != nil {
   104  				f.Close()
   105  				return err
   106  			}
   107  			nw, err := f.Write(buf[:nr])
   108  			if err != nil {
   109  				f.Close()
   110  				return err
   111  			}
   112  			if nr != nw {
   113  				f.Close()
   114  				return fmt.Errorf("failed to write data read from the reader into file, R: %d W: %d", nr, nw)
   115  			}
   116  			remainingSize -= int64(nr)
   117  			if remainingSize < 1 {
   118  				f.Close()
   119  				break
   120  			}
   121  		}
   122  		l.logFileIdx = l.logFileIdx + 1
   123  	}
   124  	return nil
   125  }
   126  
   127  // PurgeOldFiles removes older files and keeps only the last N files rotated for
   128  // a file
   129  func (l *LogRotator) PurgeOldFiles() {
   130  	var fIndexes []int
   131  	files, err := ioutil.ReadDir(l.path)
   132  	if err != nil {
   133  		return
   134  	}
   135  	// Inserting all the rotated files in a slice
   136  	for _, f := range files {
   137  		if strings.HasPrefix(f.Name(), l.fileName) {
   138  			fileIdx := strings.TrimPrefix(f.Name(), fmt.Sprintf("%s.", l.fileName))
   139  			n, err := strconv.Atoi(fileIdx)
   140  			if err != nil {
   141  				continue
   142  			}
   143  			fIndexes = append(fIndexes, n)
   144  		}
   145  	}
   146  
   147  	// Sorting the file indexes so that we can purge the older files and keep
   148  	// only the number of files as configured by the user
   149  	sort.Sort(sort.IntSlice(fIndexes))
   150  	toDelete := fIndexes[l.maxFiles-1 : len(fIndexes)-1]
   151  	for _, fIndex := range toDelete {
   152  		fname := filepath.Join(l.path, fmt.Sprintf("%s.%d", l.fileName, fIndex))
   153  		os.RemoveAll(fname)
   154  	}
   155  }