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 }