package logkit import ( "bufio" "bytes" "fmt" "os" "path/filepath" "sync" "time" ) type bufferNode struct { bytes.Buffer next *bufferNode } type mFileLogger struct { writer *bufferWriter mu sync.Mutex filepath string name string freeList *bufferNode freeListMu sync.Mutex flushInterval time.Duration fileSplitSize uint64 bufferSize int } type bufferWriter struct { *bufio.Writer logPath string logName string file *os.File slot int startTime time.Time byteSize uint64 // The number of bytes written to this file maxFileSize uint64 bufferSize int } func (w *mFileLogger) getBuffer() *bufferNode { w.freeListMu.Lock() b := w.freeList if b != nil { w.freeList = b.next } w.freeListMu.Unlock() if b == nil { b = new(bufferNode) } else { b.next = nil b.Reset() } return b } func (w *mFileLogger) putBuffer(b *bufferNode) { if b.Len() >= 256 { // Let big buffers die with gc. return } w.freeListMu.Lock() b.next = w.freeList w.freeList = b w.freeListMu.Unlock() } func (w *mFileLogger) Exit() { w.flush() } func NewFileLogger(path, name string, flushInterval time.Duration, fileSplitSize uint64, bufferSize int) Writer { writer := &mFileLogger{ filepath: path, name: name, flushInterval: flushInterval, fileSplitSize: fileSplitSize, bufferSize: bufferSize, } go writer.flushDaemon() return writer } func (w *mFileLogger) flushDaemon() { for _ = range time.NewTicker(w.flushInterval).C { w.flush() } } func (w *mFileLogger) flush() { w.writer.Flush() w.writer.Sync() } func (w *mFileLogger) Write(level Level, msg string) { buf := w.getBuffer() buf.WriteString(msg) w.mu.Lock() defer w.mu.Unlock() writer := w.writer if writer == nil { w.writer = &bufferWriter{ logPath: w.filepath, logName: w.name, maxFileSize: w.fileSplitSize, bufferSize: w.bufferSize, } writer = w.writer } if err := writer.checkRotate(time.Now()); err != nil { fmt.Println("[logkit] check rotate err: " + err.Error()) return } writer.Write(buf.Bytes()) w.putBuffer(buf) } func (bufferW *bufferWriter) Write(p []byte) (int, error) { n, err := bufferW.Writer.Write(p) bufferW.byteSize += uint64(n) return n, err } func (bufferW *bufferWriter) Sync() error { return bufferW.file.Sync() } func (bufferW *bufferWriter) checkRotate(now time.Time) error { if bufferW.file == nil { return bufferW.rotate(now, 0) } sYear, sMonth, sDay := bufferW.startTime.Date() year, month, day := now.Date() if year != sYear || month != sMonth || day != sDay { return bufferW.rotate(now, 0) } if bufferW.byteSize >= bufferW.maxFileSize { return bufferW.rotate(now, bufferW.slot+1) } return nil } func (bufferW *bufferWriter) write(p []byte) (int, error) { n, err := bufferW.Writer.Write(p) bufferW.byteSize += uint64(n) return n, err } func (bufferW *bufferWriter) rotate(oldTime time.Time, slot int) error { if bufferW.file != nil { bufferW.Flush() bufferW.file.Close() var newFileName string year, month, day := oldTime.Date() if slot > 0 { newFileName = fmt.Sprintf("%s-%02d%02d%02d.%02d", bufferW.logPath, year, month, day, slot-1) } else { newFileName = fmt.Sprintf("%s-%02d%02d%02d", bufferW.logPath, year, month, day) } os.Rename(bufferW.logPath, newFileName) } if err := bufferW.openFile(bufferW.logPath, bufferW.logName); err != nil { return fmt.Errorf("rotate file error: %#v", err) } fileInfo, _ := bufferW.file.Stat() bufferW.byteSize = uint64(fileInfo.Size()) bufferW.Writer = bufio.NewWriterSize(bufferW.file, bufferW.bufferSize) bufferW.slot = slot bufferW.startTime = time.Now() bufferW.byteSize = 0 return nil } func (bufferW *bufferWriter) openFile(fileName, logName string) error { var file *os.File var err error for { file, err = os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err == nil { break } // try to create all the parent directories for specified log file // if it doesn't exist if os.IsNotExist(err) { err2 := os.MkdirAll(filepath.Dir(fileName), 0755) if err2 != nil { return err } continue } return err } bufferW.file = file return nil }