diff --git a/client/driver/logs.go b/client/driver/logs.go index 262686566..4e6b059c2 100644 --- a/client/driver/logs.go +++ b/client/driver/logs.go @@ -7,6 +7,7 @@ import ( "log" "os" "path/filepath" + "sort" "strconv" "strings" ) @@ -15,6 +16,8 @@ const ( bufSize = 32 * 1024 ) +// LogRotator rotates files for a buffer and retains only the last N rotated +// files type LogRotator struct { maxFiles int fileSize int64 @@ -25,6 +28,7 @@ type LogRotator struct { logger *log.Logger } +// NewLogRotator configures and returns a new LogRotator func NewLogRotator(path string, fileName string, maxFiles int, fileSize int64, logger *log.Logger) (*LogRotator, error) { files, err := ioutil.ReadDir(path) if err != nil { @@ -55,6 +59,8 @@ func NewLogRotator(path string, fileName string, maxFiles int, fileSize int64, l }, nil } +// Start reads from a Reader and writes them to files and rotates them when the +// size of the file becomes equal to the max size configured func (l *LogRotator) Start(r io.Reader) error { buf := make([]byte, bufSize) for { @@ -111,3 +117,32 @@ func (l *LogRotator) Start(r io.Reader) error { } return nil } + +// PurgeOldFiles removes older files and keeps only the last N files rotated for +// a file +func (l *LogRotator) PurgeOldFiles() { + fIndexes := make([]int, l.maxFiles) + files, err := ioutil.ReadDir(l.path) + if err != nil { + return + } + count := 0 + for _, f := range files { + if strings.HasPrefix(f.Name(), l.fileName) { + fileIdx := strings.TrimPrefix(f.Name(), fmt.Sprintf("%s.", l.fileName)) + n, err := strconv.Atoi(fileIdx) + if err != nil { + continue + } + if count == l.maxFiles { + sort.Sort(sort.Reverse(sort.IntSlice(fIndexes))) + fname := filepath.Join(l.path, fmt.Sprintf("%s.%d", l.fileName, fIndexes[count-1])) + l.logger.Printf("[INFO] removing file: %v", fname) + os.RemoveAll(fname) + count -= 1 + } + fIndexes[count] = n + count += 1 + } + } +} diff --git a/client/driver/logs_test.go b/client/driver/logs_test.go index 436159434..348663fd1 100644 --- a/client/driver/logs_test.go +++ b/client/driver/logs_test.go @@ -12,6 +12,7 @@ import ( var ( logger = log.New(os.Stdout, "", log.LstdFlags) + path = "/tmp/logrotator" ) func TestLogRotator_IncorrectPath(t *testing.T) { @@ -23,7 +24,6 @@ func TestLogRotator_IncorrectPath(t *testing.T) { } func TestLogRotator_FindCorrectIndex(t *testing.T) { - path := "/tmp/tmplogrator" if err := os.Mkdir(path, os.ModeDir|os.ModePerm); err != nil { t.Fatalf("test setup err: %v", err) } @@ -49,7 +49,6 @@ func TestLogRotator_FindCorrectIndex(t *testing.T) { } func TestLogRotator_AppendToCurrentFile(t *testing.T) { - path := "/tmp/tmplogrator" defer os.RemoveAll(path) if err := os.Mkdir(path, os.ModeDir|os.ModePerm); err != nil { t.Fatalf("test setup err: %v", err) @@ -84,7 +83,6 @@ func TestLogRotator_AppendToCurrentFile(t *testing.T) { } func TestLogRotator_RotateFiles(t *testing.T) { - path := "/tmp/tmplogrator" defer os.RemoveAll(path) if err := os.Mkdir(path, os.ModeDir|os.ModePerm); err != nil { t.Fatalf("test setup err: %v", err) @@ -127,7 +125,6 @@ func TestLogRotator_RotateFiles(t *testing.T) { } func TestLogRotator_StartFromEmptyDir(t *testing.T) { - path := "/tmp/tmplogrator" defer os.RemoveAll(path) if err := os.Mkdir(path, os.ModeDir|os.ModePerm); err != nil { t.Fatalf("test setup err: %v", err) @@ -159,7 +156,6 @@ func TestLogRotator_StartFromEmptyDir(t *testing.T) { } func TestLogRotator_SetPathAsFile(t *testing.T) { - path := "/tmp/tmplogrator" defer os.RemoveAll(path) if _, err := os.Create(path); err != nil { t.Fatalf("test setup problem: %v", err) @@ -172,7 +168,6 @@ func TestLogRotator_SetPathAsFile(t *testing.T) { } func TestLogRotator_ExcludeDirs(t *testing.T) { - path := "/tmp/tmplogrator" defer os.RemoveAll(path) if err := os.Mkdir(path, os.ModeDir|os.ModePerm); err != nil { t.Fatalf("test setup err: %v", err) @@ -196,7 +191,7 @@ func TestLogRotator_ExcludeDirs(t *testing.T) { t.Fatalf("Failure in logrotator start %v", err) } - finfo, err := os.Stat("/tmp/tmplogrator/redis.stdout.1") + finfo, err := os.Stat(filepath.Join(path, "redis.stdout.1")) if err != nil { t.Fatal("expected rotator to create redis.stdout.1") } @@ -204,3 +199,35 @@ func TestLogRotator_ExcludeDirs(t *testing.T) { t.Fatalf("expected size: %v, actual: %v", 2, finfo.Size()) } } + +func TestLogRotator_PurgeDirs(t *testing.T) { + defer os.RemoveAll(path) + if err := os.Mkdir(path, os.ModeDir|os.ModePerm); err != nil { + t.Fatalf("test setup err: %v", err) + } + + l, err := NewLogRotator(path, "redis.stdout", 2, 4, logger) + if err != nil { + t.Fatalf("test setup err: %v", err) + } + + r, w := io.Pipe() + go func() { + w.Write([]byte("abcdefghijklmno")) + w.Close() + }() + + err = l.Start(r) + if err != nil && err != io.EOF { + t.Fatalf("failure in logrotator start: %v", err) + } + l.PurgeOldFiles() + + files, err := ioutil.ReadDir(path) + if err != nil { + t.Fatalf("err: %v", err) + } + if len(files) != 2 { + t.Fatalf("expected number of files: %v, actual: %v", 2, len(files)) + } +}