aboutsummaryrefslogtreecommitdiffstats
path: root/log/handler.go
diff options
context:
space:
mode:
Diffstat (limited to 'log/handler.go')
-rw-r--r--log/handler.go110
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) {