package logger import ( "bufio" "fmt" "io/ioutil" "os" "path" "path/filepath" "regexp" "strconv" "strings" "sync" "time" ) const ( bufferSize = 256 * 1024 ) func getLastCheck(now time.Time) uint64 { return uint64(now.Year())*1000000 + uint64(now.Month())*10000 + uint64(now.Day())*100 + uint64(now.Hour()) } type syncBuffer struct { *bufio.Writer file *os.File count uint64 cur int filePath string parent *FileBackend } func (self *syncBuffer) Sync() error { return self.file.Sync() } func (self *syncBuffer) close() { self.Flush() self.Sync() self.file.Close() } func (self *syncBuffer) write(b []byte) { if !self.parent.rotateByHour && self.parent.maxSize > 0 && self.parent.rotateNum > 0 && self.count+uint64(len(b)) >= self.parent.maxSize { os.Rename(self.filePath, self.filePath+fmt.Sprintf(".%03d", self.cur)) self.cur++ if self.cur >= self.parent.rotateNum { self.cur = 0 } self.count = 0 } self.count += uint64(len(b)) self.Writer.Write(b) } type FileBackend struct { mu sync.Mutex dir string //directory for log files files [numSeverity]syncBuffer flushInterval time.Duration rotateNum int maxSize uint64 fall bool rotateByHour bool lastCheck uint64 reg *regexp.Regexp // for rotatebyhour log del... keepHours uint // keep how many hours old, only make sense when rotatebyhour is T } func (self *FileBackend) Flush() { self.mu.Lock() defer self.mu.Unlock() for i := 0; i < numSeverity; i++ { self.files[i].Flush() self.files[i].Sync() } } func (self *FileBackend) close() { self.Flush() } func (self *FileBackend) flushDaemon() { for { time.Sleep(self.flushInterval) self.Flush() } } func shouldDel(fileName string, left uint) bool { // tag should be like 2016071114 tagInt, err := strconv.Atoi(strings.Split(fileName, ".")[2]) if err != nil { return false } point := time.Now().Unix() - int64(left*3600) if getLastCheck(time.Unix(point, 0)) > uint64(tagInt) { return true } return false } func (self *FileBackend) rotateByHourDaemon() { for { time.Sleep(time.Second * 1) if self.rotateByHour { check := getLastCheck(time.Now()) if self.lastCheck < check { for i := 0; i < numSeverity; i++ { os.Rename(self.files[i].filePath, self.files[i].filePath+fmt.Sprintf(".%d", self.lastCheck)) } self.lastCheck = check } // also check log dir to del overtime files files, err := ioutil.ReadDir(self.dir) if err == nil { for _, file := range files { // exactly match, then we if file.Name() == self.reg.FindString(file.Name()) && shouldDel(file.Name(), self.keepHours) { os.Remove(filepath.Join(self.dir, file.Name())) } } } } } } func (self *FileBackend) monitorFiles() { for range time.NewTicker(time.Second * 5).C { for i := 0; i < numSeverity; i++ { fileName := path.Join(self.dir, severityName[i]+".log") if _, err := os.Stat(fileName); err != nil && os.IsNotExist(err) { if f, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { self.mu.Lock() self.files[i].close() self.files[i].Writer = bufio.NewWriterSize(f, bufferSize) self.files[i].file = f self.mu.Unlock() } } } } } func (self *FileBackend) Log(s Severity, msg []byte) { self.mu.Lock() switch s { case FATAL: self.files[FATAL].write(msg) case ERROR: self.files[ERROR].write(msg) case WARNING: self.files[WARNING].write(msg) case INFO: self.files[INFO].write(msg) case DEBUG: self.files[DEBUG].write(msg) } if self.fall && s < INFO { self.files[INFO].write(msg) } self.mu.Unlock() if s == FATAL { self.Flush() } } func (self *FileBackend) Rotate(rotateNum1 int, maxSize1 uint64) { self.rotateNum = rotateNum1 self.maxSize = maxSize1 } func (self *FileBackend) SetRotateByHour(rotateByHour bool) { self.rotateByHour = rotateByHour if self.rotateByHour { self.lastCheck = getLastCheck(time.Now()) } else { self.lastCheck = 0 } } func (self *FileBackend) SetKeepHours(hours uint) { self.keepHours = hours } func (self *FileBackend) Fall() { self.fall = true } func (self *FileBackend) SetFlushDuration(t time.Duration) { if t >= time.Second { self.flushInterval = t } else { self.flushInterval = time.Second } } func NewFileBackend(dir string) (*FileBackend, error) { if err := os.MkdirAll(dir, 0755); err != nil { return nil, err } var fb FileBackend fb.dir = dir for i := 0; i < numSeverity; i++ { fileName := path.Join(dir, severityName[i]+".log") f, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return nil, err } count := uint64(0) stat, err := f.Stat() if err == nil { count = uint64(stat.Size()) } fb.files[i] = syncBuffer{ Writer: bufio.NewWriterSize(f, bufferSize), file: f, filePath: fileName, parent: &fb, count: count, } } // default fb.flushInterval = time.Second * 3 fb.rotateNum = 20 fb.maxSize = 1024 * 1024 * 1024 fb.rotateByHour = false fb.lastCheck = 0 // init reg to match files // ONLY cover this centry... fb.reg = regexp.MustCompile("(INFO|ERROR|WARNING|DEBUG|FATAL)\\.log\\.20[0-9]{8}") fb.keepHours = 24 * 7 go fb.flushDaemon() go fb.monitorFiles() go fb.rotateByHourDaemon() return &fb, nil } func Rotate(rotateNum1 int, maxSize1 uint64) { if fileback != nil { fileback.Rotate(rotateNum1, maxSize1) } } func Fall() { if fileback != nil { fileback.Fall() } } func SetFlushDuration(t time.Duration) { if fileback != nil { fileback.SetFlushDuration(t) } } func SetRotateByHour(rotateByHour bool) { if fileback != nil { fileback.SetRotateByHour(rotateByHour) } } func SetKeepHours(hours uint) { if fileback != nil { fileback.SetKeepHours(hours) } }