Skip to content

Commit

Permalink
Merge pull request #187 from yohamta/feat/scheduler-log-rotation
Browse files Browse the repository at this point in the history
feat: scheduler log rotation
  • Loading branch information
yottahmd authored Jul 4, 2022
2 parents 675c6ce + d63108a commit eaed682
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 7 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ It runs [DAGs (Directed acyclic graph)](https://en.wikipedia.org/wiki/Directed_a
- [Admin Configuration](#admin-configuration)
- [Environment Variables](#environment-variables-1)
- [Web UI Configuration](#web-ui-configuration)
- [Scheduler Log](#scheduler-log)
- [Global Configuration](#global-configuration)
- [REST API Interface](#rest-api-interface)
- [FAQ](#faq)
Expand Down Expand Up @@ -405,6 +406,14 @@ basicAuthUsername: <username for basic auth of web UI> # [optional] basic
basicAuthPassword: <password for basic auth of web UI> # [optional] basic auth config
```

### Scheduler Log

The default path is `~/.dagu/logs/`. If you want to change this, you can set `log` field in `~/.dagu/admin.yaml`.

```yaml
logs: <path-to-log-files-for-scheduler>
```

### Global Configuration

Creating a global configuration `~/.dagu/config.yaml` is a convenient way to organize shared settings.
Expand Down
101 changes: 101 additions & 0 deletions internal/logger/simple.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package logger

import (
"fmt"
"io"
"os"
"path"
"sync"
"time"

"github.com/yohamta/dagu/internal/utils"
)

type SimpleLogger struct {
Dir string
LogName string
Period time.Duration
mu sync.Mutex
file *os.File
stop chan struct{}
}

var _ io.Writer = (*SimpleLogger)(nil)

func NewSimpleLogger(dir, logName string, period time.Duration) *SimpleLogger {
return &SimpleLogger{
Dir: dir,
LogName: logName,
Period: period,
stop: make(chan struct{}),
}
}

func (rl *SimpleLogger) Open() error {
err := rl.setupFile()
if err != nil {
return err
}
go func() {
timer := time.NewTimer(time.Until(rl.timeToSwitchLog()))
for {
select {
case <-timer.C:
rl.mu.Lock()
err := rl.setupFile()
utils.LogErr("setup log file", err)
timer = time.NewTimer(time.Until(rl.timeToSwitchLog()))
rl.mu.Unlock()
case <-rl.stop:
timer.Stop()
return
}
}
}()
return nil
}

func (rl *SimpleLogger) Write(p []byte) (n int, err error) {
rl.mu.Lock()
defer rl.mu.Unlock()
if rl.file != nil {
return rl.file.Write(p)
}
return 0, nil
}

func (rl *SimpleLogger) Close() (err error) {
rl.mu.Lock()
defer rl.mu.Unlock()
err = rl.closeFile()
rl.stop <- struct{}{}
return nil
}

func (rl *SimpleLogger) setupFile() (err error) {
rl.closeFile()
filename := path.Join(
rl.Dir,
fmt.Sprintf("%s%s.log",
rl.LogName,
time.Now().Format("20060102.15:04:05.000"),
))
dir := path.Dir(filename)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
rl.file, err = utils.OpenOrCreateFile(filename)
return
}

func (rl *SimpleLogger) closeFile() (err error) {
if rl.file != nil {
_ = rl.file.Sync()
err = rl.file.Close()
}
return
}

func (rl *SimpleLogger) timeToSwitchLog() time.Time {
return time.Now().Add(rl.Period).Truncate(rl.Period)
}
63 changes: 63 additions & 0 deletions internal/logger/simple_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package logger

import (
"os"
"path"
"sort"
"testing"
"time"

"github.com/stretchr/testify/require"
"github.com/yohamta/dagu/internal/utils"
)

func TestSimpleLogger(t *testing.T) {
tmpDir := utils.MustTempDir("test-simple-logger")
defer func() {
_ = os.RemoveAll(tmpDir)
}()

rl := NewSimpleLogger(tmpDir, "test", time.Millisecond*300)
rl.Open()

_, err := rl.Write([]byte("test log\n"))
require.NoError(t, err)

time.Sleep(time.Millisecond * 300)

_, err = rl.Write([]byte("test log2\n"))
require.NoError(t, err)

_ = rl.Close()
time.Sleep(time.Millisecond * 100)

f, err := os.Open(tmpDir)
require.NoError(t, err)
defer func() {
_ = f.Close()
}()

fis, _ := f.Readdir(0)
require.Equal(t, 2, len(fis))
for _, fi := range fis {
require.Regexp(t, "test\\d{8}.\\d{2}:\\d{2}:\\d{2}.\\d{3}.log", fi.Name())
}

sort.Slice(fis, func(i, j int) bool {
return fis[i].Name() < fis[j].Name()
})

b, _ := os.ReadFile(path.Join(tmpDir, fis[0].Name()))
require.Equal(t, "test log\n", string(b))

b, _ = os.ReadFile(path.Join(tmpDir, fis[1].Name()))
require.Equal(t, "test log2\n", string(b))
}

func TestTimeToSwitchLog(t *testing.T) {
rl := NewSimpleLogger("", "test", time.Hour*24)
tm := rl.timeToSwitchLog()
d := time.Hour*24 - time.Until(tm)
require.GreaterOrEqual(t, d, time.Duration(0))
require.LessOrEqual(t, time.Second, d)
}
16 changes: 9 additions & 7 deletions internal/runner/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import (

type Agent struct {
*admin.Config
stop chan struct{}
logFile *os.File
stop chan struct{}
logger *logger.SimpleLogger
}

func NewAgent(cfg *admin.Config) *Agent {
Expand All @@ -44,8 +44,7 @@ func (a *Agent) Stop() {
}

func (a *Agent) start() error {
// TODO: log rotation
tl := &logger.TeeLogger{Writer: a.logFile}
tl := &logger.TeeLogger{Writer: a.logger}
if err := tl.Open(); err != nil {
return err
}
Expand Down Expand Up @@ -90,13 +89,16 @@ func (a *Agent) setupLogFile() (err error) {
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
a.logFile, err = utils.OpenOrCreateFile(filename)
a.logger = logger.NewSimpleLogger(
a.LogDir, "scheduler", time.Hour*24,
)
err = a.logger.Open()
return
}

func (a *Agent) closeLogFile() error {
if a.logFile != nil {
return a.logFile.Close()
if a.logger != nil {
return a.logger.Close()
}
return nil
}

0 comments on commit eaed682

Please sign in to comment.