@ -0,0 +1,97 @@ | |||
package log | |||
// NewFilter wraps next and implements filtering. See the commentary on the | |||
// Option functions for a detailed description of how to configure levels. If | |||
// no options are provided, all leveled log events created with Debug, Info or | |||
// Error helper methods are squelched. | |||
func NewFilter(next Logger, options ...Option) Logger { | |||
l := &filter{ | |||
next: next, | |||
} | |||
for _, option := range options { | |||
option(l) | |||
} | |||
return l | |||
} | |||
type filter struct { | |||
next Logger | |||
allowed level | |||
errNotAllowed error | |||
} | |||
func (l *filter) Info(msg string, keyvals ...interface{}) error { | |||
levelAllowed := l.allowed&levelInfo != 0 | |||
if !levelAllowed { | |||
return l.errNotAllowed | |||
} | |||
return l.next.Info(msg, keyvals...) | |||
} | |||
func (l *filter) Debug(msg string, keyvals ...interface{}) error { | |||
levelAllowed := l.allowed&levelDebug != 0 | |||
if !levelAllowed { | |||
return l.errNotAllowed | |||
} | |||
return l.next.Debug(msg, keyvals...) | |||
} | |||
func (l *filter) Error(msg string, keyvals ...interface{}) error { | |||
levelAllowed := l.allowed&levelError != 0 | |||
if !levelAllowed { | |||
return l.errNotAllowed | |||
} | |||
return l.next.Error(msg, keyvals...) | |||
} | |||
func (l *filter) With(keyvals ...interface{}) Logger { | |||
return l.next.With(keyvals...) | |||
} | |||
// Option sets a parameter for the filter. | |||
type Option func(*filter) | |||
// AllowAll is an alias for AllowDebug. | |||
func AllowAll() Option { | |||
return AllowDebug() | |||
} | |||
// AllowDebug allows error, warn, info and debug level log events to pass. | |||
func AllowDebug() Option { | |||
return allowed(levelError | levelInfo | levelDebug) | |||
} | |||
// AllowInfo allows error, warn and info level log events to pass. | |||
func AllowInfo() Option { | |||
return allowed(levelError | levelInfo) | |||
} | |||
// AllowError allows only error level log events to pass. | |||
func AllowError() Option { | |||
return allowed(levelError) | |||
} | |||
// AllowNone allows no leveled log events to pass. | |||
func AllowNone() Option { | |||
return allowed(0) | |||
} | |||
func allowed(allowed level) Option { | |||
return func(l *filter) { l.allowed = allowed } | |||
} | |||
// ErrNotAllowed sets the error to return from Log when it squelches a log | |||
// event disallowed by the configured Allow[Level] option. By default, | |||
// ErrNotAllowed is nil; in this case the log event is squelched with no | |||
// error. | |||
func ErrNotAllowed(err error) Option { | |||
return func(l *filter) { l.errNotAllowed = err } | |||
} | |||
type level byte | |||
const ( | |||
levelDebug level = 1 << iota | |||
levelInfo | |||
levelError | |||
) |
@ -0,0 +1,103 @@ | |||
package log_test | |||
import ( | |||
"bytes" | |||
"errors" | |||
"strings" | |||
"testing" | |||
"github.com/tendermint/tmlibs/log" | |||
) | |||
func TestVariousLevels(t *testing.T) { | |||
testCases := []struct { | |||
name string | |||
allowed log.Option | |||
want string | |||
}{ | |||
{ | |||
"AllowAll", | |||
log.AllowAll(), | |||
strings.Join([]string{ | |||
`{"_msg":"here","level":"debug","this is":"debug log"}`, | |||
`{"_msg":"here","level":"info","this is":"info log"}`, | |||
`{"_msg":"here","level":"error","this is":"error log"}`, | |||
}, "\n"), | |||
}, | |||
{ | |||
"AllowDebug", | |||
log.AllowDebug(), | |||
strings.Join([]string{ | |||
`{"_msg":"here","level":"debug","this is":"debug log"}`, | |||
`{"_msg":"here","level":"info","this is":"info log"}`, | |||
`{"_msg":"here","level":"error","this is":"error log"}`, | |||
}, "\n"), | |||
}, | |||
{ | |||
"AllowInfo", | |||
log.AllowInfo(), | |||
strings.Join([]string{ | |||
`{"_msg":"here","level":"info","this is":"info log"}`, | |||
`{"_msg":"here","level":"error","this is":"error log"}`, | |||
}, "\n"), | |||
}, | |||
{ | |||
"AllowError", | |||
log.AllowError(), | |||
strings.Join([]string{ | |||
`{"_msg":"here","level":"error","this is":"error log"}`, | |||
}, "\n"), | |||
}, | |||
{ | |||
"AllowNone", | |||
log.AllowNone(), | |||
``, | |||
}, | |||
} | |||
for _, tc := range testCases { | |||
t.Run(tc.name, func(t *testing.T) { | |||
var buf bytes.Buffer | |||
logger := log.NewFilter(log.NewTMJSONLogger(&buf), tc.allowed) | |||
logger.Debug("here", "this is", "debug log") | |||
logger.Info("here", "this is", "info log") | |||
logger.Error("here", "this is", "error log") | |||
if want, have := tc.want, strings.TrimSpace(buf.String()); want != have { | |||
t.Errorf("\nwant:\n%s\nhave:\n%s", want, have) | |||
} | |||
}) | |||
} | |||
} | |||
func TestErrNotAllowed(t *testing.T) { | |||
myError := errors.New("squelched!") | |||
opts := []log.Option{ | |||
log.AllowError(), | |||
log.ErrNotAllowed(myError), | |||
} | |||
logger := log.NewFilter(log.NewNopLogger(), opts...) | |||
if want, have := myError, logger.Info("foo", "bar", "baz"); want != have { | |||
t.Errorf("want %#+v, have %#+v", want, have) | |||
} | |||
if want, have := error(nil), logger.Error("foo", "bar", "baz"); want != have { | |||
t.Errorf("want %#+v, have %#+v", want, have) | |||
} | |||
} | |||
func TestLevelContext(t *testing.T) { | |||
var buf bytes.Buffer | |||
var logger log.Logger | |||
logger = log.NewTMJSONLogger(&buf) | |||
logger = log.NewFilter(logger, log.AllowAll()) | |||
logger = logger.With("context", "value") | |||
logger.Info("foo", "bar", "baz") | |||
if want, have := `{"_msg":"foo","bar":"baz","context":"value","level":"info"}`, strings.TrimSpace(buf.String()); want != have { | |||
t.Errorf("\nwant '%s'\nhave '%s'", want, have) | |||
} | |||
} |
@ -0,0 +1,15 @@ | |||
package log | |||
import ( | |||
"io" | |||
kitlog "github.com/go-kit/kit/log" | |||
) | |||
// NewTMJSONLogger returns a Logger that encodes keyvals to the Writer as a | |||
// single JSON object. Each log event produces no more than one call to | |||
// w.Write. The passed Writer must be safe for concurrent use by multiple | |||
// goroutines if the returned Logger will be used concurrently. | |||
func NewTMJSONLogger(w io.Writer) Logger { | |||
return &tmLogger{kitlog.NewJSONLogger(w)} | |||
} |