diff --git a/logs/zap/core.go b/logs/zap/core.go index 0b54b5c38324e7ec18d1ad80497c3858ed246014..a6c1c2ecd7e9e2075d4a008bddb4e7911278d64d 100644 --- a/logs/zap/core.go +++ b/logs/zap/core.go @@ -28,14 +28,10 @@ func NewCore(writeSyncer WriteSyncer) *Core { } func (core *Core) With(fields []zapcore.Field) zapcore.Core { - clone := core.enc.Clone() - for i := range fields { - fields[i].AddTo(clone) - } return &Core{ LevelEnabler: core.LevelEnabler, ws: core.ws, - enc: clone, + enc: core.enc.With(fields), } } @@ -47,11 +43,11 @@ func (core *Core) Check(entry zapcore.Entry, checkedEntry *zapcore.CheckedEntry) } func (core *Core) Write(entry zapcore.Entry, fields []zapcore.Field) error { - encodeEntry, err := core.enc.EncodeEntry(entry, fields) + encodedEntry, err := core.enc.EncodeEntry(entry, fields) if err != nil { return err } - return core.ws.Write(encodeEntry) + return core.ws.Write(encodedEntry) } func (core *Core) Sync() error { diff --git a/logs/zap/entry_encoder.go b/logs/zap/entry_encoder.go index 0f5692852f59fbb620b476d982f61617dd476294..8fb66f37a2e2fc80124a3ade0f853fd386e9dd13 100644 --- a/logs/zap/entry_encoder.go +++ b/logs/zap/entry_encoder.go @@ -2,45 +2,37 @@ package zap import ( "fmt" - "maps" + "slices" oid "git.perx.ru/perxis/perxis-go/id" "git.perx.ru/perxis/perxis-go/logs" "git.perx.ru/perxis/perxis-go/pkg/id" + "git.perx.ru/perxis/perxis-go/zap" "go.uber.org/zap/zapcore" ) type Encoder interface { - zapcore.ObjectEncoder - - Clone() Encoder + With([]zapcore.Field) Encoder EncodeEntry(zapcore.Entry, []zapcore.Field) (*logs.Entry, error) } type entryEncoder struct { - *zapcore.MapObjectEncoder + fields []zapcore.Field } func NewEntryEncoder() Encoder { - return &entryEncoder{MapObjectEncoder: zapcore.NewMapObjectEncoder()} + return &entryEncoder{} } -func (enc *entryEncoder) Clone() Encoder { - return enc.clone() +func (enc *entryEncoder) With(fields []zapcore.Field) Encoder { + return enc.with(fields) } -func (enc *entryEncoder) clone() *entryEncoder { - objEnc := zapcore.NewMapObjectEncoder() - maps.Copy(objEnc.Fields, enc.MapObjectEncoder.Fields) - return &entryEncoder{MapObjectEncoder: objEnc} +func (enc *entryEncoder) with(fields []zapcore.Field) *entryEncoder { + return &entryEncoder{fields: slices.Concat(enc.fields, fields)} } func (enc *entryEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*logs.Entry, error) { - clone := enc.clone() - for i := range fields { - fields[i].AddTo(clone) - } - ent := &logs.Entry{ ID: id.GenerateNewID(), Timestamp: entry.Time, @@ -48,22 +40,31 @@ func (enc *entryEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field Message: entry.Message, } - ent.Category, _ = clone.Fields["category"].(string) - ent.Component, _ = clone.Fields["component"].(string) - ent.Event, _ = clone.Fields["event"].(string) - ent.ObjectID, _ = clone.Fields["object"].(*oid.ObjectId) - ent.CallerID, _ = clone.Fields["caller"].(*oid.ObjectId) - ent.Attr = clone.Fields["attr"] - - if err, _ := clone.Fields["error"].(error); err != nil { - ent.Message = fmt.Sprintf("%s. Error: %s", ent.Message, err.Error()) - } + clone := enc.with(fields) - if tags, ok := clone.Fields["tags"].([]any); ok { - for i := range tags { - if tag, ok := tags[i].(string); ok { - ent.Tags = append(ent.Tags, tag) + for i := range clone.fields { + switch clone.fields[i].Key { + // При добавлении новых полей стоит учитывать, как zap хранит значения в структуре Field. + // Например: + // zap.Bool хранит bool как 1/0 в поле Field.Integer + case "category": + ent.Category = clone.fields[i].String + case "component": + ent.Component = clone.fields[i].String + case "event": + ent.Event = clone.fields[i].String + case "object": + ent.ObjectID, _ = clone.fields[i].Interface.(*oid.ObjectId) + case "caller": + ent.CallerID, _ = clone.fields[i].Interface.(*oid.ObjectId) + case "attr": + ent.Attr = clone.fields[i].Interface + case "error": + if err, _ := clone.fields[i].Interface.(error); err != nil { + ent.Message = fmt.Sprintf("%s. Error: %s", ent.Message, err.Error()) } + case "tags": + ent.Tags, _ = clone.fields[i].Interface.(zap.StringArray) } } diff --git a/logs/zap/entry_encoder_slow.go b/logs/zap/entry_encoder_slow.go new file mode 100644 index 0000000000000000000000000000000000000000..303b5c4c80f16ebe02eab7cf2b6f9731f33b9ec5 --- /dev/null +++ b/logs/zap/entry_encoder_slow.go @@ -0,0 +1,71 @@ +package zap + +import ( + "fmt" + "maps" + + oid "git.perx.ru/perxis/perxis-go/id" + "git.perx.ru/perxis/perxis-go/logs" + "git.perx.ru/perxis/perxis-go/pkg/id" + "go.uber.org/zap/zapcore" +) + +type EncoderSlow interface { + zapcore.ObjectEncoder + + Clone() EncoderSlow + EncodeEntry(zapcore.Entry, []zapcore.Field) (*logs.Entry, error) +} + +type entryEncoderSlow struct { + *zapcore.MapObjectEncoder +} + +func NewEntryEncoderSlow() EncoderSlow { + return &entryEncoderSlow{MapObjectEncoder: zapcore.NewMapObjectEncoder()} +} + +func (enc *entryEncoderSlow) Clone() EncoderSlow { + return enc.clone() +} + +func (enc *entryEncoderSlow) clone() *entryEncoderSlow { + objEnc := zapcore.NewMapObjectEncoder() + maps.Copy(objEnc.Fields, enc.MapObjectEncoder.Fields) + return &entryEncoderSlow{MapObjectEncoder: objEnc} +} + +func (enc *entryEncoderSlow) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*logs.Entry, error) { + clone := enc.clone() + for i := range fields { + fields[i].AddTo(clone) + } + + ent := &logs.Entry{ + ID: id.GenerateNewID(), + Timestamp: entry.Time, + LogLevel: logs.Level(entry.Level), + Message: entry.Message, + } + + ent.Category, _ = clone.Fields["category"].(string) + ent.Component, _ = clone.Fields["component"].(string) + ent.Event, _ = clone.Fields["event"].(string) + ent.ObjectID, _ = clone.Fields["object"].(*oid.ObjectId) + ent.CallerID, _ = clone.Fields["caller"].(*oid.ObjectId) + ent.Attr = clone.Fields["attr"] + + if err, _ := clone.Fields["error"].(error); err != nil { + ent.Message = fmt.Sprintf("%s. Error: %s", ent.Message, err.Error()) + } + + if tags, ok := clone.Fields["tags"].([]any); ok { + for i := range tags { + if tag, ok := tags[i].(string); ok { + ent.Tags = append(ent.Tags, tag) + } + } + } + + return ent, nil +} diff --git a/logs/zap/entry_encoder_test.go b/logs/zap/entry_encoder_test.go index 052b13f7f85fc2f2a2848427767d3445077ac3b1..ee21e0fe63c78c1b94de26ac1b3b47cd859e8bc8 100644 --- a/logs/zap/entry_encoder_test.go +++ b/logs/zap/entry_encoder_test.go @@ -4,12 +4,68 @@ import ( "fmt" "testing" + "git.perx.ru/perxis/perxis-go/id" + "git.perx.ru/perxis/perxis-go/logs" "git.perx.ru/perxis/perxis-go/pkg/items" logzap "git.perx.ru/perxis/perxis-go/zap" + "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) +func TestEntryEncoder_EncodeEntry(t *testing.T) { + tests := []struct { + name string + input struct { + entry zapcore.Entry + fields []zapcore.Field + } + want *logs.Entry + }{ + { + name: "simple", + input: struct { + entry zapcore.Entry + fields []zapcore.Field + }{ + entry: zapcore.Entry{Level: zapcore.InfoLevel, Message: "создан элемент коллекции"}, + fields: []zapcore.Field{ + zap.String("key", "val"), // будет проигнорировано + logzap.Category("create"), + logzap.Component("Items.Service"), + logzap.Event("Items.Create"), + logzap.Object("/spaces/WPNN/envs/9VGP/cols/GxNv/items/W0fl"), + logzap.Caller("/users/PHVz"), + logzap.Attr("any"), + logzap.Tags("tag1", "tag2", "tag3"), + }, + }, + want: &logs.Entry{ + LogLevel: logs.Level(zapcore.InfoLevel), + Message: "создан элемент коллекции", + Category: "create", + Component: "Items.Service", + Event: "Items.Create", + ObjectID: id.MustObjectId("/spaces/WPNN/envs/9VGP/cols/GxNv/items/W0fl"), + CallerID: id.MustObjectId("/users/PHVz"), + Attr: "any", + Tags: []string{"tag1", "tag2", "tag3"}, + }, + }, + } + + enc := NewEntryEncoder() + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, _ := enc.EncodeEntry(tc.input.entry, tc.input.fields) + got.ID = tc.want.ID // игнорируем ID + got.Timestamp = tc.want.Timestamp // игнорируем Timestamp + require.Equal(t, tc.want, got) + }) + } +} + func BenchmarkEntryEncoderSimple(b *testing.B) { fields := []zapcore.Field{ logzap.Event(items.EventCreateItem), @@ -18,7 +74,7 @@ func BenchmarkEntryEncoderSimple(b *testing.B) { logzap.Tags("tag1", "tag2", "tag3"), } - enc := NewEntryEncoder() + enc := NewEntryEncoderSlow() for i := 0; i < b.N; i++ { _, _ = enc.EncodeEntry(zapcore.Entry{Message: fmt.Sprintf("Msg: %d", i)}, fields) } @@ -36,7 +92,7 @@ func BenchmarkEntryEncoderUnknownFields(b *testing.B) { fields = append(fields, zap.String(fmt.Sprintf("Key: %d", i), fmt.Sprintf("Value: %d", i))) } - enc := NewEntryEncoder() + enc := NewEntryEncoderSlow() for i := 0; i < b.N; i++ { _, _ = enc.EncodeEntry(zapcore.Entry{Message: fmt.Sprintf("Msg: %d", i)}, fields) } @@ -50,7 +106,7 @@ func BenchmarkEntryEncoderV2Simple(b *testing.B) { logzap.Tags("tag1", "tag2", "tag3"), } - enc := NewEntryEncoderV2() + enc := NewEntryEncoder() for i := 0; i < b.N; i++ { _, _ = enc.EncodeEntry(zapcore.Entry{Message: fmt.Sprintf("Msg: %d", i)}, fields) } @@ -68,7 +124,7 @@ func BenchmarkEntryEncoderV2UnknownFields(b *testing.B) { fields = append(fields, zap.String(fmt.Sprintf("Key: %d", i), fmt.Sprintf("Value: %d", i))) } - enc := NewEntryEncoderV2() + enc := NewEntryEncoder() for i := 0; i < b.N; i++ { _, _ = enc.EncodeEntry(zapcore.Entry{Message: fmt.Sprintf("Msg: %d", i)}, fields) } diff --git a/logs/zap/entry_encoder_v2.go b/logs/zap/entry_encoder_v2.go deleted file mode 100644 index a73be0930a2a082e4bcc27f9218850e6d3e9395c..0000000000000000000000000000000000000000 --- a/logs/zap/entry_encoder_v2.go +++ /dev/null @@ -1,69 +0,0 @@ -package zap - -import ( - "fmt" - "slices" - - oid "git.perx.ru/perxis/perxis-go/id" - "git.perx.ru/perxis/perxis-go/logs" - "git.perx.ru/perxis/perxis-go/pkg/id" - "git.perx.ru/perxis/perxis-go/zap" - "go.uber.org/zap/zapcore" -) - -type EncoderV2 interface { - With([]zapcore.Field) EncoderV2 - EncodeEntry(zapcore.Entry, []zapcore.Field) (*logs.Entry, error) -} - -type entryEncoderV2 struct { - fields []zapcore.Field -} - -func NewEntryEncoderV2() EncoderV2 { - return &entryEncoderV2{} -} - -func (enc *entryEncoderV2) With(fields []zapcore.Field) EncoderV2 { - return enc.with(fields) -} - -func (enc *entryEncoderV2) with(fields []zapcore.Field) *entryEncoderV2 { - return &entryEncoderV2{fields: slices.Concat(enc.fields, fields)} -} - -func (enc *entryEncoderV2) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*logs.Entry, error) { - ent := &logs.Entry{ - ID: id.GenerateNewID(), - Timestamp: entry.Time, - LogLevel: logs.Level(entry.Level), - Message: entry.Message, - } - - clone := enc.with(fields) - - for i := range clone.fields { - switch clone.fields[i].Key { - case "category": - ent.Category = clone.fields[i].String - case "component": - ent.Component = clone.fields[i].String - case "event": - ent.Event = clone.fields[i].String - case "object": - ent.ObjectID, _ = clone.fields[i].Interface.(*oid.ObjectId) - case "caller": - ent.CallerID, _ = clone.fields[i].Interface.(*oid.ObjectId) - case "attr": - ent.Attr = clone.fields[i].Interface - case "error": - if err, _ := clone.fields[i].Interface.(error); err != nil { - ent.Message = fmt.Sprintf("%s. Error: %s", ent.Message, err.Error()) - } - case "tags": - ent.Tags, _ = clone.fields[i].Interface.(zap.StringArray) - } - } - - return ent, nil -} diff --git a/zap/field.go b/zap/field.go index bccee93496f88404f2ca90332fcf3f1b89909104..f967504c6c9e797fd75eb7185fa718609e82151b 100644 --- a/zap/field.go +++ b/zap/field.go @@ -60,7 +60,7 @@ func CallerFromContext(ctx context.Context) zap.Field { } func Attr(attr any) zap.Field { - return zap.Any("attr", attr) + return zap.Reflect("attr", attr) } func Tags(tags ...string) zap.Field {