diff options
author | Sean E. Russell <ser@ser1.net> | 2020-02-15 14:27:31 -0600 |
---|---|---|
committer | Sean E. Russell <ser@ser1.net> | 2020-02-15 14:27:31 -0600 |
commit | a007047fc551abdcebe85a6240f508ed120867bc (patch) | |
tree | 298755b1339d73273f5ec52621129c9bd2f34e42 | |
parent | 774b4fcda6a7c0bd956b0396fe0c4f77bce3a76d (diff) |
Fixes #49, logs filling up hard drive.
-rw-r--r-- | CHANGELOG.md | 7 | ||||
-rw-r--r-- | cmd/gotop/main.go | 26 | ||||
-rw-r--r-- | config.go | 3 | ||||
-rw-r--r-- | logging/logging_arm64.go | 2 | ||||
-rw-r--r-- | logging/logging_other.go | 97 | ||||
-rw-r--r-- | logging/logging_test.go | 55 |
6 files changed, 164 insertions, 26 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 78c81bd..14b710f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 > - **Fixed**: for any bug fixes. > - **Security**: in case of vulnerabilities. +## [3.3.0] - + +- Logs are now rotated. Settings are currently hard-coded at 4 files of 5MB + each, so logs shouldn't take up more than 20MB. I'm going to see how many + complain about wanting to configure these settings before I add code to do + that. + ## [3.2.0] - 2020-02-14 Bug fixes & pull requests diff --git a/cmd/gotop/main.go b/cmd/gotop/main.go index d3384b0..eb2364f 100644 --- a/cmd/gotop/main.go +++ b/cmd/gotop/main.go @@ -79,7 +79,7 @@ Colorschemes: conf = gotop.Config{ ConfigDir: cd, LogDir: ld, - LogPath: filepath.Join(ld, "errors.log"), + LogFile: "errors.log", GraphHorizontalScale: 7, HelpVisible: false, Colorscheme: colorschemes.Default, @@ -90,6 +90,7 @@ Colorschemes: Battery: false, Statusbar: false, NetInterface: w.NET_INTERFACE_ALL, + MaxLogSize: 5000000, } args, err := docopt.ParseArgs(usage, os.Args[1:], version) @@ -376,32 +377,13 @@ func eventLoop(c gotop.Config, grid *layout.MyGrid) { } } -func setupLogfile(c gotop.Config) (*os.File, error) { - // create the log directory - if err := os.MkdirAll(c.LogDir, 0755); err != nil { - return nil, fmt.Errorf("failed to make the log directory: %v", err) - } - // open the log file - logfile, err := os.OpenFile(c.LogPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0660) - if err != nil { - return nil, fmt.Errorf("failed to open log file: %v", err) - } - - // log time, filename, and line number - log.SetFlags(log.Ltime | log.Lshortfile) - // log to file - log.SetOutput(logfile) - - return logfile, nil -} - func main() { conf, err := parseArgs() if err != nil { stderrLogger.Fatalf("failed to parse cli args: %v", err) } - logfile, err := setupLogfile(conf) + logfile, err := logging.New(conf) if err != nil { stderrLogger.Fatalf("failed to setup log file: %v", err) } @@ -412,8 +394,6 @@ func main() { } defer ui.Close() - logging.StderrToLogfile(logfile) - setDefaultTermuiColors(conf) // done before initializing widgets to allow inheriting colors help = w.NewHelpMenu() if statusbar { @@ -18,7 +18,7 @@ import ( type Config struct { ConfigDir string LogDir string - LogPath string + LogFile string GraphHorizontalScale int HelpVisible bool @@ -32,4 +32,5 @@ type Config struct { Statusbar bool NetInterface string Layout io.Reader + MaxLogSize int64 } diff --git a/logging/logging_arm64.go b/logging/logging_arm64.go index 2b7a25f..48ace31 100644 --- a/logging/logging_arm64.go +++ b/logging/logging_arm64.go @@ -5,6 +5,6 @@ import ( "syscall" ) -func StderrToLogfile(logfile *os.File) { +func stderrToLogfile(logfile *os.File) { syscall.Dup3(int(logfile.Fd()), 2, 0) } diff --git a/logging/logging_other.go b/logging/logging_other.go index 382baca..b102e29 100644 --- a/logging/logging_other.go +++ b/logging/logging_other.go @@ -3,10 +3,105 @@ package logging import ( + "fmt" + "io" + "log" "os" + "path/filepath" + "sync" "syscall" + + "github.com/xxxserxxx/gotop" ) -func StderrToLogfile(logfile *os.File) { +func stderrToLogfile(logfile *os.File) { syscall.Dup2(int(logfile.Fd()), 2) } + +func New(c gotop.Config) (io.WriteCloser, error) { + // create the log directory + if err := os.MkdirAll(c.LogDir, 0755); err != nil { + return nil, fmt.Errorf("failed to make the log directory: %v", err) + } + w := &RotateWriter{ + filename: filepath.Join(c.LogDir, c.LogFile), + maxLogSize: c.MaxLogSize, + } + err := w.rotate() + if err != nil { + return nil, err + } + // log time, filename, and line number + log.SetFlags(log.Ltime | log.Lshortfile) + // log to file + log.SetOutput(w) + + stderrToLogfile(w.fp) + return w, nil +} + +type RotateWriter struct { + lock sync.Mutex + filename string // should be set to the actual filename + fp *os.File + maxLogSize int64 +} + +func (w *RotateWriter) Close() error { + return w.fp.Close() +} + +// Write satisfies the io.Writer interface. +func (w *RotateWriter) Write(output []byte) (int, error) { + w.lock.Lock() + defer w.lock.Unlock() + // Rotate if the log hits the size limit + s, err := os.Stat(w.filename) + if err == nil { + if s.Size() > w.maxLogSize { + w.rotate() + } + } + return w.fp.Write(output) +} + +// Perform the actual act of rotating and reopening file. +func (w *RotateWriter) rotate() (err error) { + // Close existing file if open + if w.fp != nil { + err = w.fp.Close() + w.fp = nil + if err != nil { + return + } + } + // This will keep three logs + for i := 1; i > -1; i-- { + from := fmt.Sprintf("%s.%d", w.filename, i) + to := fmt.Sprintf("%s.%d", w.filename, i+1) + // Rename dest file if it already exists + _, err = os.Stat(from) + if err == nil { + err = os.Rename(from, to) + if err != nil { + return + } + } + } + // Rename dest file if it already exists + _, err = os.Stat(w.filename) + if err == nil { + err = os.Rename(w.filename, fmt.Sprintf("%s.%d", w.filename, 0)) + if err != nil { + return + } + } + + // open the log file + w.fp, err = os.OpenFile(w.filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0660) + if err != nil { + return fmt.Errorf("failed to open log file %s: %v", w.filename, err) + } + + return nil +} diff --git a/logging/logging_test.go b/logging/logging_test.go new file mode 100644 index 0000000..3bff162 --- /dev/null +++ b/logging/logging_test.go @@ -0,0 +1,55 @@ +package logging + +import ( + "io/ioutil" + //"log" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/xxxserxxx/gotop" +) + +func TestLogging(t *testing.T) { + tdn := "testdir" + path, err := filepath.Abs(tdn) + defer os.RemoveAll(path) + c := gotop.Config{ + MaxLogSize: 300, + LogDir: path, + LogFile: "errors.log", + } + wc, err := New(c) + assert.NoError(t, err) + if err != nil { + return + } + defer wc.Close() + ds := make([]byte, 100) + for i, _ := range ds { + ds[i] = 'x' + } + + // Base case -- empty log file + td, err := ioutil.ReadDir(path) + assert.NoError(t, err) + if err != nil { + return + } + assert.Equal(t, 1, len(td)) + + for i := 1; i < 6; i++ { + wc.Write(ds) + wc.Write(ds) + wc.Write(ds) + wc.Write([]byte{'\n'}) // max... + 1 + td, err = ioutil.ReadDir(path) + assert.NoError(t, err) + k := i + if k > 4 { + k = 4 + } + assert.Equal(t, k, len(td)) + } +} |