diff --git a/logger/default.go b/logger/default.go index 263426ee..87974e5c 100644 --- a/logger/default.go +++ b/logger/default.go @@ -40,7 +40,6 @@ func (l *defaultLogger) Init(opts ...Option) error { l.logFunc = l.opts.Wrappers[i-1].Log(l.logFunc) l.logfFunc = l.opts.Wrappers[i-1].Logf(l.logfFunc) } - l.Unlock() return nil } @@ -56,26 +55,20 @@ func (l *defaultLogger) V(level Level) bool { return ok } -func (l *defaultLogger) Fields(fields map[string]interface{}) Logger { +func (l *defaultLogger) Fields(fields ...interface{}) Logger { nl := &defaultLogger{opts: l.opts, enc: l.enc} - nl.opts.Fields = make(map[string]interface{}, len(l.opts.Fields)+len(fields)) - l.RLock() - for k, v := range l.opts.Fields { - nl.opts.Fields[k] = v - } - l.RUnlock() - - for k, v := range fields { - nl.opts.Fields[k] = v + if len(fields) == 0 { + return nl + } else if len(fields)%2 != 0 { + fields = fields[:len(fields)-1] } + nl.opts.Fields = append(l.opts.Fields, fields...) return nl } -func copyFields(src map[string]interface{}) map[string]interface{} { - dst := make(map[string]interface{}, len(src)) - for k, v := range src { - dst[k] = v - } +func copyFields(src []interface{}) []interface{} { + dst := make([]interface{}, len(src)) + copy(dst, src) return dst } @@ -162,19 +155,23 @@ func (l *defaultLogger) Log(ctx context.Context, level Level, args ...interface{ fields := copyFields(l.opts.Fields) l.RUnlock() - fields["level"] = level.String() + fields = append(fields, "level", level.String()) if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok { - fields["caller"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line) + fields = append(fields, "caller", fmt.Sprintf("%s:%d", logCallerfilePath(file), line)) } + fields = append(fields, "timestamp", time.Now().Format("2006-01-02 15:04:05")) - fields["timestamp"] = time.Now().Format("2006-01-02 15:04:05") if len(args) > 0 { - fields["msg"] = fmt.Sprint(args...) + fields = append(fields, "msg", fmt.Sprint(args...)) } + out := make(map[string]interface{}, len(fields)/2) + for i := 0; i < len(fields); i += 2 { + out[fields[i].(string)] = fields[i+1] + } l.RLock() - _ = l.enc.Encode(fields) + _ = l.enc.Encode(out) l.RUnlock() } @@ -187,30 +184,30 @@ func (l *defaultLogger) Logf(ctx context.Context, level Level, msg string, args fields := copyFields(l.opts.Fields) l.RUnlock() - fields["level"] = level.String() + fields = append(fields, "level", level.String()) if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok { - fields["caller"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line) + fields = append(fields, "caller", fmt.Sprintf("%s:%d", logCallerfilePath(file), line)) } - fields["timestamp"] = time.Now().Format("2006-01-02 15:04:05") + fields = append(fields, "timestamp", time.Now().Format("2006-01-02 15:04:05")) if len(args) > 0 { - fields["msg"] = fmt.Sprintf(msg, args...) + fields = append(fields, "msg", fmt.Sprintf(msg, args...)) } else if msg != "" { - fields["msg"] = msg + fields = append(fields, "msg", msg) + } + + out := make(map[string]interface{}, len(fields)/2) + for i := 0; i < len(fields); i += 2 { + out[fields[i].(string)] = fields[i+1] } l.RLock() - _ = l.enc.Encode(fields) + _ = l.enc.Encode(out) l.RUnlock() } func (l *defaultLogger) Options() Options { - // not guard against options Context values - l.RLock() - opts := l.opts - opts.Fields = copyFields(l.opts.Fields) - l.RUnlock() - return opts + return l.opts } // NewLogger builds a new logger based on options diff --git a/logger/logger.go b/logger/logger.go index 516886b3..c31d4b62 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -20,8 +20,8 @@ type Logger interface { V(level Level) bool // The Logger options Options() Options - // Fields set fields to always be logged - Fields(fields map[string]interface{}) Logger + // Fields set fields to always be logged with keyval pairs + Fields(fields ...interface{}) Logger // Info level message Info(ctx context.Context, args ...interface{}) // Trace level message @@ -54,6 +54,9 @@ type Logger interface { String() string } +// Field contains keyval pair +type Field interface{} + // Info writes msg to default logger on info level func Info(ctx context.Context, args ...interface{}) { DefaultLogger.Info(ctx, args...) @@ -125,6 +128,6 @@ func Init(opts ...Option) error { } // Fields create logger with specific fields -func Fields(fields map[string]interface{}) Logger { - return DefaultLogger.Fields(fields) +func Fields(fields ...interface{}) Logger { + return DefaultLogger.Fields(fields...) } diff --git a/logger/logger_test.go b/logger/logger_test.go index a893cf77..64c55d74 100644 --- a/logger/logger_test.go +++ b/logger/logger_test.go @@ -15,7 +15,7 @@ func TestLogger(t *testing.T) { } l.Trace(ctx, "trace_msg1") l.Warn(ctx, "warn_msg1") - l.Fields(map[string]interface{}{"error": "test"}).Info(ctx, "error message") + l.Fields("error", "test").Info(ctx, "error message") l.Warn(ctx, "first", " ", "second") if !bytes.Contains(buf.Bytes(), []byte(`"level":"trace","msg":"trace_msg1"`)) { t.Fatalf("logger error, buf %s", buf.Bytes()) diff --git a/logger/options.go b/logger/options.go index c618cae6..9dc7e87b 100644 --- a/logger/options.go +++ b/logger/options.go @@ -16,7 +16,7 @@ type Options struct { // Context holds exernal options Context context.Context // Fields holds additional metadata - Fields map[string]interface{} + Fields []interface{} // Name holds the logger name Name string // CallerSkipCount number of frmaes to skip @@ -31,7 +31,7 @@ type Options struct { func NewOptions(opts ...Option) Options { options := Options{ Level: DefaultLevel, - Fields: make(map[string]interface{}), + Fields: make([]interface{}, 0, 6), Out: os.Stderr, CallerSkipCount: DefaultCallerSkipCount, Context: context.Background(), @@ -43,7 +43,7 @@ func NewOptions(opts ...Option) Options { } // WithFields set default fields for the logger -func WithFields(fields map[string]interface{}) Option { +func WithFields(fields ...interface{}) Option { return func(o *Options) { o.Fields = fields } diff --git a/logger/wrapper.go b/logger/wrapper.go index 02c2a246..48b90ab5 100644 --- a/logger/wrapper.go +++ b/logger/wrapper.go @@ -44,8 +44,8 @@ func (w *OmitLogger) Options() Options { return w.l.Options() } -func (w *OmitLogger) Fields(fields map[string]interface{}) Logger { - return w.l.Fields(fields) +func (w *OmitLogger) Fields(fields ...interface{}) Logger { + return w.l.Fields(fields...) } func (w *OmitLogger) Info(ctx context.Context, args ...interface{}) {