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 }