diff --git a/log/filter.go b/log/filter.go new file mode 100644 index 0000000..e9a8cc6 --- /dev/null +++ b/log/filter.go @@ -0,0 +1,80 @@ +package log + +type FilterOption func(*Filter) + +const fuzzyStr = "***" + +func FilterLevel(level Level) FilterOption { + return func(opts *Filter) { + opts.level = level + } +} +func FilterKey(key ...string) FilterOption { + return func(opts *Filter) { + for _, v := range key { + opts.key[v] = struct{}{} + } + } +} +func FilterValue(value ...string) FilterOption { + return func(opts *Filter) { + for _, v := range value { + opts.value[v] = struct{}{} + } + } +} +func FilterFunc(f func(level Level, keyvals ...any) bool) FilterOption { + return func(opts *Filter) { + opts.filter = f + } +} + +type Filter struct { + logger Logger + level Level + key map[any]struct{} + value map[any]struct{} + filter func(level Level, keyvals ...any) bool +} + +func NewFilter(logger Logger, opts ...FilterOption) *Filter { + options := &Filter{ + logger: logger, + key: make(map[any]struct{}), + value: make(map[any]struct{}), + } + for _, o := range opts { + o(options) + } + return options +} +func (f *Filter) Log(level Level, keyvals ...any) error { + if level < f.level { + return nil + } + var prefixkv []any + l, ok := f.logger.(*logger) + if ok && len(l.prefix) > 0 { + prefixkv = make([]any, 0, len(l.prefix)) + prefixkv = append(prefixkv, l.prefix...) + } + if f.filter != nil && (f.filter(level, prefixkv...) || f.filter(level, keyvals)) { + return nil + } + if len(f.key) > 0 || len(f.value) > 0 { + for i := 0; i < len(keyvals); i += 2 { + v := i + 1 + if v >= len(keyvals) { + continue + } + if _, ok := f.key[keyvals[i]]; ok { + keyvals[v] = fuzzyStr + } + + if _, ok := f.key[keyvals[v]]; ok { + keyvals[v] = fuzzyStr + } + } + } + return f.logger.Log(level, keyvals...) +} diff --git a/log/filter_test.go b/log/filter_test.go new file mode 100644 index 0000000..596c30c --- /dev/null +++ b/log/filter_test.go @@ -0,0 +1,31 @@ +package log + +import ( + "testing" +) + +func TestNewFilter(t *testing.T) { + logger := With(DefaultLogger, + //"ts", DefaultTimestamp, "caller", DefaultCaller + ) + log := NewHelper(NewFilter(logger, + FilterKey("username"), + FilterValue("hello"), + FilterFunc(func(level Level, keyvals ...any) bool { + if level == LevelWarn { + return true + } + for i := 0; i < len(keyvals); i++ { + if keyvals[i] == "password" { + keyvals[i+1] = fuzzyStr + } + } + return false + }), + )) + log.Log(LevelDebug, "msg", "test debug") + log.Info("hello") + log.Infow("password", "123456") + log.Infow("username", "kratos") + log.Warn("warn log") +} diff --git a/log/global.go b/log/global.go new file mode 100644 index 0000000..5975bce --- /dev/null +++ b/log/global.go @@ -0,0 +1,114 @@ +package log + +import ( + "context" + "fmt" + "os" + "sync" +) + +var global = &loggerAppliance{} + +type loggerAppliance struct { + lock sync.Mutex + Logger +} + +func init() { + global.SetLogger(DefaultLogger) +} +func (l *loggerAppliance) SetLogger(in Logger) { + l.lock.Lock() + defer l.lock.Unlock() + l.Logger = in +} +func SetLogger(logger Logger) { + global.SetLogger(logger) +} +func GetLogger() Logger { + return global +} +func Log(level Level, keyvals ...any) { + _ = global.Log(level, keyvals...) +} +func Context(ctx context.Context) *Helper { + return NewHelper(WithContext(ctx, global.Logger)) +} + +// Debug logs a message at debug level. +func Debug(a ...interface{}) { + _ = global.Log(LevelDebug, DefaultMessageKey, fmt.Sprint(a...)) +} + +// Debugf logs a message at debug level. +func Debugf(format string, a ...interface{}) { + _ = global.Log(LevelDebug, DefaultMessageKey, fmt.Sprintf(format, a...)) +} + +// Debugw logs a message at debug level. +func Debugw(keyvals ...interface{}) { + _ = global.Log(LevelDebug, keyvals...) +} + +// Info logs a message at info level. +func Info(a ...interface{}) { + _ = global.Log(LevelInfo, DefaultMessageKey, fmt.Sprint(a...)) +} + +// Infof logs a message at info level. +func Infof(format string, a ...interface{}) { + _ = global.Log(LevelInfo, DefaultMessageKey, fmt.Sprintf(format, a...)) +} + +// Infow logs a message at info level. +func Infow(keyvals ...interface{}) { + _ = global.Log(LevelInfo, keyvals...) +} + +// Warn logs a message at warn level. +func Warn(a ...interface{}) { + _ = global.Log(LevelWarn, DefaultMessageKey, fmt.Sprint(a...)) +} + +// Warnf logs a message at warnf level. +func Warnf(format string, a ...interface{}) { + _ = global.Log(LevelWarn, DefaultMessageKey, fmt.Sprintf(format, a...)) +} + +// Warnw logs a message at warnf level. +func Warnw(keyvals ...interface{}) { + _ = global.Log(LevelWarn, keyvals...) +} + +// Error logs a message at error level. +func Error(a ...interface{}) { + _ = global.Log(LevelError, DefaultMessageKey, fmt.Sprint(a...)) +} + +// Errorf logs a message at error level. +func Errorf(format string, a ...interface{}) { + _ = global.Log(LevelError, DefaultMessageKey, fmt.Sprintf(format, a...)) +} + +// Errorw logs a message at error level. +func Errorw(keyvals ...interface{}) { + _ = global.Log(LevelError, keyvals...) +} + +// Fatal logs a message at fatal level. +func Fatal(a ...interface{}) { + _ = global.Log(LevelFatal, DefaultMessageKey, fmt.Sprint(a...)) + os.Exit(1) +} + +// Fatalf logs a message at fatal level. +func Fatalf(format string, a ...interface{}) { + _ = global.Log(LevelFatal, DefaultMessageKey, fmt.Sprintf(format, a...)) + os.Exit(1) +} + +// Fatalw logs a message at fatal level. +func Fatalw(keyvals ...interface{}) { + _ = global.Log(LevelFatal, keyvals...) + os.Exit(1) +} diff --git a/log/global_test.go b/log/global_test.go new file mode 100644 index 0000000..1c6efb6 --- /dev/null +++ b/log/global_test.go @@ -0,0 +1,9 @@ +package log + +import ( + "testing" +) + +func TestDebug(t *testing.T) { + Debug("debug") +} diff --git a/log/helper.go b/log/helper.go new file mode 100644 index 0000000..0ef52ec --- /dev/null +++ b/log/helper.go @@ -0,0 +1,121 @@ +package log + +import ( + "context" + "fmt" + "os" +) + +var DefaultMessageKey = "msg" + +type Option func(*Helper) +type Helper struct { + logger Logger + msgKey string +} + +func WithMessageKey(k string) Option { + return func(opts *Helper) { + opts.msgKey = k + } +} + +func NewHelper(logger Logger, opts ...Option) *Helper { + options := &Helper{ + msgKey: DefaultMessageKey, + logger: logger, + } + for _, o := range opts { + o(options) + } + return options +} +func (h *Helper) WithContext(ctx context.Context) *Helper { + return &Helper{ + logger: WithContext(ctx, h.logger), + msgKey: h.msgKey, + } +} + +// Log Print log by level and keyvals. +func (h *Helper) Log(level Level, keyvals ...interface{}) { + _ = h.logger.Log(level, keyvals...) +} + +// Debug logs a message at debug level. +func (h *Helper) Debug(a ...interface{}) { + _ = h.logger.Log(LevelDebug, h.msgKey, fmt.Sprint(a...)) +} + +// Debugf logs a message at debug level. +func (h *Helper) Debugf(format string, a ...interface{}) { + _ = h.logger.Log(LevelDebug, h.msgKey, fmt.Sprintf(format, a...)) +} + +// Debugw logs a message at debug level. +func (h *Helper) Debugw(keyvals ...interface{}) { + _ = h.logger.Log(LevelDebug, keyvals...) +} + +// Info logs a message at info level. +func (h *Helper) Info(a ...interface{}) { + _ = h.logger.Log(LevelInfo, h.msgKey, fmt.Sprint(a...)) +} + +// Infof logs a message at info level. +func (h *Helper) Infof(format string, a ...interface{}) { + _ = h.logger.Log(LevelInfo, h.msgKey, fmt.Sprintf(format, a...)) +} + +// Infow logs a message at info level. +func (h *Helper) Infow(keyvals ...interface{}) { + _ = h.logger.Log(LevelInfo, keyvals...) +} + +// Warn logs a message at warn level. +func (h *Helper) Warn(a ...interface{}) { + _ = h.logger.Log(LevelWarn, h.msgKey, fmt.Sprint(a...)) +} + +// Warnf logs a message at warnf level. +func (h *Helper) Warnf(format string, a ...interface{}) { + _ = h.logger.Log(LevelWarn, h.msgKey, fmt.Sprintf(format, a...)) +} + +// Warnw logs a message at warnf level. +func (h *Helper) Warnw(keyvals ...interface{}) { + _ = h.logger.Log(LevelWarn, keyvals...) +} + +// Error logs a message at error level. +func (h *Helper) Error(a ...interface{}) { + _ = h.logger.Log(LevelError, h.msgKey, fmt.Sprint(a...)) +} + +// Errorf logs a message at error level. +func (h *Helper) Errorf(format string, a ...interface{}) { + _ = h.logger.Log(LevelError, h.msgKey, fmt.Sprintf(format, a...)) +} + +// Errorw logs a message at error level. +func (h *Helper) Errorw(keyvals ...interface{}) { + _ = h.logger.Log(LevelError, keyvals...) +} + +// Fatal logs a message at fatal level. +func (h *Helper) Fatal(a ...interface{}) { + _ = h.logger.Log(LevelFatal, h.msgKey, fmt.Sprint(a...)) + os.Exit(1) +} + +// Fatalf logs a message at fatal level. +func (h *Helper) Fatalf(format string, a ...interface{}) { + _ = h.logger.Log(LevelFatal, h.msgKey, fmt.Sprintf(format, a...)) + os.Exit(1) +} + +// Fatalw logs a message at fatal level. +func (h *Helper) Fatalw(keyvals ...interface{}) { + _ = h.logger.Log(LevelFatal, keyvals...) + os.Exit(1) +} diff --git a/log/helper_test.go b/log/helper_test.go new file mode 100644 index 0000000..32db525 --- /dev/null +++ b/log/helper_test.go @@ -0,0 +1,9 @@ +package log + +import "testing" + +func TestNewHelper(t *testing.T) { + logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller) + log := NewHelper(logger) + log.Debug("debug") +} diff --git a/log/level.go b/log/level.go new file mode 100644 index 0000000..0807fee --- /dev/null +++ b/log/level.go @@ -0,0 +1,49 @@ +package log + +import "strings" + +type Level int8 + +const LevelKey = "level" +const ( + LevelDebug Level = iota - 1 + LevelInfo + LevelWarn + LevelError + LevelFatal +) + +func (l Level) Key() string { + return LevelKey +} +func (l Level) String() string { + switch l { + case LevelDebug: + return "DEBUG" + case LevelInfo: + return "INFO" + case LevelWarn: + return "WARN" + case LevelError: + return "ERROR" + case LevelFatal: + return "FATAL" + default: + return "" + } +} +func ParseLevel(s string) Level { + switch strings.ToUpper(s) { + case "DEBUG": + return LevelDebug + case "INFO": + return LevelInfo + case "WARN": + return LevelWarn + case "ERROR": + return LevelError + case "FATAL": + return LevelFatal + } + return LevelInfo +} diff --git a/log/log.go b/log/log.go new file mode 100644 index 0000000..cf3a012 --- /dev/null +++ b/log/log.go @@ -0,0 +1,65 @@ +package log + +import ( + "context" + "log" +) + +var DefaultLogger = NewStdLogger(log.Writer()) + +type Logger interface { + Log(level Level, keyvals ...any) error +} + +type logger struct { + logger Logger + prefix []any + hasValuer bool + ctx context.Context +} + +func (l *logger) Log(level Level, keyvals ...any) error { + kvs := make([]any, 0, len(l.prefix)+len(keyvals)) + kvs = append(kvs, l.prefix...) + if l.hasValuer { + bindValues(l.ctx, kvs) + } + kvs = append(kvs, keyvals...) + return l.logger.Log(level, kvs...) +} + +func With(l Logger, kv ...any) Logger { + c, ok := l.(*logger) + if !ok { + return &logger{ + logger: l, + prefix: kv, + hasValuer: containsValuer(kv), + ctx: context.Background(), + } + } + kvs := make([]any, 0, len(c.prefix)+len(kv)) + kvs = append(kvs, c.prefix...) + kvs = append(kvs, kv...) + return &logger{ + logger: c.logger, + prefix: kvs, + hasValuer: containsValuer(kvs), + ctx: c.ctx, + } +} +func WithContext(ctx context.Context, l Logger) Logger { + c, ok := l.(*logger) + if !ok { + return &logger{ + logger: l, + ctx: ctx, + } + } + return &logger{ + logger: c.logger, + prefix: c.prefix, + hasValuer: c.hasValuer, + ctx: ctx, + } +} diff --git a/log/log_test.go b/log/log_test.go new file mode 100644 index 0000000..037b494 --- /dev/null +++ b/log/log_test.go @@ -0,0 +1,12 @@ +package log + +import ( + "testing" +) + +func Test_logger_Log(t *testing.T) { + logger := DefaultLogger + logger = With(logger, "ts", DefaultTimestamp) + logger = With(logger, "call", DefaultCaller) + _ = logger.Log(LevelInfo, "key1", "val1") +} diff --git a/log/std.go b/log/std.go new file mode 100644 index 0000000..9203215 --- /dev/null +++ b/log/std.go @@ -0,0 +1,42 @@ +package log + +import ( + "bytes" + "fmt" + "io" + "log" + "sync" +) + +type stdLogger struct { + log *log.Logger + pool *sync.Pool +} + +func NewStdLogger(w io.Writer) Logger { + return &stdLogger{ + log: log.New(w, "", 0), + pool: &sync.Pool{ + New: func() any { + return new(bytes.Buffer) + }, + }, + } +} +func (l *stdLogger) Log(level Level, keyvals ...any) error { + if len(keyvals) == 0 { + return nil + } + if len(keyvals)&1 == 1 { + keyvals = append(keyvals, "KEYVALS UNPAIRED") + } + buf := l.pool.Get().(*bytes.Buffer) + buf.WriteString(level.String()) + for i := 0; i < len(keyvals); i += 2 { + _, _ = fmt.Fprintf(buf, " %s=%v", keyvals[i], keyvals[i+1]) + } + _ = l.log.Output(4, buf.String()) + buf.Reset() + l.pool.Put(buf) + return nil +} diff --git a/log/value.go b/log/value.go new file mode 100644 index 0000000..77417f5 --- /dev/null +++ b/log/value.go @@ -0,0 +1,55 @@ +package log + +import ( + "context" + "runtime" + "strconv" + "strings" + "time" +) + +var ( + DefaultCaller = Caller(4) + DefaultTimestamp = Timestamp(time.RFC3339) +) + +type Valuer func(ctx context.Context) any + +func Value(ctx context.Context, v any) any { + if v, ok := v.(Valuer); ok { + return v(ctx) + } + return v +} +func Caller(depth int) Valuer { + return func(ctx context.Context) any { + _, file, line, _ := runtime.Caller(depth) + idx := strings.LastIndexByte(file, '/') + if idx == -1 { + return file[idx+1:] + ":" + strconv.Itoa(line) + } + idx = strings.LastIndexByte(file[:idx], '/') + return file[idx+1:] + ":" + strconv.Itoa(line) + } +} +func Timestamp(layout string) Valuer { + return func(ctx context.Context) any { + return time.Now().Format(layout) + } +} + +func bindValues(ctx context.Context, keyvals []any) { + for i := 1; i < len(keyvals); i += 2 { + if v, ok := keyvals[i].(Valuer); ok { + keyvals[i] = v(ctx) + } + } +} +func containsValuer(keyvals []any) bool { + for i := 1; i < len(keyvals); i += 2 { + if _, ok := keyvals[i].(Valuer); ok { + return true + } + } + return false +}