From 3fa7c2694657a01287e66cab6b031535313f77af Mon Sep 17 00:00:00 2001 From: Sumanth Chinthagunta Date: Thu, 20 Feb 2020 23:57:59 -0800 Subject: [PATCH] logger with helper methods (#1216) * support unix daemon socket * refactor(logger): logger fields changed to map[string]interface{} * improvement(logger): adding string to Level Parser * improvement(logger): rename ParseLevel to GetLevel * refactor(logger): adding basic logger adding micro default logger, and refactor logger interface * refactor(logger): moved basic logger to top level package * refactor(logger): adding default logger --- logger/default.go | 96 +++++++++++++++++++++++++++++++++++++++++++++++ logger/level.go | 43 +++++++++++++++++++-- logger/logger.go | 65 +++++++++++++------------------- logger/options.go | 50 ++++++++++++++++-------- 4 files changed, 196 insertions(+), 58 deletions(-) create mode 100644 logger/default.go diff --git a/logger/default.go b/logger/default.go new file mode 100644 index 00000000..ec099c6c --- /dev/null +++ b/logger/default.go @@ -0,0 +1,96 @@ +package logger + +import ( + "context" + "encoding/json" + "fmt" + "log" + "os" +) + +type defaultLogger struct { + opts Options + err error +} + +// Init(opts...) should only overwrite provided options +func (l *defaultLogger) Init(opts ...Option) error { + for _, o := range opts { + o(&l.opts) + } + return nil +} + +func (l *defaultLogger) String() string { + return "default" +} + +func (l *defaultLogger) Fields(fields map[string]interface{}) Logger { + l.opts.Fields = fields + return l +} + +func (l *defaultLogger) Error(err error) Logger { + l.err = err + return l +} + +func (l *defaultLogger) Log(level Level, v ...interface{}) { + if !l.opts.Level.Enabled(level) { + return + } + msg := fmt.Sprint(v...) + + fields := l.opts.Fields + fields["level"] = level.String() + fields["message"] = msg + if l.err != nil { + fields["error"] = l.err.Error() + } + + enc := json.NewEncoder(l.opts.Out) + + if err := enc.Encode(fields); err != nil { + log.Fatal(err) + } +} + +func (l *defaultLogger) Logf(level Level, format string, v ...interface{}) { + if level < l.opts.Level { + return + } + msg := fmt.Sprintf(format, v...) + + fields := l.opts.Fields + fields["level"] = level.String() + fields["message"] = msg + if l.err != nil { + fields["error"] = l.err.Error() + } + + enc := json.NewEncoder(l.opts.Out) + + if err := enc.Encode(fields); err != nil { + log.Fatal(err) + } + +} + +func (n *defaultLogger) Options() Options { + return n.opts +} + +// NewLogger builds a new logger based on options +func NewLogger(opts ...Option) Logger { + // Default options + options := Options{ + Level: InfoLevel, + Fields: make(map[string]interface{}), + Out: os.Stderr, + Context: context.Background(), + } + + l := &defaultLogger{opts: options} + _ = l.Init(opts...) + return l +} diff --git a/logger/level.go b/logger/level.go index b49ab52d..f09f7e8a 100644 --- a/logger/level.go +++ b/logger/level.go @@ -1,14 +1,24 @@ package logger +import "fmt" + type Level int8 const ( - TraceLevel Level = iota - 1 + // TraceLevel level. Designates finer-grained informational events than the Debug. + TraceLevel Level = iota - 2 + // DebugLevel level. Usually only enabled when debugging. Very verbose logging. DebugLevel + // InfoLevel is the default logging priority. + // General operational entries about what's going on inside the application. InfoLevel + // WarnLevel level. Non-critical entries that deserve eyes. WarnLevel + // ErrorLevel level. Logs. Used for errors that should definitely be noted. ErrorLevel + // PanicLevel level, logs the message and then panics. PanicLevel + // FatalLevel level. Logs and then calls `logger.Exit(1)`. highest level of severity. FatalLevel ) @@ -24,10 +34,37 @@ func (l Level) String() string { return "warn" case ErrorLevel: return "error" - case FatalLevel: - return "fatal" case PanicLevel: return "panic" + case FatalLevel: + return "fatal" } return "" } + +// Enabled returns true if the given level is at or above this level. +func (l Level) Enabled(lvl Level) bool { + return lvl >= l +} + +// GetLevel converts a level string into a logger Level value. +// returns an error if the input string does not match known values. +func GetLevel(levelStr string) (Level, error) { + switch levelStr { + case TraceLevel.String(): + return TraceLevel, nil + case DebugLevel.String(): + return DebugLevel, nil + case InfoLevel.String(): + return InfoLevel, nil + case WarnLevel.String(): + return WarnLevel, nil + case ErrorLevel.String(): + return ErrorLevel, nil + case PanicLevel.String(): + return PanicLevel, nil + case FatalLevel.String(): + return FatalLevel, nil + } + return InfoLevel, fmt.Errorf("Unknown Level String: '%s', defaulting to NoLevel", levelStr) +} diff --git a/logger/logger.go b/logger/logger.go index fa50662f..ffc00ff3 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -1,9 +1,9 @@ // Package log provides a log interface package logger -import ( - "fmt" - "sync" +var ( + // Default logger + DefaultLogger Logger = NewLogger() ) // Logger is a generic logging interface @@ -24,45 +24,32 @@ type Logger interface { String() string } -var ( - mtx sync.Mutex - loggerMap = map[string]Logger{} -) - -func Register(logger Logger) { - mtx.Lock() - defer mtx.Unlock() - - loggerMap[logger.String()] = logger +func Init(opts ...Option) error { + return DefaultLogger.Init(opts...) } -func GetLogger(name string) (Logger, error) { - l := loggerMap[name] - if l == nil { - return nil, fmt.Errorf("no such name logger found %s", name) +func Error(err error) Logger { + return DefaultLogger.Error(err) +} + +func Fields(fields map[string]interface{}) Logger { + return DefaultLogger.Fields(fields) +} + +func Log(level Level, v ...interface{}) { + DefaultLogger.Log(level, v...) +} + +func Logf(level Level, format string, v ...interface{}) { + DefaultLogger.Logf(level, format, v...) +} + +func SetGlobalLevel(lvl Level) { + if err := Init(WithLevel(lvl)); err != nil { + print(err) } - - return l, nil } -// GetLevel converts a level string into a logger Level value. -// returns an error if the input string does not match known values. -func GetLevel(levelStr string) (Level, error) { - switch levelStr { - case TraceLevel.String(): - return TraceLevel, nil - case DebugLevel.String(): - return DebugLevel, nil - case InfoLevel.String(): - return InfoLevel, nil - case WarnLevel.String(): - return WarnLevel, nil - case ErrorLevel.String(): - return ErrorLevel, nil - case FatalLevel.String(): - return FatalLevel, nil - case PanicLevel.String(): - return PanicLevel, nil - } - return InfoLevel, fmt.Errorf("Unknown Level String: '%s', defaulting to NoLevel", levelStr) +func String() string { + return DefaultLogger.String() } diff --git a/logger/options.go b/logger/options.go index 2b4e6aef..ef13d49f 100644 --- a/logger/options.go +++ b/logger/options.go @@ -2,30 +2,48 @@ package logger import ( "context" + "io" ) -// Option for load profiles maybe -// eg. yml -// micro: -// logger: -// name: -// dialect: zap/default/logrus -// zap: -// xxx: -// logrus: -// xxx: type Option func(*Options) type Options struct { - // The Log Level + // The logging level the logger should log at. default is `InfoLevel` Level Level - // Other opts + // fields to always be logged + Fields map[string]interface{} + // It's common to set this to a file, or leave it default which is `os.Stderr` + Out io.Writer + // Alternative options Context context.Context } -// WithLevel sets the log level -func WithLevel(l Level) Option { - return func(o *Options) { - o.Level = l +// WithFields set default fields for the logger +func WithFields(fields map[string]interface{}) Option { + return func(args *Options) { + args.Fields = fields + } +} + +// WithLevel set default level for the logger +func WithLevel(level Level) Option { + return func(args *Options) { + args.Level = level + } +} + +// WithOutput set default output writer for the logger +func WithOutput(out io.Writer) Option { + return func(args *Options) { + args.Out = out + } +} + +func SetOption(k, v interface{}) Option { + return func(o *Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, k, v) } }