Skip to content

Commit

Permalink
feature: add support for maximum log size and max number of log files
Browse files Browse the repository at this point in the history
Add max-size and max-file flags followed by --log-opt in pouchd.
Example:
  pouchd --log-opt max-size=100m --log-opt max-file=5
This will rotate container log when its size exceeds 100Mb, and
it is saved in 5 log files at most.
Note that max-size and max-file flags are only supported in jsonfile
driver.

Signed-off-by: Wangrui <[email protected]>
  • Loading branch information
zjumoon01 committed Sep 25, 2018
1 parent a1afa11 commit 52f7be1
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 14 deletions.
2 changes: 1 addition & 1 deletion daemon/containerio/jsonfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (jf *jsonFile) Init(opt *Option) error {
}

logPath := filepath.Join(rootDir, jsonFilePathName)
w, err := jsonfile.NewJSONLogFile(logPath, 0644)
w, err := jsonfile.NewJSONLogFile(logPath, 0644, opt.info.LogConfig)
if err != nil {
return err
}
Expand Down
109 changes: 99 additions & 10 deletions daemon/logger/jsonfile/jsonfile.go
Original file line number Diff line number Diff line change
@@ -1,32 +1,68 @@
package jsonfile

import (
"fmt"
"os"
"strconv"
"sync"

"github.com/alibaba/pouch/daemon/logger"
"github.com/alibaba/pouch/pkg/bytefmt"
)

// JSONLogFile is uses to log the container's stdout and stderr.
type JSONLogFile struct {
mu sync.Mutex

f *os.File
perms os.FileMode
closed bool
mu sync.Mutex
f *os.File
perms os.FileMode
closed bool
maxSize uint64 // maximum size of log in byte
currentSize uint64 // current size of the latest log in byte
maxFile int // maximum number of logs
}

// NewJSONLogFile returns new JSONLogFile instance.
func NewJSONLogFile(logPath string, perms os.FileMode) (*JSONLogFile, error) {
func NewJSONLogFile(logPath string, perms os.FileMode, logConfig map[string]string) (*JSONLogFile, error) {
var (
currentsize uint64
maxsize uint64
maxfiles = 1
)
f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, perms)
if err != nil {
return nil, err
}

if logConfig != nil {
size, err := f.Seek(0, os.SEEK_END)
if err != nil {
return nil, err
}
currentsize = uint64(size)
if maxsizeString, ok := logConfig["max-size"]; ok {
maxsize, err = bytefmt.ToBytes(maxsizeString)
if err != nil {
return nil, err
}
}
if maxfileString, ok := logConfig["max-file"]; ok {
maxfiles, err = strconv.Atoi(maxfileString)
if err != nil {
return nil, err
}
if maxfiles < 1 {
return nil, fmt.Errorf("max-file cannot be less than 1")
}
}
}

return &JSONLogFile{
f: f,
perms: perms,
closed: false,
f: f,
perms: perms,
closed: false,
maxSize: maxsize,
currentSize: currentsize,
maxFile: maxfiles,
}, nil
}

Expand All @@ -39,10 +75,63 @@ func (lf *JSONLogFile) WriteLogMessage(msg *logger.LogMessage) error {

lf.mu.Lock()
defer lf.mu.Unlock()
_, err = lf.f.Write(b)
if err = lf.checkRotate(); err != nil {
return err
}

n, err := lf.f.Write(b)
if err == nil {
lf.currentSize += uint64(n)
}
return err
}

// checkRotate rotates logs according to maxSize and maxFile parameters
// TODO: after rotating logs, a notice should be made to the log reader
func (lf *JSONLogFile) checkRotate() error {
if lf.maxSize == 0 || lf.maxFile < 2 || lf.currentSize < lf.maxSize {
// no need to rotate
return nil
}

logname := lf.f.Name()
// step1. close current log file
if err := lf.f.Close(); err != nil {
return err
}
// step2. rotate logs. move x.log.(n-1) to x.log.n
if err := rotate(logname, lf.maxFile); err != nil {
return err
}
// step3. reopen new log file with the same name
newfile, err := os.OpenFile(logname, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
if err != nil {
return err
}
lf.f = newfile
lf.currentSize = 0

return nil
}

func rotate(logname string, maxFiles int) error {
if maxFiles < 2 {
return nil
}
for i := maxFiles - 1; i > 1; i-- {
newName := logname + "." + strconv.Itoa(i)
oldName := logname + "." + strconv.Itoa(i-1)
if err := os.Rename(oldName, newName); err != nil && !os.IsNotExist(err) {
return err
}
}

if err := os.Rename(logname, logname+".1"); err != nil && !os.IsNotExist(err) {
return err
}
return nil
}

// Close closes the file.
func (lf *JSONLogFile) Close() error {
lf.mu.Lock()
Expand Down
4 changes: 2 additions & 2 deletions daemon/logger/jsonfile/jsonfile_read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestReadLogMessagesWithRemoveFileInFollowMode(t *testing.T) {
defer f.Close()
defer os.RemoveAll(f.Name())

jf, err := NewJSONLogFile(f.Name(), 0640)
jf, err := NewJSONLogFile(f.Name(), 0640, nil)
if err != nil {
t.Fatalf("unexpected error during create JSONLogFile: %v", err)
}
Expand Down Expand Up @@ -52,7 +52,7 @@ func TestReadLogMessagesForEmptyFileWithoutFollow(t *testing.T) {
defer f.Close()
defer os.RemoveAll(f.Name())

jf, err := NewJSONLogFile(f.Name(), 0644)
jf, err := NewJSONLogFile(f.Name(), 0644, nil)
if err != nil {
t.Fatalf("unexpected error during create JSONLogFile: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/mgr/container_logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (mgr *ContainerManager) Logs(ctx context.Context, name string, logOpt *type
}

fileName := filepath.Join(mgr.Store.Path(c.ID), "json.log")
jf, err := jsonfile.NewJSONLogFile(fileName, 0640)
jf, err := jsonfile.NewJSONLogFile(fileName, 0640, nil)
if err != nil {
return nil, false, err
}
Expand Down

0 comments on commit 52f7be1

Please sign in to comment.