mirror of
https://gitlab.com/pulsechaincom/go-pulse.git
synced 2024-12-25 21:07:17 +00:00
a9835c1816
* 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
470 lines
13 KiB
Go
470 lines
13 KiB
Go
package log
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"reflect"
|
|
"sync"
|
|
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/go-stack/stack"
|
|
)
|
|
|
|
// Handler defines where and how log records are written.
|
|
// A Logger prints its log records by writing to a Handler.
|
|
// Handlers are composable, providing you great flexibility in combining
|
|
// them to achieve the logging structure that suits your applications.
|
|
type Handler interface {
|
|
Log(r *Record) error
|
|
}
|
|
|
|
// FuncHandler returns a Handler that logs records with the given
|
|
// function.
|
|
func FuncHandler(fn func(r *Record) error) Handler {
|
|
return funcHandler(fn)
|
|
}
|
|
|
|
type funcHandler func(r *Record) error
|
|
|
|
func (h funcHandler) Log(r *Record) error {
|
|
return h(r)
|
|
}
|
|
|
|
// StreamHandler writes log records to an io.Writer
|
|
// with the given format. StreamHandler can be used
|
|
// to easily begin writing log records to other
|
|
// outputs.
|
|
//
|
|
// StreamHandler wraps itself with LazyHandler and SyncHandler
|
|
// to evaluate Lazy objects and perform safe concurrent writes.
|
|
func StreamHandler(wr io.Writer, fmtr Format) Handler {
|
|
h := FuncHandler(func(r *Record) error {
|
|
_, err := wr.Write(fmtr.Format(r))
|
|
return err
|
|
})
|
|
return LazyHandler(SyncHandler(h))
|
|
}
|
|
|
|
// SyncHandler can be wrapped around a handler to guarantee that
|
|
// only a single Log operation can proceed at a time. It's necessary
|
|
// for thread-safe concurrent writes.
|
|
func SyncHandler(h Handler) Handler {
|
|
var mu sync.Mutex
|
|
return FuncHandler(func(r *Record) error {
|
|
defer mu.Unlock()
|
|
mu.Lock()
|
|
return h.Log(r)
|
|
})
|
|
}
|
|
|
|
// FileHandler returns a handler which writes log records to the give file
|
|
// using the given format. If the path
|
|
// already exists, FileHandler will append to the given file. If it does not,
|
|
// FileHandler will create the file with mode 0644.
|
|
func FileHandler(path string, fmtr Format) (Handler, error) {
|
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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) {
|
|
conn, err := net.Dial(network, addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return closingHandler{conn, StreamHandler(conn, fmtr)}, nil
|
|
}
|
|
|
|
// XXX: closingHandler is essentially unused at the moment
|
|
// it's meant for a future time when the Handler interface supports
|
|
// a possible Close() operation
|
|
type closingHandler struct {
|
|
io.WriteCloser
|
|
Handler
|
|
}
|
|
|
|
func (h *closingHandler) Close() error {
|
|
return h.WriteCloser.Close()
|
|
}
|
|
|
|
// CallerFileHandler returns a Handler that adds the line number and file of
|
|
// the calling function to the context with key "caller".
|
|
func CallerFileHandler(h Handler) Handler {
|
|
return FuncHandler(func(r *Record) error {
|
|
r.Ctx = append(r.Ctx, "caller", fmt.Sprint(r.Call))
|
|
return h.Log(r)
|
|
})
|
|
}
|
|
|
|
// CallerFuncHandler returns a Handler that adds the calling function name to
|
|
// the context with key "fn".
|
|
func CallerFuncHandler(h Handler) Handler {
|
|
return FuncHandler(func(r *Record) error {
|
|
r.Ctx = append(r.Ctx, "fn", formatCall("%+n", r.Call))
|
|
return h.Log(r)
|
|
})
|
|
}
|
|
|
|
// This function is here to please go vet on Go < 1.8.
|
|
func formatCall(format string, c stack.Call) string {
|
|
return fmt.Sprintf(format, c)
|
|
}
|
|
|
|
// CallerStackHandler returns a Handler that adds a stack trace to the context
|
|
// with key "stack". The stack trace is formated as a space separated list of
|
|
// call sites inside matching []'s. The most recent call site is listed first.
|
|
// Each call site is formatted according to format. See the documentation of
|
|
// package github.com/go-stack/stack for the list of supported formats.
|
|
func CallerStackHandler(format string, h Handler) Handler {
|
|
return FuncHandler(func(r *Record) error {
|
|
s := stack.Trace().TrimBelow(r.Call).TrimRuntime()
|
|
if len(s) > 0 {
|
|
r.Ctx = append(r.Ctx, "stack", fmt.Sprintf(format, s))
|
|
}
|
|
return h.Log(r)
|
|
})
|
|
}
|
|
|
|
// FilterHandler returns a Handler that only writes records to the
|
|
// wrapped Handler if the given function evaluates true. For example,
|
|
// to only log records where the 'err' key is not nil:
|
|
//
|
|
// logger.SetHandler(FilterHandler(func(r *Record) bool {
|
|
// for i := 0; i < len(r.Ctx); i += 2 {
|
|
// if r.Ctx[i] == "err" {
|
|
// return r.Ctx[i+1] != nil
|
|
// }
|
|
// }
|
|
// return false
|
|
// }, h))
|
|
//
|
|
func FilterHandler(fn func(r *Record) bool, h Handler) Handler {
|
|
return FuncHandler(func(r *Record) error {
|
|
if fn(r) {
|
|
return h.Log(r)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// MatchFilterHandler returns a Handler that only writes records
|
|
// to the wrapped Handler if the given key in the logged
|
|
// context matches the value. For example, to only log records
|
|
// from your ui package:
|
|
//
|
|
// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler)
|
|
//
|
|
func MatchFilterHandler(key string, value interface{}, h Handler) Handler {
|
|
return FilterHandler(func(r *Record) (pass bool) {
|
|
switch key {
|
|
case r.KeyNames.Lvl:
|
|
return r.Lvl == value
|
|
case r.KeyNames.Time:
|
|
return r.Time == value
|
|
case r.KeyNames.Msg:
|
|
return r.Msg == value
|
|
}
|
|
|
|
for i := 0; i < len(r.Ctx); i += 2 {
|
|
if r.Ctx[i] == key {
|
|
return r.Ctx[i+1] == value
|
|
}
|
|
}
|
|
return false
|
|
}, h)
|
|
}
|
|
|
|
// LvlFilterHandler returns a Handler that only writes
|
|
// records which are less than the given verbosity
|
|
// level to the wrapped Handler. For example, to only
|
|
// log Error/Crit records:
|
|
//
|
|
// log.LvlFilterHandler(log.LvlError, log.StdoutHandler)
|
|
//
|
|
func LvlFilterHandler(maxLvl Lvl, h Handler) Handler {
|
|
return FilterHandler(func(r *Record) (pass bool) {
|
|
return r.Lvl <= maxLvl
|
|
}, h)
|
|
}
|
|
|
|
// MultiHandler dispatches any write to each of its handlers.
|
|
// This is useful for writing different types of log information
|
|
// to different locations. For example, to log to a file and
|
|
// standard error:
|
|
//
|
|
// log.MultiHandler(
|
|
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
|
|
// log.StderrHandler)
|
|
//
|
|
func MultiHandler(hs ...Handler) Handler {
|
|
return FuncHandler(func(r *Record) error {
|
|
for _, h := range hs {
|
|
// what to do about failures?
|
|
h.Log(r)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// FailoverHandler writes all log records to the first handler
|
|
// specified, but will failover and write to the second handler if
|
|
// the first handler has failed, and so on for all handlers specified.
|
|
// For example you might want to log to a network socket, but failover
|
|
// to writing to a file if the network fails, and then to
|
|
// standard out if the file write fails:
|
|
//
|
|
// log.FailoverHandler(
|
|
// log.Must.NetHandler("tcp", ":9090", log.JSONFormat()),
|
|
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
|
|
// log.StdoutHandler)
|
|
//
|
|
// All writes that do not go to the first handler will add context with keys of
|
|
// the form "failover_err_{idx}" which explain the error encountered while
|
|
// trying to write to the handlers before them in the list.
|
|
func FailoverHandler(hs ...Handler) Handler {
|
|
return FuncHandler(func(r *Record) error {
|
|
var err error
|
|
for i, h := range hs {
|
|
err = h.Log(r)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err)
|
|
}
|
|
|
|
return err
|
|
})
|
|
}
|
|
|
|
// ChannelHandler writes all records to the given channel.
|
|
// It blocks if the channel is full. Useful for async processing
|
|
// of log messages, it's used by BufferedHandler.
|
|
func ChannelHandler(recs chan<- *Record) Handler {
|
|
return FuncHandler(func(r *Record) error {
|
|
recs <- r
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// BufferedHandler writes all records to a buffered
|
|
// channel of the given size which flushes into the wrapped
|
|
// handler whenever it is available for writing. Since these
|
|
// writes happen asynchronously, all writes to a BufferedHandler
|
|
// never return an error and any errors from the wrapped handler are ignored.
|
|
func BufferedHandler(bufSize int, h Handler) Handler {
|
|
recs := make(chan *Record, bufSize)
|
|
go func() {
|
|
for m := range recs {
|
|
_ = h.Log(m)
|
|
}
|
|
}()
|
|
return ChannelHandler(recs)
|
|
}
|
|
|
|
// LazyHandler writes all values to the wrapped handler after evaluating
|
|
// any lazy functions in the record's context. It is already wrapped
|
|
// around StreamHandler and SyslogHandler in this library, you'll only need
|
|
// it if you write your own Handler.
|
|
func LazyHandler(h Handler) Handler {
|
|
return FuncHandler(func(r *Record) error {
|
|
// go through the values (odd indices) and reassign
|
|
// the values of any lazy fn to the result of its execution
|
|
hadErr := false
|
|
for i := 1; i < len(r.Ctx); i += 2 {
|
|
lz, ok := r.Ctx[i].(Lazy)
|
|
if ok {
|
|
v, err := evaluateLazy(lz)
|
|
if err != nil {
|
|
hadErr = true
|
|
r.Ctx[i] = err
|
|
} else {
|
|
if cs, ok := v.(stack.CallStack); ok {
|
|
v = cs.TrimBelow(r.Call).TrimRuntime()
|
|
}
|
|
r.Ctx[i] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
if hadErr {
|
|
r.Ctx = append(r.Ctx, errorKey, "bad lazy")
|
|
}
|
|
|
|
return h.Log(r)
|
|
})
|
|
}
|
|
|
|
func evaluateLazy(lz Lazy) (interface{}, error) {
|
|
t := reflect.TypeOf(lz.Fn)
|
|
|
|
if t.Kind() != reflect.Func {
|
|
return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn)
|
|
}
|
|
|
|
if t.NumIn() > 0 {
|
|
return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn)
|
|
}
|
|
|
|
if t.NumOut() == 0 {
|
|
return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn)
|
|
}
|
|
|
|
value := reflect.ValueOf(lz.Fn)
|
|
results := value.Call([]reflect.Value{})
|
|
if len(results) == 1 {
|
|
return results[0].Interface(), nil
|
|
}
|
|
values := make([]interface{}, len(results))
|
|
for i, v := range results {
|
|
values[i] = v.Interface()
|
|
}
|
|
return values, nil
|
|
}
|
|
|
|
// DiscardHandler reports success for all writes but does nothing.
|
|
// It is useful for dynamically disabling logging at runtime via
|
|
// a Logger's SetHandler method.
|
|
func DiscardHandler() Handler {
|
|
return FuncHandler(func(r *Record) error {
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Must provides the following Handler creation functions
|
|
// which instead of returning an error parameter only return a Handler
|
|
// and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler
|
|
var Must muster
|
|
|
|
func must(h Handler, err error) Handler {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return h
|
|
}
|
|
|
|
type muster struct{}
|
|
|
|
func (m muster) FileHandler(path string, fmtr Format) Handler {
|
|
return must(FileHandler(path, fmtr))
|
|
}
|
|
|
|
func (m muster) NetHandler(network, addr string, fmtr Format) Handler {
|
|
return must(NetHandler(network, addr, fmtr))
|
|
}
|