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 }