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
This commit is contained in:
Kurkó Mihály
2018-07-11 10:59:04 +03:00
committed by Péter Szilágyi
parent 2eedbe799f
commit a9835c1816
28 changed files with 11444 additions and 8211 deletions

View File

@ -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.

View File

@ -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) {

View File

@ -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 {

View File

@ -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,
},
})
}