diff --git a/log/filter.go b/log/filter.go new file mode 100644 index 000000000..de1ebf55f --- /dev/null +++ b/log/filter.go @@ -0,0 +1,81 @@ +package log + +// FilterOption is filter option. +type FilterOption func(*Filter) + +// FilterLevel with filter level. +func FilterLevel(level Level) FilterOption { + return func(opts *Filter) { + opts.level = level + } +} + +// FilterKey with filter key. +func FilterKey(key ...string) FilterOption { + return func(o *Filter) { + for _, v := range key { + o.key[v] = struct{}{} + } + } +} + +// FilterValue with filter value. +func FilterValue(value ...string) FilterOption { + return func(o *Filter) { + for _, v := range value { + o.value[v] = struct{}{} + } + } +} + +// FilterFunc with filter func. +func FilterFunc(f func(level Level, keyvals ...interface{}) bool) FilterOption { + return func(o *Filter) { + o.filter = f + } +} + +// Filter is a logger filter. +type Filter struct { + logger Logger + level Level + key map[interface{}]struct{} + value map[interface{}]struct{} + filter func(level Level, keyvals ...interface{}) bool +} + +// NewFilter new a logger filter. +func NewFilter(logger Logger, opts ...FilterOption) *Filter { + options := Filter{ + logger: logger, + key: make(map[interface{}]struct{}), + value: make(map[interface{}]struct{}), + } + for _, o := range opts { + o(&options) + } + return &options +} + +// Log Print log by level and keyvals. +func (f *Filter) Log(level Level, keyvals ...interface{}) error { + if f.level > level { + return nil + } + if f.filter != nil && f.filter(level, keyvals...) { + return nil + } + for i := 0; i < len(keyvals); i += 2 { + if _, ok := f.key[keyvals[i]]; ok { + keyvals[i+1] = "***" + } + vi := i + 1 + if vi >= len(keyvals) { + continue + } + if _, ok := f.value[keyvals[vi]]; ok { + keyvals[i+1] = "***" + } + } + return f.logger.Log(level, keyvals...) +} diff --git a/log/filter_test.go b/log/filter_test.go new file mode 100644 index 000000000..386ee81cb --- /dev/null +++ b/log/filter_test.go @@ -0,0 +1,90 @@ +package log + +import ( + "io/ioutil" + "testing" +) + +func TestFilterAll(t *testing.T) { + logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller) + log := NewHelper(NewFilter(logger, + FilterLevel(LevelDebug), + FilterKey("username"), + FilterValue("hello"), + FilterFunc(testFilterFunc), + )) + log.Log(LevelDebug, "msg", "test debug") + log.Info("hello") + log.Infow("password", "123456") + log.Infow("username", "kratos") + log.Warn("warn log") +} +func TestFilterLevel(t *testing.T) { + logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller) + log := NewHelper(NewFilter(NewFilter(logger, FilterLevel(LevelWarn)))) + log.Log(LevelDebug, "msg1", "te1st debug") + log.Debug("test debug") + log.Debugf("test %s", "debug") + log.Debugw("log", "test debug") + log.Warn("warn log") +} + +func TestFilterCaller(t *testing.T) { + logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller) + log := NewFilter(logger) + log.Log(LevelDebug, "msg1", "te1st debug") + logHelper := NewHelper(NewFilter(logger)) + logHelper.Log(LevelDebug, "msg1", "te1st debug") +} + +func TestFilterKey(t *testing.T) { + logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller) + log := NewHelper(NewFilter(logger, FilterKey("password"))) + log.Debugw("password", "123456") +} + +func TestFilterValue(t *testing.T) { + logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller) + log := NewHelper(NewFilter(logger, FilterValue("debug"))) + log.Debugf("test %s", "debug") +} + +func TestFilterFunc(t *testing.T) { + logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller) + log := NewHelper(NewFilter(logger, FilterFunc(testFilterFunc))) + log.Debug("debug level") + log.Infow("password", "123456") +} + +func BenchmarkFilterKey(b *testing.B) { + log := NewHelper(NewFilter(NewStdLogger(ioutil.Discard), FilterKey("password"))) + for i := 0; i < b.N; i++ { + log.Infow("password", "123456") + } +} + +func BenchmarkFilterValue(b *testing.B) { + log := NewHelper(NewFilter(NewStdLogger(ioutil.Discard), FilterValue("password"))) + for i := 0; i < b.N; i++ { + log.Infow("password") + } +} + +func BenchmarkFilterFunc(b *testing.B) { + log := NewHelper(NewFilter(NewStdLogger(ioutil.Discard), FilterFunc(testFilterFunc))) + for i := 0; i < b.N; i++ { + log.Info("password", "123456") + } +} + +func testFilterFunc(level Level, keyvals ...interface{}) bool { + if level == LevelWarn { + return true + } + for i := 0; i < len(keyvals); i++ { + if keyvals[i] == "password" { + keyvals[i+1] = "***" + } + } + return false +} diff --git a/log/helper.go b/log/helper.go index 2a2aceb43..7e6a25d12 100644 --- a/log/helper.go +++ b/log/helper.go @@ -25,7 +25,7 @@ func (h *Helper) WithContext(ctx context.Context) *Helper { } } -// Log . +// Log Print log by level and keyvals. func (h *Helper) Log(level Level, keyvals ...interface{}) { h.logger.Log(level, keyvals...) } diff --git a/log/value.go b/log/value.go index ea6338ebb..a472d77af 100644 --- a/log/value.go +++ b/log/value.go @@ -33,8 +33,13 @@ func Value(ctx context.Context, v interface{}) interface{} { func Caller(depth int) Valuer { return func(context.Context) interface{} { _, file, line, _ := runtime.Caller(depth) + if strings.LastIndex(file, "/log/filter.go") > 0 { + depth++ + _, file, line, _ = runtime.Caller(depth) + } if strings.LastIndex(file, "/log/helper.go") > 0 { - _, file, line, _ = runtime.Caller(depth + 1) + depth++ + _, file, line, _ = runtime.Caller(depth) } idx := strings.LastIndexByte(file, '/') return file[idx+1:] + ":" + strconv.Itoa(line)