aboutsummaryrefslogtreecommitdiffstats
path: root/log
diff options
context:
space:
mode:
authorKurkó Mihály <kurkomisi@users.noreply.github.com>2018-07-11 15:59:04 +0800
committerPéter Szilágyi <peterke@gmail.com>2018-07-11 15:59:04 +0800
commita9835c1816bc49ee54c82b4f2a5b05cbcd89881b (patch)
treee1badefd627aa3a7c4e1937eab22b8fe3eb204d1 /log
parent2eedbe799f5eb8766e4808d8a1810cc1c90c4b93 (diff)
downloaddexon-a9835c1816bc49ee54c82b4f2a5b05cbcd89881b.tar
dexon-a9835c1816bc49ee54c82b4f2a5b05cbcd89881b.tar.gz
dexon-a9835c1816bc49ee54c82b4f2a5b05cbcd89881b.tar.bz2
dexon-a9835c1816bc49ee54c82b4f2a5b05cbcd89881b.tar.lz
dexon-a9835c1816bc49ee54c82b4f2a5b05cbcd89881b.tar.xz
dexon-a9835c1816bc49ee54c82b4f2a5b05cbcd89881b.tar.zst
dexon-a9835c1816bc49ee54c82b4f2a5b05cbcd89881b.zip
cmd, dashboard, log: log collection and exploration (#17097)
* cmd, dashboard, internal, log, node: logging feature * cmd, dashboard, internal, log: requested changes * dashboard, vendor: gofmt, govendor, use vendored file watcher * dashboard, log: gofmt -s -w, goimports * dashboard, log: gosimple
Diffstat (limited to 'log')
-rw-r--r--log/format.go46
-rw-r--r--log/handler.go110
-rw-r--r--log/handler_glog.go5
-rw-r--r--log/logger.go3
4 files changed, 162 insertions, 2 deletions
diff --git a/log/format.go b/log/format.go
index 0d4732cc0..7902b296e 100644
--- a/log/format.go
+++ b/log/format.go
@@ -77,11 +77,11 @@ type TerminalStringer interface {
// a terminal with color-coded level output and terser human friendly timestamp.
// This format should only be used for interactive programs or while developing.
//
-// [TIME] [LEVEL] MESAGE key=value key=value ...
+// [LEVEL] [TIME] MESAGE key=value key=value ...
//
// Example:
//
-// [May 16 20:58:45] [DBUG] remove route ns=haproxy addr=127.0.0.1:50002
+// [DBUG] [May 16 20:58:45] remove route ns=haproxy addr=127.0.0.1:50002
//
func TerminalFormat(usecolor bool) Format {
return FormatFunc(func(r *Record) []byte {
@@ -202,6 +202,48 @@ func JSONFormat() Format {
return JSONFormatEx(false, true)
}
+// JSONFormatOrderedEx formats log records as JSON arrays. If pretty is true,
+// records will be pretty-printed. If lineSeparated is true, records
+// will be logged with a new line between each record.
+func JSONFormatOrderedEx(pretty, lineSeparated bool) Format {
+ jsonMarshal := json.Marshal
+ if pretty {
+ jsonMarshal = func(v interface{}) ([]byte, error) {
+ return json.MarshalIndent(v, "", " ")
+ }
+ }
+ return FormatFunc(func(r *Record) []byte {
+ props := make(map[string]interface{})
+
+ props[r.KeyNames.Time] = r.Time
+ props[r.KeyNames.Lvl] = r.Lvl.String()
+ props[r.KeyNames.Msg] = r.Msg
+
+ ctx := make([]string, len(r.Ctx))
+ for i := 0; i < len(r.Ctx); i += 2 {
+ k, ok := r.Ctx[i].(string)
+ if !ok {
+ props[errorKey] = fmt.Sprintf("%+v is not a string key,", r.Ctx[i])
+ }
+ ctx[i] = k
+ ctx[i+1] = formatLogfmtValue(r.Ctx[i+1], true)
+ }
+ props[r.KeyNames.Ctx] = ctx
+
+ b, err := jsonMarshal(props)
+ if err != nil {
+ b, _ = jsonMarshal(map[string]string{
+ errorKey: err.Error(),
+ })
+ return b
+ }
+ if lineSeparated {
+ b = append(b, '\n')
+ }
+ return b
+ })
+}
+
// JSONFormatEx formats log records as JSON objects. If pretty is true,
// records will be pretty-printed. If lineSeparated is true, records
// will be logged with a new line between each record.
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) {
diff --git a/log/handler_glog.go b/log/handler_glog.go
index f8b932fd1..83dae44bd 100644
--- a/log/handler_glog.go
+++ b/log/handler_glog.go
@@ -57,6 +57,11 @@ func NewGlogHandler(h Handler) *GlogHandler {
}
}
+// SetHandler updates the handler to write records to the specified sub-handler.
+func (h *GlogHandler) SetHandler(nh Handler) {
+ h.origin = nh
+}
+
// pattern contains a filter for the Vmodule option, holding a verbosity level
// and a file pattern to match.
type pattern struct {
diff --git a/log/logger.go b/log/logger.go
index 438aa548f..ca3e0b059 100644
--- a/log/logger.go
+++ b/log/logger.go
@@ -11,6 +11,7 @@ import (
const timeKey = "t"
const lvlKey = "lvl"
const msgKey = "msg"
+const ctxKey = "ctx"
const errorKey = "LOG15_ERROR"
const skipLevel = 2
@@ -101,6 +102,7 @@ type RecordKeyNames struct {
Time string
Msg string
Lvl string
+ Ctx string
}
// A Logger writes key/value pairs to a Handler
@@ -139,6 +141,7 @@ func (l *logger) write(msg string, lvl Lvl, ctx []interface{}, skip int) {
Time: timeKey,
Msg: msgKey,
Lvl: lvlKey,
+ Ctx: ctxKey,
},
})
}