git » log » commit 2b2e1b6

Implement a Reopen() method (experimental)

author Alberto Bertogli
2017-09-10 11:25:55 UTC
committer Alberto Bertogli
2017-09-10 11:25:55 UTC
parent 0a5133de1669cbfc04af450dfb72a998dc80f22d

Implement a Reopen() method (experimental)

This patch implements an experimental Reopen() method, which can be used
for log rotation.

It only works for file-backed logs, it is a no-op on everything else.

log.go +28 -0
log_test.go +35 -0

diff --git a/log.go b/log.go
index 6b049d0..3ada5b2 100644
--- a/log.go
+++ b/log.go
@@ -81,6 +81,10 @@ type Logger struct {
 	// could change in the future.
 	Level Level
 
+	// File name, if this logger is backed by a file. It's used to implement
+	// reopening.
+	fname string
+
 	logTime    bool
 	callerSkip int
 	w          io.WriteCloser
@@ -106,6 +110,7 @@ func NewFile(path string) (*Logger, error) {
 
 	l := New(f)
 	l.logTime = true
+	l.fname = path
 	return l, nil
 }
 
@@ -127,6 +132,29 @@ func (l *Logger) Close() {
 	l.w.Close()
 }
 
+// Reopen the file behind the logger, if any. This can be used to implement
+// log rotation.
+//
+// Only works for loggers created via NewFile, otherwise it is a no-op.
+//
+// EXPERIMENTAL, this API could change in the future.
+func (l *Logger) Reopen() error {
+	if l.fname == "" {
+		return nil
+	}
+
+	f, err := os.OpenFile(l.fname, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
+	if err != nil {
+		return err
+	}
+
+	l.Lock()
+	l.Close()
+	l.w = f
+	l.Unlock()
+	return nil
+}
+
 // V returns true if the logger's level is >= the one given, false otherwise.
 // It can be used to decide whether to use or gather debugging information
 // only at a certain level, to avoid computing it needlessly.
diff --git a/log_test.go b/log_test.go
index 73a671b..6443fe9 100644
--- a/log_test.go
+++ b/log_test.go
@@ -1,6 +1,7 @@
 package log
 
 import (
+	"io"
 	"io/ioutil"
 	"os"
 	"regexp"
@@ -107,3 +108,37 @@ func TestDefaultFile(t *testing.T) {
 
 	testLogger(t, fname, Default)
 }
+
+func TestReopen(t *testing.T) {
+	fname, l := mustNewFile(t)
+	defer l.Close()
+	defer os.Remove(fname)
+	l.logTime = false
+
+	l.Infof("pre rename")
+	checkContentsMatch(t, "r", fname, `^_ log_test.go:....   pre rename\n`)
+
+	os.Rename(fname, fname+"-m")
+	defer os.Remove(fname + "-m")
+	l.Infof("post rename")
+	checkContentsMatch(t, "r", fname+"-m", `pre rename\n.* post rename`)
+
+	if err := l.Reopen(); err != nil {
+		t.Errorf("reopen: %v", err)
+	}
+	l.Infof("post reopen")
+	checkContentsMatch(t, "r", fname, `^_ log_test.go:....   post reopen\n`)
+}
+
+type nopCloser struct {
+	io.Writer
+}
+
+func (nopCloser) Close() error { return nil }
+
+func TestReopenNull(t *testing.T) {
+	l := New(nopCloser{ioutil.Discard})
+	if err := l.Reopen(); err != nil {
+		t.Errorf("reopen: %v", err)
+	}
+}