Implemented file rotator
This commit is contained in:
parent
ef7cfb1f0b
commit
824df263aa
189
client/driver/logging/rotator.go
Normal file
189
client/driver/logging/rotator.go
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bufSize = 32
|
||||||
|
buf = make([]byte, bufSize)
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileRotator struct {
|
||||||
|
MaxFiles int
|
||||||
|
FileSize int64
|
||||||
|
|
||||||
|
path string
|
||||||
|
baseFileName string
|
||||||
|
|
||||||
|
logFileIdx int
|
||||||
|
oldestLogFileIdx int
|
||||||
|
|
||||||
|
currentFile *os.File
|
||||||
|
currentWr int64
|
||||||
|
|
||||||
|
logger *log.Logger
|
||||||
|
purgeCh chan interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileRotator(path string, baseFile string, maxFiles int, fileSize int64, logger *log.Logger) (*FileRotator, error) {
|
||||||
|
rotator := &FileRotator{
|
||||||
|
MaxFiles: maxFiles,
|
||||||
|
FileSize: fileSize,
|
||||||
|
|
||||||
|
path: path,
|
||||||
|
baseFileName: baseFile,
|
||||||
|
|
||||||
|
logger: logger,
|
||||||
|
purgeCh: make(chan interface{}),
|
||||||
|
}
|
||||||
|
if err := rotator.lastFile(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
go rotator.purgeOldFiles()
|
||||||
|
return rotator, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileRotator) Write(p []byte) (n int, err error) {
|
||||||
|
n = 0
|
||||||
|
var nw int
|
||||||
|
|
||||||
|
for n < len(p) {
|
||||||
|
if f.currentWr >= f.FileSize {
|
||||||
|
f.currentFile.Close()
|
||||||
|
if err := f.nextFile(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remainingSize := f.FileSize - f.currentWr
|
||||||
|
if remainingSize < int64(len(p[n:])) {
|
||||||
|
li := int64(n) + remainingSize
|
||||||
|
nw, err = f.currentFile.Write(p[n:li])
|
||||||
|
} else {
|
||||||
|
nw, err = f.currentFile.Write(p[n:])
|
||||||
|
}
|
||||||
|
n += nw
|
||||||
|
f.currentWr += int64(n)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileRotator) nextFile() error {
|
||||||
|
nextFileIdx := f.logFileIdx
|
||||||
|
for {
|
||||||
|
nextFileIdx += 1
|
||||||
|
logFileName := filepath.Join(f.path, fmt.Sprintf("%s.%d", f.baseFileName, nextFileIdx))
|
||||||
|
if fi, err := os.Stat(logFileName); err == nil {
|
||||||
|
if fi.IsDir() {
|
||||||
|
nextFileIdx += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fi.Size() >= f.FileSize {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.logFileIdx = nextFileIdx
|
||||||
|
if err := f.createFile(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Purge old files if we have more files than MaxFiles
|
||||||
|
if f.logFileIdx-f.oldestLogFileIdx >= f.MaxFiles {
|
||||||
|
select {
|
||||||
|
case f.purgeCh <- new(interface{}):
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileRotator) lastFile() error {
|
||||||
|
finfos, err := ioutil.ReadDir(f.path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := fmt.Sprintf("%s.", f.baseFileName)
|
||||||
|
for _, fi := range finfos {
|
||||||
|
if fi.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(fi.Name(), prefix) {
|
||||||
|
fileIdx := strings.TrimPrefix(fi.Name(), prefix)
|
||||||
|
n, err := strconv.Atoi(fileIdx)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n > f.logFileIdx {
|
||||||
|
f.logFileIdx = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := f.createFile(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileRotator) createFile() error {
|
||||||
|
logFileName := filepath.Join(f.path, fmt.Sprintf("%s.%d", f.baseFileName, f.logFileIdx))
|
||||||
|
cFile, err := os.OpenFile(logFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.currentFile = cFile
|
||||||
|
fi, err := f.currentFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.currentWr = fi.Size()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PurgeOldFiles removes older files and keeps only the last N files rotated for
|
||||||
|
// a file
|
||||||
|
func (f *FileRotator) purgeOldFiles() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-f.purgeCh:
|
||||||
|
var fIndexes []int
|
||||||
|
files, err := ioutil.ReadDir(f.path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Inserting all the rotated files in a slice
|
||||||
|
for _, fi := range files {
|
||||||
|
if strings.HasPrefix(fi.Name(), f.baseFileName) {
|
||||||
|
fileIdx := strings.TrimPrefix(fi.Name(), fmt.Sprintf("%s.", f.baseFileName))
|
||||||
|
n, err := strconv.Atoi(fileIdx)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fIndexes = append(fIndexes, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sorting the file indexes so that we can purge the older files and keep
|
||||||
|
// only the number of files as configured by the user
|
||||||
|
sort.Sort(sort.IntSlice(fIndexes))
|
||||||
|
var toDelete []int
|
||||||
|
toDelete = fIndexes[0 : len(fIndexes)-f.MaxFiles]
|
||||||
|
for _, fIndex := range toDelete {
|
||||||
|
fname := filepath.Join(f.path, fmt.Sprintf("%s.%d", f.baseFileName, fIndex))
|
||||||
|
os.RemoveAll(fname)
|
||||||
|
}
|
||||||
|
f.oldestLogFileIdx = fIndexes[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
236
client/driver/logging/rotator_test.go
Normal file
236
client/driver/logging/rotator_test.go
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logger = log.New(os.Stdout, "", log.LstdFlags)
|
||||||
|
pathPrefix = "logrotator"
|
||||||
|
baseFileName = "redis.stdout"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFileRotator_IncorrectPath(t *testing.T) {
|
||||||
|
if _, err := NewFileRotator("/foo", baseFileName, 10, 10, logger); err == nil {
|
||||||
|
t.Fatalf("expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileRotator_CreateNewFile(t *testing.T) {
|
||||||
|
var path string
|
||||||
|
var err error
|
||||||
|
if path, err = ioutil.TempDir("", pathPrefix); err != nil {
|
||||||
|
t.Fatalf("test setup err: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
_, err = NewFileRotator(path, baseFileName, 10, 10, logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test setup err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(filepath.Join(path, "redis.stdout.0")); err != nil {
|
||||||
|
t.Fatalf("expected file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileRotator_OpenLastFile(t *testing.T) {
|
||||||
|
var path string
|
||||||
|
var err error
|
||||||
|
if path, err = ioutil.TempDir("", pathPrefix); err != nil {
|
||||||
|
t.Fatalf("test setup err: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
fname1 := filepath.Join(path, "redis.stdout.0")
|
||||||
|
fname2 := filepath.Join(path, "redis.stdout.2")
|
||||||
|
if _, err := os.Create(fname1); err != nil {
|
||||||
|
t.Fatalf("test setup failure: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := os.Create(fname2); err != nil {
|
||||||
|
t.Fatalf("test setup failure: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fr, err := NewFileRotator(path, baseFileName, 10, 10, logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test setup err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fr.currentFile.Name() != fname2 {
|
||||||
|
t.Fatalf("expected current file: %v, got: %v", fname2, fr.currentFile.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileRotator_WriteToCurrentFile(t *testing.T) {
|
||||||
|
var path string
|
||||||
|
var err error
|
||||||
|
if path, err = ioutil.TempDir("", pathPrefix); err != nil {
|
||||||
|
t.Fatalf("test setup err: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
fname1 := filepath.Join(path, "redis.stdout.0")
|
||||||
|
if _, err := os.Create(fname1); err != nil {
|
||||||
|
t.Fatalf("test setup failure: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fr, err := NewFileRotator(path, baseFileName, 10, 5, logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test setup err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fr.Write([]byte("abcde"))
|
||||||
|
fi, err := os.Stat(fname1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting the file info: %v", err)
|
||||||
|
}
|
||||||
|
if fi.Size() != 5 {
|
||||||
|
t.Fatalf("expected size: %v, actual: %v", 5, fi.Size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileRotator_RotateFiles(t *testing.T) {
|
||||||
|
var path string
|
||||||
|
var err error
|
||||||
|
if path, err = ioutil.TempDir("", pathPrefix); err != nil {
|
||||||
|
t.Fatalf("test setup err: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
fr, err := NewFileRotator(path, baseFileName, 10, 5, logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test setup err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
str := "abcdefgh"
|
||||||
|
nw, err := fr.Write([]byte(str))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("got error while writing: %v", err)
|
||||||
|
}
|
||||||
|
if nw != len(str) {
|
||||||
|
t.Fatalf("expected %v, got %v", len(str), nw)
|
||||||
|
}
|
||||||
|
fname1 := filepath.Join(path, "redis.stdout.0")
|
||||||
|
fi, err := os.Stat(fname1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting the file info: %v", err)
|
||||||
|
}
|
||||||
|
if fi.Size() != 5 {
|
||||||
|
t.Fatalf("expected size: %v, actual: %v", 5, fi.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
fname2 := filepath.Join(path, "redis.stdout.1")
|
||||||
|
if _, err := os.Stat(fname2); err != nil {
|
||||||
|
t.Fatalf("expected file %v to exist", fname2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi2, err := os.Stat(fname2); err == nil {
|
||||||
|
if fi2.Size() != 3 {
|
||||||
|
t.Fatalf("expected size: %v, actual: %v", 3, fi2.Size())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("error getting the file info: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileRotator_WriteRemaining(t *testing.T) {
|
||||||
|
var path string
|
||||||
|
var err error
|
||||||
|
if path, err = ioutil.TempDir("", pathPrefix); err != nil {
|
||||||
|
t.Fatalf("test setup err: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
fname1 := filepath.Join(path, "redis.stdout.0")
|
||||||
|
if f, err := os.Create(fname1); err == nil {
|
||||||
|
f.Write([]byte("abcd"))
|
||||||
|
} else {
|
||||||
|
t.Fatalf("test setup failure: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fr, err := NewFileRotator(path, baseFileName, 10, 5, logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test setup err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
str := "efghijkl"
|
||||||
|
nw, err := fr.Write([]byte(str))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("got error while writing: %v", err)
|
||||||
|
}
|
||||||
|
if nw != len(str) {
|
||||||
|
t.Fatalf("expected %v, got %v", len(str), nw)
|
||||||
|
}
|
||||||
|
fi, err := os.Stat(fname1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting the file info: %v", err)
|
||||||
|
}
|
||||||
|
if fi.Size() != 5 {
|
||||||
|
t.Fatalf("expected size: %v, actual: %v", 5, fi.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
fname2 := filepath.Join(path, "redis.stdout.1")
|
||||||
|
if _, err := os.Stat(fname2); err != nil {
|
||||||
|
t.Fatalf("expected file %v to exist", fname2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi2, err := os.Stat(fname2); err == nil {
|
||||||
|
if fi2.Size() != 5 {
|
||||||
|
t.Fatalf("expected size: %v, actual: %v", 5, fi2.Size())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("error getting the file info: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fname3 := filepath.Join(path, "redis.stdout.2")
|
||||||
|
if _, err := os.Stat(fname3); err != nil {
|
||||||
|
t.Fatalf("expected file %v to exist", fname3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi3, err := os.Stat(fname3); err == nil {
|
||||||
|
if fi3.Size() != 2 {
|
||||||
|
t.Fatalf("expected size: %v, actual: %v", 2, fi3.Size())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("error getting the file info: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileRotator_PurgeOldFiles(t *testing.T) {
|
||||||
|
var path string
|
||||||
|
var err error
|
||||||
|
if path, err = ioutil.TempDir("", pathPrefix); err != nil {
|
||||||
|
t.Fatalf("test setup err: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
fr, err := NewFileRotator(path, baseFileName, 2, 2, logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test setup err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
str := "abcdeghijklmn"
|
||||||
|
nw, err := fr.Write([]byte(str))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("got error while writing: %v", err)
|
||||||
|
}
|
||||||
|
if nw != len(str) {
|
||||||
|
t.Fatalf("expected %v, got %v", len(str), nw)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
f, err := ioutil.ReadDir(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(f) != 2 {
|
||||||
|
t.Fatalf("expected number of files: %v, got: %v", 2, len(f))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue