log: added option to rotate logs based on number of hours with a maximum of 24 (#1735)
log: added daily rotate option which overrides hourly
This commit is contained in:
parent
8a430c06a9
commit
5ba63e3071
@ -10,42 +10,51 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type HourTicker struct {
|
||||
type TimeTicker struct {
|
||||
stop chan struct{}
|
||||
C <-chan time.Time
|
||||
}
|
||||
|
||||
func NewHourTicker() *HourTicker {
|
||||
ht := &HourTicker{
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
ht.C = ht.Ticker()
|
||||
return ht
|
||||
}
|
||||
|
||||
func (ht *HourTicker) Stop() {
|
||||
ht.stop <- struct{}{}
|
||||
}
|
||||
|
||||
func (ht *HourTicker) Ticker() <-chan time.Time {
|
||||
// NewTimeTicker creates a TimeTicker that notifies based on rotateHours parameter.
|
||||
// if rotateHours is 1 and current time is 11:32 it means that the ticker will tick at 12:00
|
||||
// if rotateHours is 5 and current time is 09:12 means that the ticker will tick at 11:00
|
||||
func NewTimeTicker(rotateHours int) *TimeTicker {
|
||||
ch := make(chan time.Time)
|
||||
tt := TimeTicker{
|
||||
stop: make(chan struct{}),
|
||||
C: ch,
|
||||
}
|
||||
|
||||
tt.startTicker(ch, rotateHours)
|
||||
|
||||
return &tt
|
||||
}
|
||||
|
||||
func (tt *TimeTicker) Stop() {
|
||||
tt.stop <- struct{}{}
|
||||
}
|
||||
|
||||
func (tt *TimeTicker) startTicker(ch chan time.Time, rotateHours int) {
|
||||
go func() {
|
||||
hour := time.Now().Hour()
|
||||
nextRotationHour := getNextRotationHour(time.Now(), rotateHours)
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case t := <-ticker.C:
|
||||
if t.Hour() != hour {
|
||||
if t.Hour() == nextRotationHour {
|
||||
ch <- t
|
||||
hour = t.Hour()
|
||||
nextRotationHour = getNextRotationHour(time.Now(), rotateHours)
|
||||
}
|
||||
case <-ht.stop:
|
||||
case <-tt.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func getNextRotationHour(now time.Time, delta int) int {
|
||||
return now.Add(time.Hour * time.Duration(delta)).Hour()
|
||||
}
|
||||
|
||||
type AsyncFileWriter struct {
|
||||
@ -56,10 +65,10 @@ type AsyncFileWriter struct {
|
||||
started int32
|
||||
buf chan []byte
|
||||
stop chan struct{}
|
||||
hourTicker *HourTicker
|
||||
timeTicker *TimeTicker
|
||||
}
|
||||
|
||||
func NewAsyncFileWriter(filePath string, bufSize int64) *AsyncFileWriter {
|
||||
func NewAsyncFileWriter(filePath string, maxBytesSize int64, rotateHours int) *AsyncFileWriter {
|
||||
absFilePath, err := filepath.Abs(filePath)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("get file path of logger error. filePath=%s, err=%s", filePath, err))
|
||||
@ -67,9 +76,9 @@ func NewAsyncFileWriter(filePath string, bufSize int64) *AsyncFileWriter {
|
||||
|
||||
return &AsyncFileWriter{
|
||||
filePath: absFilePath,
|
||||
buf: make(chan []byte, bufSize),
|
||||
buf: make(chan []byte, maxBytesSize),
|
||||
stop: make(chan struct{}),
|
||||
hourTicker: NewHourTicker(),
|
||||
timeTicker: NewTimeTicker(rotateHours),
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,7 +168,7 @@ func (w *AsyncFileWriter) SyncWrite(msg []byte) {
|
||||
|
||||
func (w *AsyncFileWriter) rotateFile() {
|
||||
select {
|
||||
case <-w.hourTicker.C:
|
||||
case <-w.timeTicker.C:
|
||||
if err := w.flushAndClose(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "flush and close file error. err=%s", err)
|
||||
}
|
||||
@ -174,7 +183,7 @@ func (w *AsyncFileWriter) Stop() {
|
||||
w.stop <- struct{}{}
|
||||
w.wg.Wait()
|
||||
|
||||
w.hourTicker.Stop()
|
||||
w.timeTicker.Stop()
|
||||
}
|
||||
|
||||
func (w *AsyncFileWriter) Write(msg []byte) (n int, err error) {
|
||||
|
@ -1,26 +1,69 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWriter(t *testing.T) {
|
||||
w := NewAsyncFileWriter("./hello.log", 100)
|
||||
func TestWriterHourly(t *testing.T) {
|
||||
w := NewAsyncFileWriter("./hello.log", 100, 1)
|
||||
w.Start()
|
||||
w.Write([]byte("hello\n"))
|
||||
w.Write([]byte("world\n"))
|
||||
w.Stop()
|
||||
files, _ := ioutil.ReadDir("./")
|
||||
files, _ := os.ReadDir("./")
|
||||
for _, f := range files {
|
||||
fn := f.Name()
|
||||
if strings.HasPrefix(fn, "hello") {
|
||||
t.Log(fn)
|
||||
content, _ := ioutil.ReadFile(fn)
|
||||
content, _ := os.ReadFile(fn)
|
||||
t.Log(content)
|
||||
os.Remove(fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNextRotationHour(t *testing.T) {
|
||||
tcs := []struct {
|
||||
now time.Time
|
||||
delta int
|
||||
expectedHour int
|
||||
}{
|
||||
{
|
||||
now: time.Date(1980, 1, 6, 15, 34, 0, 0, time.UTC),
|
||||
delta: 3,
|
||||
expectedHour: 18,
|
||||
},
|
||||
{
|
||||
now: time.Date(1980, 1, 6, 23, 59, 0, 0, time.UTC),
|
||||
delta: 1,
|
||||
expectedHour: 0,
|
||||
},
|
||||
{
|
||||
now: time.Date(1980, 1, 6, 22, 15, 0, 0, time.UTC),
|
||||
delta: 2,
|
||||
expectedHour: 0,
|
||||
},
|
||||
{
|
||||
now: time.Date(1980, 1, 6, 0, 0, 0, 0, time.UTC),
|
||||
delta: 1,
|
||||
expectedHour: 1,
|
||||
},
|
||||
}
|
||||
|
||||
test := func(now time.Time, delta, expectedHour int) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
got := getNextRotationHour(now, delta)
|
||||
if got != expectedHour {
|
||||
t.Fatalf("Expected %d, found: %d\n", expectedHour, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, tc := range tcs {
|
||||
t.Run("TestGetNextRotationHour_"+strconv.Itoa(i), test(tc.now, tc.delta, tc.expectedHour))
|
||||
}
|
||||
}
|
||||
|
@ -74,14 +74,14 @@ func FileHandler(path string, fmtr Format) (Handler, error) {
|
||||
// RotatingFileHandler returns a handler which writes log records to file chunks
|
||||
// at the given path. When a file's size reaches the limit, the handler creates
|
||||
// a new file named after the timestamp of the first log record it will contain.
|
||||
func RotatingFileHandler(filePath string, limit uint, formatter Format) (Handler, error) {
|
||||
func RotatingFileHandler(filePath string, limit uint, formatter Format, rotateHours int) (Handler, error) {
|
||||
if _, err := os.Stat(path.Dir(filePath)); os.IsNotExist(err) {
|
||||
err := os.MkdirAll(path.Dir(filePath), 0755)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create directory %s, %v", path.Dir(filePath), err)
|
||||
}
|
||||
}
|
||||
fileWriter := NewAsyncFileWriter(filePath, int64(limit))
|
||||
fileWriter := NewAsyncFileWriter(filePath, int64(limit), rotateHours)
|
||||
fileWriter.Start()
|
||||
return StreamHandler(fileWriter, formatter), nil
|
||||
}
|
||||
|
@ -244,12 +244,8 @@ func (c Ctx) toArray() []interface{} {
|
||||
return arr
|
||||
}
|
||||
|
||||
func NewFileLvlHandler(logPath string, maxBytesSize uint, level string) Handler {
|
||||
rfh, err := RotatingFileHandler(
|
||||
logPath,
|
||||
maxBytesSize,
|
||||
LogfmtFormat(),
|
||||
)
|
||||
func NewFileLvlHandler(logPath string, maxBytesSize uint, level string, rotateHours int) Handler {
|
||||
rfh, err := RotatingFileHandler(logPath, maxBytesSize, LogfmtFormat(), rotateHours)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -520,6 +520,7 @@ type LogConfig struct {
|
||||
FilePath *string `toml:",omitempty"`
|
||||
MaxBytesSize *uint `toml:",omitempty"`
|
||||
Level *string `toml:",omitempty"`
|
||||
RotateHours int `toml:",omitempty"`
|
||||
|
||||
// TermTimeFormat is the time format used for console logging.
|
||||
TermTimeFormat *string `toml:",omitempty"`
|
||||
|
12
node/node.go
12
node/node.go
@ -103,7 +103,17 @@ func New(conf *Config) (*Node, error) {
|
||||
} else {
|
||||
logFilePath = path.Join(*conf.LogConfig.FileRoot, *conf.LogConfig.FilePath)
|
||||
}
|
||||
log.Root().SetHandler(log.NewFileLvlHandler(logFilePath, *conf.LogConfig.MaxBytesSize, *conf.LogConfig.Level))
|
||||
|
||||
if conf.LogConfig.RotateHours > 24 {
|
||||
return nil, errors.New("Config.LogConfig.RotateHours cannot be greater than 24")
|
||||
}
|
||||
|
||||
// To maintain backwards compatibility, if RotateHours is not set or set to a negative value, then it defaults to 1
|
||||
if conf.LogConfig.RotateHours < 1 {
|
||||
conf.LogConfig.RotateHours = 1
|
||||
}
|
||||
|
||||
log.Root().SetHandler(log.NewFileLvlHandler(logFilePath, *conf.LogConfig.MaxBytesSize, *conf.LogConfig.Level, conf.LogConfig.RotateHours))
|
||||
}
|
||||
}
|
||||
if conf.Logger == nil {
|
||||
|
Loading…
Reference in New Issue
Block a user