diff --git a/.gitignore b/.gitignore index ae81252..2a83b0a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,23 @@ -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# -# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib - -# Test binary, built with `go test -c` *.test - -# Output of the go coverage tool, specifically when used with LiteIDE *.out +*.db +*.db-shm +*.db-wal +*.db-journal +*.dev.yaml -# Dependency directories (remove the comment below to include it) -# vendor/ - -# Go workspace file -go.work -go.work.sum +.idea/ +.vscode/ .tools/ + +coverage.txt +coverage.out + +bin/ +vendor/ +build/ diff --git a/common.go b/common.go index 62e48f5..2eb22ac 100644 --- a/common.go +++ b/common.go @@ -23,28 +23,18 @@ var levels = map[uint32]string{ LevelDebug: "DBG", } -type Fields map[string]interface{} - type Sender interface { - PutEntity(v *entity) SendMessage(level uint32, call func(v *Message)) Close() } // Writer interface type Writer interface { - Fatalf(format string, args ...interface{}) - Errorf(format string, args ...interface{}) - Warnf(format string, args ...interface{}) - Infof(format string, args ...interface{}) - Debugf(format string, args ...interface{}) -} - -type WriterContext interface { - WithError(key string, err error) Writer - WithField(key string, value interface{}) Writer - WithFields(Fields) Writer - Writer + Fatal(format string, args ...interface{}) + Error(format string, args ...interface{}) + Warn(format string, args ...interface{}) + Info(format string, args ...interface{}) + Debug(format string, args ...interface{}) } // Logger base interface @@ -55,5 +45,5 @@ type Logger interface { GetLevel() uint32 Close() - WriterContext + Writer } diff --git a/default.go b/default.go index 438fb77..9df51a4 100644 --- a/default.go +++ b/default.go @@ -38,42 +38,27 @@ func Close() { std.Close() } -// Infof info message -func Infof(format string, args ...interface{}) { - std.Infof(format, args...) +// Info message +func Info(format string, args ...interface{}) { + std.Info(format, args...) } -// Warnf warning message -func Warnf(format string, args ...interface{}) { - std.Warnf(format, args...) +// Warn message +func Warn(format string, args ...interface{}) { + std.Warn(format, args...) } -// Errorf error message -func Errorf(format string, args ...interface{}) { - std.Errorf(format, args...) +// Error message +func Error(format string, args ...interface{}) { + std.Error(format, args...) } -// Debugf debug message -func Debugf(format string, args ...interface{}) { - std.Debugf(format, args...) +// Debug message +func Debug(format string, args ...interface{}) { + std.Debug(format, args...) } -// Fatalf fatal message and exit -func Fatalf(format string, args ...interface{}) { - std.Fatalf(format, args...) -} - -// WithFields setter context to log message -func WithFields(v Fields) Writer { - return std.WithFields(v) -} - -// WithError setter context to log message -func WithError(key string, err error) Writer { - return std.WithError(key, err) -} - -// WithField setter context to log message -func WithField(key string, value interface{}) Writer { - return std.WithField(key, value) +// Fatal message and exit +func Fatal(format string, args ...interface{}) { + std.Fatal(format, args...) } diff --git a/entity.go b/entity.go deleted file mode 100644 index bd19601..0000000 --- a/entity.go +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2024 Mikhail Knyazhev . All rights reserved. - * Use of this source code is governed by a BSD 3-Clause license that can be found in the LICENSE file. - */ - -package logx - -import ( - "fmt" - "os" - "reflect" -) - -type entity struct { - log Sender - ctx Fields -} - -func newEntity(log Sender) *entity { - return &entity{ - log: log, - ctx: Fields{}, - } -} - -func (e *entity) Reset() { - e.ctx = Fields{} -} - -func (e *entity) WithError(key string, err error) Writer { - if err != nil { - e.ctx[key] = err.Error() - } else { - e.ctx[key] = nil - } - return e -} - -func (e *entity) WithField(key string, value interface{}) Writer { - ref := reflect.TypeOf(value) - if ref != nil { - switch ref.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Struct: - e.ctx[key] = fmt.Sprintf("unsupported field value: %#v", value) - return e - } - } - e.ctx[key] = value - return e -} - -func (e *entity) WithFields(fields Fields) Writer { - for key, value := range fields { - ref := reflect.TypeOf(value) - if ref != nil { - switch ref.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Struct: - e.ctx[key] = fmt.Sprintf("unsupported field value: %#v", value) - continue - } - } - e.ctx[key] = value - } - return e -} - -func (e *entity) prepareMessage(format string, args ...interface{}) func(v *Message) { - return func(v *Message) { - v.Message = fmt.Sprintf(format, args...) - for key, value := range e.ctx { - v.Ctx[key] = value - } - e.log.PutEntity(e) - } -} - -// Infof info message -func (e *entity) Infof(format string, args ...interface{}) { - e.log.SendMessage(LevelInfo, e.prepareMessage(format, args...)) -} - -// Warnf warning message -func (e *entity) Warnf(format string, args ...interface{}) { - e.log.SendMessage(LevelWarn, e.prepareMessage(format, args...)) -} - -// Errorf error message -func (e *entity) Errorf(format string, args ...interface{}) { - e.log.SendMessage(LevelError, e.prepareMessage(format, args...)) -} - -// Debugf debug message -func (e *entity) Debugf(format string, args ...interface{}) { - e.log.SendMessage(LevelDebug, e.prepareMessage(format, args...)) -} - -// Fatalf fatal message and exit -func (e *entity) Fatalf(format string, args ...interface{}) { - e.log.SendMessage(levelFatal, e.prepareMessage(format, args...)) - e.log.Close() - os.Exit(1) -} diff --git a/formatter.go b/formatter.go index fe90cd3..729c2ec 100644 --- a/formatter.go +++ b/formatter.go @@ -9,14 +9,20 @@ import ( "bytes" "encoding/json" "fmt" - "sync" + "strings" "time" + + "go.osspkg.com/ioutils/pool" ) type Formatter interface { Encode(m *Message) ([]byte, error) } +var newLine = []byte("\n") + +// ////////////////////////////////////////////////////////////////////////////// + type FormatJSON struct{} func NewFormatJSON() *FormatJSON { @@ -24,6 +30,7 @@ func NewFormatJSON() *FormatJSON { } func (*FormatJSON) Encode(m *Message) ([]byte, error) { + m.CtxToMap() b, err := json.Marshal(m) if err != nil { return nil, err @@ -31,15 +38,11 @@ func (*FormatJSON) Encode(m *Message) ([]byte, error) { return append(b, '\n'), nil } -var poolBuff = sync.Pool{ - New: func() interface{} { - return newBuff() - }, -} +// ////////////////////////////////////////////////////////////////////////////// -func newBuff() *bytes.Buffer { - return bytes.NewBuffer(nil) -} +var poolBuff = pool.New[*bytes.Buffer](func() *bytes.Buffer { + return bytes.NewBuffer(make([]byte, 0, 1024)) +}) type FormatString struct { delim string @@ -54,25 +57,43 @@ func (v *FormatString) SetDelimiter(d string) { } func (v *FormatString) Encode(m *Message) ([]byte, error) { - b, ok := poolBuff.Get().(*bytes.Buffer) - if !ok { - b = newBuff() - } - + buff := poolBuff.Get() defer func() { - b.Reset() - poolBuff.Put(b) + poolBuff.Put(buff) }() - fmt.Fprintf(b, "time=%s%slvl=%s%smsg=%#v", - time.Unix(m.UnixTime, 0).Format(time.RFC3339), - v.delim, m.Level, v.delim, m.Message) - if len(m.Ctx) > 0 { - for key, value := range m.Ctx { - fmt.Fprintf(b, "%s%s=%#v", v.delim, key, value) + fmt.Fprintf(buff, "time=%s%slvl=%s%smsg=%#v", + m.Time.Format(time.RFC3339), v.delim, m.Level, v.delim, m.Message) + + if count := len(m.Ctx); count > 0 { + if count%2 != 0 { + m.Ctx = append(m.Ctx, nil) + count++ + } + for i := 0; i < count; i = i + 2 { + fmt.Fprintf(buff, "%s%s=\"%s\"", v.delim, typing(m.Ctx[i]), typing(m.Ctx[i+1])) } } - b.WriteString("\n") + buff.Write(newLine) + + return append(make([]byte, 0, buff.Len()), buff.Bytes()...), nil +} - return append(make([]byte, 0, b.Len()), b.Bytes()...), nil +func typing(v interface{}) (s string) { + if v == nil { + s = "null" + return + } + switch vv := v.(type) { + case error: + s = vv.Error() + case fmt.GoStringer: + s = vv.GoString() + case fmt.Stringer: + s = vv.String() + default: + s = fmt.Sprintf("%#v", v) + } + s = strings.Trim(s, "\"") + return } diff --git a/formatter_test.go b/formatter_test.go index 6e472da..15d196b 100644 --- a/formatter_test.go +++ b/formatter_test.go @@ -8,6 +8,7 @@ package logx import ( "bytes" "testing" + "time" ) func TestUnit_FormatString_Encode(t *testing.T) { @@ -20,11 +21,11 @@ func TestUnit_FormatString_Encode(t *testing.T) { { name: "Case1", args: &Message{ - UnixTime: 123456789, - Level: "INF", - Message: "Hello", - Ctx: map[string]interface{}{ - "err": "err\nmsg", + Time: time.Now(), + Level: "INF", + Message: "Hello", + Ctx: []interface{}{ + "err", "err\nmsg", }, }, want: []byte("lvl=INF\tmsg=\"Hello\"\terr=\"err\\nmsg\"\n"), diff --git a/go.mod b/go.mod index 514cbd2..80af821 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( github.com/mailru/easyjson v0.7.7 go.osspkg.com/casecheck v0.3.0 + go.osspkg.com/ioutils v0.4.4 go.osspkg.com/syncing v0.3.0 ) diff --git a/go.sum b/go.sum index 5c979dd..4fedd05 100644 --- a/go.sum +++ b/go.sum @@ -4,5 +4,7 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= go.osspkg.com/casecheck v0.3.0 h1:x15blEszElbrHrEH5H02JIIhGIg/lGZzIt1kQlD3pwM= go.osspkg.com/casecheck v0.3.0/go.mod h1:TRFXDMFJEOtnlp3ET2Hix3osbxwPWhvaiT/HfD3+gBA= +go.osspkg.com/ioutils v0.4.4 h1:1DCGtlPn0/OaoRgUxNzRcH1L3K90WyFRY6CPcKbWuMU= +go.osspkg.com/ioutils v0.4.4/go.mod h1:58HhG2NHf9JUtixAH3R2XISlUmJruwVIUZ3039QVjOY= go.osspkg.com/syncing v0.3.0 h1:yBkCsDPEt12a+qagInFFt7+ZongfT+GjSQl7nBmcybI= go.osspkg.com/syncing v0.3.0/go.mod h1:Dpe0ljlEG6cI2Y9PxEjKiYEX2sgs1eUjWNVjFu4/iB0= diff --git a/logger.go b/logger.go index 2967db7..305d95a 100644 --- a/logger.go +++ b/logger.go @@ -8,7 +8,6 @@ package logx import ( "io" "os" - "sync" "sync/atomic" "time" @@ -17,29 +16,25 @@ import ( // log base model type log struct { - status uint32 + level uint32 writer io.Writer - entities sync.Pool formatter Formatter channel chan []byte mux syncing.Lock wg syncing.Group + closed bool } // New init new logger func New() Logger { object := &log{ - status: LevelError, + level: LevelError, writer: os.Stdout, formatter: NewFormatJSON(), - channel: make(chan []byte, 1024), - wg: syncing.NewGroup(), + channel: make(chan []byte, 100000), mux: syncing.NewLock(), - } - object.entities = sync.Pool{ - New: func() interface{} { - return newEntity(object) - }, + wg: syncing.NewGroup(), + closed: false, } object.wg.Background(func() { object.queue() @@ -52,19 +47,24 @@ func (l *log) SendMessage(level uint32, call func(v *Message)) { return } - m, ok := poolMessage.Get().(*Message) - if !ok { - m = &Message{} - } + m := poolMessage.Get() + defer func() { + poolMessage.Put(m) + }() call(m) + lvl, ok := levels[level] if !ok { lvl = "UNK" } - m.Level, m.UnixTime = lvl, time.Now().Unix() + m.Level, m.Time = lvl, time.Now() l.mux.RLock(func() { + if l.closed { + return + } + b, err := l.formatter.Encode(m) if err != nil { b = []byte(err.Error()) @@ -75,42 +75,22 @@ func (l *log) SendMessage(level uint32, call func(v *Message)) { default: } }) - - m.Reset() - poolMessage.Put(m) } func (l *log) queue() { - for { - b, ok := <-l.channel - if !ok { - return - } - if b == nil { - return - } + for b := range l.channel { l.mux.RLock(func() { l.writer.Write(b) //nolint:errcheck }) } } -func (l *log) getEntity() *entity { - lw, ok := l.entities.Get().(*entity) - if !ok { - lw = newEntity(l) - } - return lw -} - -func (l *log) PutEntity(v *entity) { - v.Reset() - l.entities.Put(v) -} - // Close waiting for all messages to finish recording func (l *log) Close() { - l.channel <- nil + l.mux.Lock(func() { + l.closed = true + close(l.channel) + }) l.wg.Wait() } @@ -129,50 +109,47 @@ func (l *log) SetFormatter(f Formatter) { // SetLevel change log level func (l *log) SetLevel(v uint32) { - atomic.StoreUint32(&l.status, v) + atomic.StoreUint32(&l.level, v) } // GetLevel getting log level func (l *log) GetLevel() uint32 { - return atomic.LoadUint32(&l.status) -} - -// Infof info message -func (l *log) Infof(format string, args ...interface{}) { - l.getEntity().Infof(format, args...) -} - -// Warnf warning message -func (l *log) Warnf(format string, args ...interface{}) { - l.getEntity().Warnf(format, args...) -} - -// Errorf error message -func (l *log) Errorf(format string, args ...interface{}) { - l.getEntity().Errorf(format, args...) + return atomic.LoadUint32(&l.level) } -// Debugf debug message -func (l *log) Debugf(format string, args ...interface{}) { - l.getEntity().Debugf(format, args...) +func (l *log) Info(message string, args ...interface{}) { + l.SendMessage(LevelInfo, func(v *Message) { + v.Message = message + v.Ctx = append(v.Ctx, args...) + }) } -// Fatalf fatal message and exit -func (l *log) Fatalf(format string, args ...interface{}) { - l.getEntity().Fatalf(format, args...) +func (l *log) Warn(message string, args ...interface{}) { + l.SendMessage(LevelWarn, func(v *Message) { + v.Message = message + v.Ctx = append(v.Ctx, args...) + }) } -// WithFields setter context to log message -func (l *log) WithFields(v Fields) Writer { - return l.getEntity().WithFields(v) +func (l *log) Error(message string, args ...interface{}) { + l.SendMessage(LevelError, func(v *Message) { + v.Message = message + v.Ctx = append(v.Ctx, args...) + }) } -// WithError setter context to log message -func (l *log) WithError(key string, err error) Writer { - return l.getEntity().WithError(key, err) +func (l *log) Debug(message string, args ...interface{}) { + l.SendMessage(LevelDebug, func(v *Message) { + v.Message = message + v.Ctx = append(v.Ctx, args...) + }) } -// WithField setter context to log message -func (l *log) WithField(key string, value interface{}) Writer { - return l.getEntity().WithField(key, value) +func (l *log) Fatal(message string, args ...interface{}) { + l.SendMessage(levelFatal, func(v *Message) { + v.Message = message + v.Ctx = append(v.Ctx, args...) + }) + l.Close() + os.Exit(1) } diff --git a/logger_test.go b/logger_test.go index f7826f5..5c4c30e 100644 --- a/logger_test.go +++ b/logger_test.go @@ -13,7 +13,6 @@ import ( "time" "go.osspkg.com/casecheck" - "go.osspkg.com/syncing" ) func TestUnit_NewJSON(t *testing.T) { @@ -26,26 +25,20 @@ func TestUnit_NewJSON(t *testing.T) { SetLevel(LevelDebug) casecheck.Equal(t, LevelDebug, GetLevel()) - go Infof("async %d", 1) - go Warnf("async %d", 2) - go Errorf("async %d", 3) - go Debugf("async %d", 4) + go Info("async", "id", 1) + go Warn("async", "id", 2) + go Error("async", "id", 3) + go Debug("async", "id", 4) - Infof("sync %d", 1) - Warnf("sync %d", 2) - Errorf("sync %d", 3) - Debugf("sync %d", 4) + Info("sync", "id", 1) + Warn("sync", "id", 2) + Error("sync", "id", 3) + Debug("sync", "id", 4) - WithFields(Fields{"ip": "0.0.0.0"}).Infof("context1") - WithFields(Fields{"nil": nil}).Infof("context2") - WithFields(Fields{"func": func() {}}).Infof("context3") - - WithField("ip", "0.0.0.0").Infof("context4") - WithField("nil", nil).Infof("context5") - WithField("func", func() {}).Infof("context6") - - WithError("err", nil).Infof("context7") - WithError("err", fmt.Errorf("er1")).Infof("context8") + Info("context1", "ip", "0.0.0.0") + Info("context2", "nil", nil) + Info("context3", "func", func() {}) + Info("context4", "err", fmt.Errorf("er1")) <-time.After(time.Second * 1) Close() @@ -56,21 +49,18 @@ func TestUnit_NewJSON(t *testing.T) { casecheck.NoError(t, os.Remove(filename.Name())) sdata := string(data) - casecheck.Contains(t, sdata, `"lvl":"INF","msg":"async 1"`) - casecheck.Contains(t, sdata, `"lvl":"WRN","msg":"async 2"`) - casecheck.Contains(t, sdata, `"lvl":"ERR","msg":"async 3"`) - casecheck.Contains(t, sdata, `"lvl":"DBG","msg":"async 4"`) - casecheck.Contains(t, sdata, `"lvl":"INF","msg":"sync 1"`) - casecheck.Contains(t, sdata, `"lvl":"WRN","msg":"sync 2"`) - casecheck.Contains(t, sdata, `"lvl":"ERR","msg":"sync 3"`) - casecheck.Contains(t, sdata, `"msg":"context1","ctx":{"ip":"0.0.0.0"}`) - casecheck.Contains(t, sdata, `"msg":"context2","ctx":{"nil":null}`) - casecheck.Contains(t, sdata, `"msg":"context3","ctx":{"func":"unsupported field value: (func())`) - casecheck.Contains(t, sdata, `"msg":"context4","ctx":{"ip":"0.0.0.0"}`) - casecheck.Contains(t, sdata, `"msg":"context5","ctx":{"nil":null}`) - casecheck.Contains(t, sdata, `"msg":"context6","ctx":{"func":"unsupported field value: (func())`) - casecheck.Contains(t, sdata, `"msg":"context7","ctx":{"err":null}`) - casecheck.Contains(t, sdata, `"msg":"context8","ctx":{"err":"er1"}`) + casecheck.Contains(t, sdata, `"lvl":"INF","msg":"async","ctx":{"id":"1"}`) + casecheck.Contains(t, sdata, `"lvl":"WRN","msg":"async","ctx":{"id":"2"}`) + casecheck.Contains(t, sdata, `"lvl":"ERR","msg":"async","ctx":{"id":"3"}`) + casecheck.Contains(t, sdata, `"lvl":"DBG","msg":"async","ctx":{"id":"4"}`) + casecheck.Contains(t, sdata, `"lvl":"INF","msg":"sync","ctx":{"id":"1"}`) + casecheck.Contains(t, sdata, `"lvl":"WRN","msg":"sync","ctx":{"id":"2"}`) + casecheck.Contains(t, sdata, `"lvl":"ERR","msg":"sync","ctx":{"id":"3"}`) + casecheck.Contains(t, sdata, `"lvl":"DBG","msg":"sync","ctx":{"id":"4"}`) + casecheck.Contains(t, sdata, `"lvl":"INF","msg":"context1","ctx":{"ip":"0.0.0.0"}`) + casecheck.Contains(t, sdata, `"lvl":"INF","msg":"context2","ctx":{"nil":"null"}`) + casecheck.Contains(t, sdata, `"lvl":"INF","msg":"context3","ctx":{"func":"(func())(0x`) + casecheck.Contains(t, sdata, `"lvl":"INF","msg":"context4","ctx":{"err":"er1"}`) } func TestUnit_NewString(t *testing.T) { @@ -86,26 +76,20 @@ func TestUnit_NewString(t *testing.T) { l.SetLevel(LevelDebug) casecheck.Equal(t, LevelDebug, l.GetLevel()) - go l.Infof("async %d", 1) - go l.Warnf("async %d", 2) - go l.Errorf("async %d", 3) - go l.Debugf("async %d", 4) - - l.Infof("sync %d", 1) - l.Warnf("sync %d", 2) - l.Errorf("sync %d", 3) - l.Debugf("sync %d", 4) - - l.WithFields(Fields{"ip": "0.0.0.0"}).Infof("context1") - l.WithFields(Fields{"nil": nil}).Infof("context2") - l.WithFields(Fields{"func": func() {}}).Infof("context3") + go l.Info("async", "id", 1) + go l.Warn("async", "id", 2) + go l.Error("async", "id", 3) + go l.Debug("async", "id", 4) - l.WithField("ip", "0.0.0.0").Infof("context4") - l.WithField("nil", nil).Infof("context5") - l.WithField("func", func() {}).Infof("context6") + l.Info("sync", "id", 1) + l.Warn("sync", "id", 2) + l.Error("sync", "id", 3) + l.Debug("sync", "id", 4) - l.WithError("err", nil).Infof("context7") - l.WithError("err", fmt.Errorf("er1")).Infof("context8") + l.Info("context1", "ip", "0.0.0.0\n") + l.Info("context2", "nil", nil) + l.Info("context3", "func", func() {}) + l.Info("context4", "err", fmt.Errorf("er1")) <-time.After(time.Second * 1) l.Close() @@ -116,22 +100,18 @@ func TestUnit_NewString(t *testing.T) { casecheck.NoError(t, os.Remove(filename.Name())) sdata := string(data) - casecheck.Contains(t, sdata, "lvl=INF msg=\"async 1\"") - casecheck.Contains(t, sdata, "lvl=WRN msg=\"async 2\"") - casecheck.Contains(t, sdata, "lvl=ERR msg=\"async 3\"") - casecheck.Contains(t, sdata, "lvl=DBG msg=\"async 4\"") - casecheck.Contains(t, sdata, "lvl=INF msg=\"sync 1\"") - casecheck.Contains(t, sdata, "lvl=WRN msg=\"sync 2\"") - casecheck.Contains(t, sdata, "lvl=ERR msg=\"sync 3\"") - casecheck.Contains(t, sdata, "lvl=DBG msg=\"sync 4\"") - casecheck.Contains(t, sdata, "lvl=INF msg=\"context1\" ip=\"0.0.0.0\"") - casecheck.Contains(t, sdata, "lvl=INF msg=\"context2\" nil=") - casecheck.Contains(t, sdata, "lvl=INF msg=\"context3\" func=\"unsupported field value: (func())(0x") - casecheck.Contains(t, sdata, "lvl=INF msg=\"context4\" ip=\"0.0.0.0\"") - casecheck.Contains(t, sdata, "lvl=INF msg=\"context5\" nil=") - casecheck.Contains(t, sdata, "lvl=INF msg=\"context6\" func=\"unsupported field value: (func())(0x") - casecheck.Contains(t, sdata, "lvl=INF msg=\"context7\" err=") - casecheck.Contains(t, sdata, "lvl=INF msg=\"context8\" err=\"er1\"") + casecheck.Contains(t, sdata, "lvl=INF\tmsg=\"async\"\tid=\"1\"") + casecheck.Contains(t, sdata, "lvl=WRN\tmsg=\"async\"\tid=\"2\"") + casecheck.Contains(t, sdata, "lvl=ERR\tmsg=\"async\"\tid=\"3\"") + casecheck.Contains(t, sdata, "lvl=DBG\tmsg=\"async\"\tid=\"4\"") + casecheck.Contains(t, sdata, "lvl=INF\tmsg=\"sync\"\tid=\"1\"") + casecheck.Contains(t, sdata, "lvl=WRN\tmsg=\"sync\"\tid=\"2\"") + casecheck.Contains(t, sdata, "lvl=ERR\tmsg=\"sync\"\tid=\"3\"") + casecheck.Contains(t, sdata, "lvl=DBG\tmsg=\"sync\"\tid=\"4\"") + casecheck.Contains(t, sdata, "lvl=INF\tmsg=\"context1\"\tip=\"0.0.0.0\\n\"") + casecheck.Contains(t, sdata, "lvl=INF\tmsg=\"context2\"\tnil=\"null\"") + casecheck.Contains(t, sdata, "lvl=INF\tmsg=\"context3\"\tfunc=\"(func())(0x") + casecheck.Contains(t, sdata, "lvl=INF\tmsg=\"context4\"\terr=\"er1\"") } func BenchmarkNewJSON(b *testing.B) { @@ -141,19 +121,13 @@ func BenchmarkNewJSON(b *testing.B) { ll.SetOutput(io.Discard) ll.SetLevel(LevelDebug) ll.SetFormatter(NewFormatJSON()) - wg := syncing.NewGroup() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { - wg.Background(func() { - ll.WithFields(Fields{"a": "b"}).Infof("hello") - ll.WithField("a", "b").Infof("hello") - ll.WithError("a", fmt.Errorf("b")).Infof("hello") - }) + ll.Info("sync", "id", 1) } }) - wg.Wait() ll.Close() } @@ -164,18 +138,12 @@ func BenchmarkNewString(b *testing.B) { ll.SetOutput(io.Discard) ll.SetLevel(LevelDebug) ll.SetFormatter(NewFormatString()) - wg := syncing.NewGroup() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { - wg.Background(func() { - ll.WithFields(Fields{"a": "b"}).Infof("hello") - ll.WithField("a", "b").Infof("hello") - ll.WithError("a", fmt.Errorf("b")).Infof("hello") - }) + ll.Info("sync", "id", 1) } }) - wg.Wait() ll.Close() } diff --git a/message.go b/message.go index 47b3661..8d662e7 100644 --- a/message.go +++ b/message.go @@ -5,35 +5,52 @@ package logx -import "sync" +import ( + "time" + + "go.osspkg.com/ioutils/pool" +) //go:generate easyjson -var poolMessage = sync.Pool{ - New: func() interface{} { - return newMessage() - }, -} +var poolMessage = pool.New[*Message](func() *Message { + return newMessage() +}) //easyjson:json type Message struct { - UnixTime int64 `json:"time" yaml:"time"` - Level string `json:"lvl" yaml:"lvl"` - Message string `json:"msg" yaml:"msg"` - Ctx map[string]interface{} `json:"ctx,omitempty" yaml:"ctx,omitempty,inline"` + Time time.Time `json:"time" yaml:"time"` + Level string `json:"lvl" yaml:"lvl"` + Message string `json:"msg" yaml:"msg"` + Ctx []interface{} `json:"-"` + Map map[string]string `json:"ctx,omitempty" yaml:"ctx,omitempty,inline"` } func newMessage() *Message { return &Message{ - Ctx: make(map[string]interface{}, 2), + Ctx: make([]interface{}, 0, 10), + Map: make(map[string]string, 10), } } func (v *Message) Reset() { - v.UnixTime = 0 - v.Level = "" - v.Message = "" - for s := range v.Ctx { - delete(v.Ctx, s) + v.Ctx = v.Ctx[:0] + for k := range v.Map { + delete(v.Map, k) + } +} + +func (v *Message) CtxToMap() { + count := len(v.Ctx) + if count == 0 { + return + } + if count%2 != 0 { + v.Ctx = append(v.Ctx, nil) + count++ + } + for i := 0; i < count; i = i + 2 { + v.Map[typing(v.Ctx[i])] = typing(v.Ctx[i+1]) } + v.Ctx = v.Ctx[:0] } diff --git a/message_easyjson.go b/message_easyjson.go index cbcbecf..356d29c 100644 --- a/message_easyjson.go +++ b/message_easyjson.go @@ -37,7 +37,9 @@ func easyjson4086215fDecodeGoOsspkgComLogx(in *jlexer.Lexer, out *Message) { } switch key { case "time": - out.UnixTime = int64(in.Int64()) + if data := in.Raw(); in.Ok() { + in.AddError((out.Time).UnmarshalJSON(data)) + } case "lvl": out.Level = string(in.String()) case "msg": @@ -48,22 +50,16 @@ func easyjson4086215fDecodeGoOsspkgComLogx(in *jlexer.Lexer, out *Message) { } else { in.Delim('{') if !in.IsDelim('}') { - out.Ctx = make(map[string]interface{}) + out.Map = make(map[string]string) } else { - out.Ctx = nil + out.Map = nil } for !in.IsDelim('}') { key := string(in.String()) in.WantColon() - var v1 interface{} - if m, ok := v1.(easyjson.Unmarshaler); ok { - m.UnmarshalEasyJSON(in) - } else if m, ok := v1.(json.Unmarshaler); ok { - _ = m.UnmarshalJSON(in.Raw()) - } else { - v1 = in.Interface() - } - (out.Ctx)[key] = v1 + var v1 string + v1 = string(in.String()) + (out.Map)[key] = v1 in.WantComma() } in.Delim('}') @@ -85,7 +81,7 @@ func easyjson4086215fEncodeGoOsspkgComLogx(out *jwriter.Writer, in Message) { { const prefix string = ",\"time\":" out.RawString(prefix[1:]) - out.Int64(int64(in.UnixTime)) + out.Raw((in.Time).MarshalJSON()) } { const prefix string = ",\"lvl\":" @@ -97,13 +93,13 @@ func easyjson4086215fEncodeGoOsspkgComLogx(out *jwriter.Writer, in Message) { out.RawString(prefix) out.String(string(in.Message)) } - if len(in.Ctx) != 0 { + if len(in.Map) != 0 { const prefix string = ",\"ctx\":" out.RawString(prefix) { out.RawByte('{') v2First := true - for v2Name, v2Value := range in.Ctx { + for v2Name, v2Value := range in.Map { if v2First { v2First = false } else { @@ -111,13 +107,7 @@ func easyjson4086215fEncodeGoOsspkgComLogx(out *jwriter.Writer, in Message) { } out.String(string(v2Name)) out.RawByte(':') - if m, ok := v2Value.(easyjson.Marshaler); ok { - m.MarshalEasyJSON(out) - } else if m, ok := v2Value.(json.Marshaler); ok { - out.Raw(m.MarshalJSON()) - } else { - out.Raw(json.Marshal(v2Value)) - } + out.String(string(v2Value)) } out.RawByte('}') }