mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 10:50:44 +00:00
log: logging feature (#17097)
This commit is contained in:
parent
38e5efbfe4
commit
49e889eb7c
4 changed files with 184 additions and 29 deletions
|
|
@ -77,11 +77,11 @@ type TerminalStringer interface {
|
||||||
// a terminal with color-coded level output and terser human friendly timestamp.
|
// a terminal with color-coded level output and terser human friendly timestamp.
|
||||||
// This format should only be used for interactive programs or while developing.
|
// 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:
|
// 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 {
|
func TerminalFormat(usecolor bool) Format {
|
||||||
return FormatFunc(func(r *Record) []byte {
|
return FormatFunc(func(r *Record) []byte {
|
||||||
|
|
@ -202,6 +202,48 @@ func JSONFormat() Format {
|
||||||
return JSONFormatEx(false, true)
|
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,
|
// JSONFormatEx formats log records as JSON objects. If pretty is true,
|
||||||
// records will be pretty-printed. If lineSeparated is true, records
|
// records will be pretty-printed. If lineSeparated is true, records
|
||||||
// will be logged with a new line between each record.
|
// will be logged with a new line between each record.
|
||||||
|
|
|
||||||
147
log/handler.go
147
log/handler.go
|
|
@ -3,9 +3,13 @@ package log
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-stack/stack"
|
"github.com/go-stack/stack"
|
||||||
|
|
@ -70,6 +74,111 @@ func FileHandler(path string, fmtr Format) (Handler, error) {
|
||||||
return closingHandler{f, StreamHandler(f, fmtr)}, nil
|
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
|
// NetHandler opens a socket to the given address and writes records
|
||||||
// over the connection.
|
// over the connection.
|
||||||
func NetHandler(network, addr string, fmtr Format) (Handler, error) {
|
func NetHandler(network, addr string, fmtr Format) (Handler, error) {
|
||||||
|
|
@ -135,15 +244,14 @@ func CallerStackHandler(format string, h Handler) Handler {
|
||||||
// wrapped Handler if the given function evaluates true. For example,
|
// wrapped Handler if the given function evaluates true. For example,
|
||||||
// to only log records where the 'err' key is not nil:
|
// to only log records where the 'err' key is not nil:
|
||||||
//
|
//
|
||||||
// logger.SetHandler(FilterHandler(func(r *Record) bool {
|
// logger.SetHandler(FilterHandler(func(r *Record) bool {
|
||||||
// for i := 0; i < len(r.Ctx); i += 2 {
|
// for i := 0; i < len(r.Ctx); i += 2 {
|
||||||
// if r.Ctx[i] == "err" {
|
// if r.Ctx[i] == "err" {
|
||||||
// return r.Ctx[i+1] != nil
|
// return r.Ctx[i+1] != nil
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// return false
|
// return false
|
||||||
// }, h))
|
// }, h))
|
||||||
//
|
|
||||||
func FilterHandler(fn func(r *Record) bool, h Handler) Handler {
|
func FilterHandler(fn func(r *Record) bool, h Handler) Handler {
|
||||||
return FuncHandler(func(r *Record) error {
|
return FuncHandler(func(r *Record) error {
|
||||||
if fn(r) {
|
if fn(r) {
|
||||||
|
|
@ -158,8 +266,7 @@ func FilterHandler(fn func(r *Record) bool, h Handler) Handler {
|
||||||
// context matches the value. For example, to only log records
|
// context matches the value. For example, to only log records
|
||||||
// from your ui package:
|
// from your ui package:
|
||||||
//
|
//
|
||||||
// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler)
|
// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler)
|
||||||
//
|
|
||||||
func MatchFilterHandler(key string, value interface{}, h Handler) Handler {
|
func MatchFilterHandler(key string, value interface{}, h Handler) Handler {
|
||||||
return FilterHandler(func(r *Record) (pass bool) {
|
return FilterHandler(func(r *Record) (pass bool) {
|
||||||
switch key {
|
switch key {
|
||||||
|
|
@ -185,8 +292,7 @@ func MatchFilterHandler(key string, value interface{}, h Handler) Handler {
|
||||||
// level to the wrapped Handler. For example, to only
|
// level to the wrapped Handler. For example, to only
|
||||||
// log Error/Crit records:
|
// log Error/Crit records:
|
||||||
//
|
//
|
||||||
// log.LvlFilterHandler(log.LvlError, log.StdoutHandler)
|
// log.LvlFilterHandler(log.LvlError, log.StdoutHandler)
|
||||||
//
|
|
||||||
func LvlFilterHandler(maxLvl Lvl, h Handler) Handler {
|
func LvlFilterHandler(maxLvl Lvl, h Handler) Handler {
|
||||||
return FilterHandler(func(r *Record) (pass bool) {
|
return FilterHandler(func(r *Record) (pass bool) {
|
||||||
return r.Lvl <= maxLvl
|
return r.Lvl <= maxLvl
|
||||||
|
|
@ -198,10 +304,9 @@ func LvlFilterHandler(maxLvl Lvl, h Handler) Handler {
|
||||||
// to different locations. For example, to log to a file and
|
// to different locations. For example, to log to a file and
|
||||||
// standard error:
|
// standard error:
|
||||||
//
|
//
|
||||||
// log.MultiHandler(
|
// log.MultiHandler(
|
||||||
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
|
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
|
||||||
// log.StderrHandler)
|
// log.StderrHandler)
|
||||||
//
|
|
||||||
func MultiHandler(hs ...Handler) Handler {
|
func MultiHandler(hs ...Handler) Handler {
|
||||||
return FuncHandler(func(r *Record) error {
|
return FuncHandler(func(r *Record) error {
|
||||||
for _, h := range hs {
|
for _, h := range hs {
|
||||||
|
|
@ -219,10 +324,10 @@ func MultiHandler(hs ...Handler) Handler {
|
||||||
// to writing to a file if the network fails, and then to
|
// to writing to a file if the network fails, and then to
|
||||||
// standard out if the file write fails:
|
// standard out if the file write fails:
|
||||||
//
|
//
|
||||||
// log.FailoverHandler(
|
// log.FailoverHandler(
|
||||||
// log.Must.NetHandler("tcp", ":9090", log.JSONFormat()),
|
// log.Must.NetHandler("tcp", ":9090", log.JSONFormat()),
|
||||||
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
|
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
|
||||||
// log.StdoutHandler)
|
// log.StdoutHandler)
|
||||||
//
|
//
|
||||||
// All writes that do not go to the first handler will add context with keys of
|
// 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
|
// the form "failover_err_{idx}" which explain the error encountered while
|
||||||
|
|
|
||||||
|
|
@ -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
|
// pattern contains a filter for the Vmodule option, holding a verbosity level
|
||||||
// and a file pattern to match.
|
// and a file pattern to match.
|
||||||
type pattern struct {
|
type pattern struct {
|
||||||
|
|
@ -77,14 +82,14 @@ func (h *GlogHandler) Verbosity(level Lvl) {
|
||||||
//
|
//
|
||||||
// For instance:
|
// For instance:
|
||||||
//
|
//
|
||||||
// pattern="gopher.go=3"
|
// pattern="gopher.go=3"
|
||||||
// sets the V level to 3 in all Go files named "gopher.go"
|
// sets the V level to 3 in all Go files named "gopher.go"
|
||||||
//
|
//
|
||||||
// pattern="foo=3"
|
// pattern="foo=3"
|
||||||
// sets V to 3 in all files of any packages whose import path ends in "foo"
|
// sets V to 3 in all files of any packages whose import path ends in "foo"
|
||||||
//
|
//
|
||||||
// pattern="foo/*=3"
|
// pattern="foo/*=3"
|
||||||
// sets V to 3 in all files of any packages whose import path contains "foo"
|
// sets V to 3 in all files of any packages whose import path contains "foo"
|
||||||
func (h *GlogHandler) Vmodule(ruleset string) error {
|
func (h *GlogHandler) Vmodule(ruleset string) error {
|
||||||
var filter []pattern
|
var filter []pattern
|
||||||
for _, rule := range strings.Split(ruleset, ",") {
|
for _, rule := range strings.Split(ruleset, ",") {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
const timeKey = "t"
|
const timeKey = "t"
|
||||||
const lvlKey = "lvl"
|
const lvlKey = "lvl"
|
||||||
const msgKey = "msg"
|
const msgKey = "msg"
|
||||||
|
const ctxKey = "ctx"
|
||||||
const errorKey = "LOG15_ERROR"
|
const errorKey = "LOG15_ERROR"
|
||||||
|
|
||||||
type Lvl int
|
type Lvl int
|
||||||
|
|
@ -100,6 +101,7 @@ type RecordKeyNames struct {
|
||||||
Time string
|
Time string
|
||||||
Msg string
|
Msg string
|
||||||
Lvl string
|
Lvl string
|
||||||
|
Ctx string
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Logger writes key/value pairs to a Handler
|
// A Logger writes key/value pairs to a Handler
|
||||||
|
|
@ -138,6 +140,7 @@ func (l *logger) write(msg string, lvl Lvl, ctx []interface{}) {
|
||||||
Time: timeKey,
|
Time: timeKey,
|
||||||
Msg: msgKey,
|
Msg: msgKey,
|
||||||
Lvl: lvlKey,
|
Lvl: lvlKey,
|
||||||
|
Ctx: ctxKey,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue