author | Alberto Bertogli
<albertito@blitiri.com.ar> 2017-09-10 11:25:55 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2017-09-10 11:25:55 UTC |
parent | 0a5133de1669cbfc04af450dfb72a998dc80f22d |
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) + } +}