diff options
Diffstat (limited to 'log/handler.go')
-rw-r--r-- | log/handler.go | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/log/handler.go b/log/handler.go index 3c99114dc..2f01b5dc6 100644 --- a/log/handler.go +++ b/log/handler.go @@ -8,6 +8,11 @@ import ( "reflect" "sync" + "io/ioutil" + "path/filepath" + "regexp" + "strings" + "github.com/go-stack/stack" ) @@ -70,6 +75,111 @@ func FileHandler(path string, fmtr Format) (Handler, error) { return closingHandler{f, StreamHandler(f, fmtr)}, nil } +// countingWriter wraps a WriteCloser object in order to count the written bytes. +type countingWriter struct { + w io.WriteCloser // the wrapped object + count uint // number of bytes written +} + +// Write increments the byte counter by the number of bytes written. +// Implements the WriteCloser interface. +func (w *countingWriter) Write(p []byte) (n int, err error) { + n, err = w.w.Write(p) + w.count += uint(n) + return n, err +} + +// Close implements the WriteCloser interface. +func (w *countingWriter) Close() error { + return w.w.Close() +} + +// prepFile opens the log file at the given path, and cuts off the invalid part +// from the end, because the previous execution could have been finished by interruption. +// Assumes that every line ended by '\n' contains a valid log record. +func prepFile(path string) (*countingWriter, error) { + f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0600) + if err != nil { + return nil, err + } + _, err = f.Seek(-1, io.SeekEnd) + if err != nil { + return nil, err + } + buf := make([]byte, 1) + var cut int64 + for { + if _, err := f.Read(buf); err != nil { + return nil, err + } + if buf[0] == '\n' { + break + } + if _, err = f.Seek(-2, io.SeekCurrent); err != nil { + return nil, err + } + cut++ + } + fi, err := f.Stat() + if err != nil { + return nil, err + } + ns := fi.Size() - cut + if err = f.Truncate(ns); err != nil { + return nil, err + } + return &countingWriter{w: f, count: uint(ns)}, nil +} + +// 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(path string, limit uint, formatter Format) (Handler, error) { + if err := os.MkdirAll(path, 0700); err != nil { + return nil, err + } + files, err := ioutil.ReadDir(path) + if err != nil { + return nil, err + } + re := regexp.MustCompile(`\.log$`) + last := len(files) - 1 + for last >= 0 && (!files[last].Mode().IsRegular() || !re.MatchString(files[last].Name())) { + last-- + } + var counter *countingWriter + if last >= 0 && files[last].Size() < int64(limit) { + // Open the last file, and continue to write into it until it's size reaches the limit. + if counter, err = prepFile(filepath.Join(path, files[last].Name())); err != nil { + return nil, err + } + } + if counter == nil { + counter = new(countingWriter) + } + h := StreamHandler(counter, formatter) + + return FuncHandler(func(r *Record) error { + if counter.count > limit { + counter.Close() + counter.w = nil + } + if counter.w == nil { + f, err := os.OpenFile( + filepath.Join(path, fmt.Sprintf("%s.log", strings.Replace(r.Time.Format("060102150405.00"), ".", "", 1))), + os.O_CREATE|os.O_APPEND|os.O_WRONLY, + 0600, + ) + if err != nil { + return err + } + counter.w = f + counter.count = 0 + } + return h.Log(r) + }), nil +} + // NetHandler opens a socket to the given address and writes records // over the connection. func NetHandler(network, addr string, fmtr Format) (Handler, error) { |