diff --git a/core/logx/writer.go b/core/logx/writer.go index ac19aa245..4395d5a63 100644 --- a/core/logx/writer.go +++ b/core/logx/writer.go @@ -254,11 +254,10 @@ func (n nopWriter) Stack(_ any) { func (n nopWriter) Stat(_ any, _ ...LogField) { } -func buildPlainFields(fields ...LogField) []string { - var items []string - - for _, field := range fields { - items = append(items, fmt.Sprintf("%s=%+v", field.Key, field.Value)) +func buildPlainFields(fields logEntry) []string { + items := make([]string, 0, len(fields)) + for k, v := range fields { + items = append(items, fmt.Sprintf("%s=%+v", k, v)) } return items @@ -289,15 +288,17 @@ func output(writer io.Writer, level string, val any, fields ...LogField) { } fields = combineGlobalFields(fields) + // +3 for timestamp, level and content + entry := make(logEntry, len(fields)+3) + for _, field := range fields { + entry[field.Key] = field.Value + } switch atomic.LoadUint32(&encoding) { case plainEncodingType: - writePlainAny(writer, level, val, buildPlainFields(fields...)...) + plainFields := buildPlainFields(entry) + writePlainAny(writer, level, val, plainFields...) default: - entry := make(logEntry) - for _, field := range fields { - entry[field.Key] = field.Value - } entry[timestampKey] = getTimestamp() entry[levelKey] = level entry[contentKey] = val diff --git a/core/logx/writer_test.go b/core/logx/writer_test.go index 0f810975f..6ac98bc1d 100644 --- a/core/logx/writer_test.go +++ b/core/logx/writer_test.go @@ -189,6 +189,41 @@ func TestWritePlainAny(t *testing.T) { assert.Contains(t, buf.String(), "runtime/debug.Stack") } +func TestWritePlainDuplicate(t *testing.T) { + old := atomic.SwapUint32(&encoding, plainEncodingType) + t.Cleanup(func() { + atomic.StoreUint32(&encoding, old) + }) + + var buf bytes.Buffer + output(&buf, levelInfo, "foo", LogField{ + Key: "first", + Value: "a", + }, LogField{ + Key: "first", + Value: "b", + }) + assert.Contains(t, buf.String(), "foo") + assert.NotContains(t, buf.String(), "first=a") + assert.Contains(t, buf.String(), "first=b") + + buf.Reset() + output(&buf, levelInfo, "foo", LogField{ + Key: "first", + Value: "a", + }, LogField{ + Key: "first", + Value: "b", + }, LogField{ + Key: "second", + Value: "c", + }) + assert.Contains(t, buf.String(), "foo") + assert.NotContains(t, buf.String(), "first=a") + assert.Contains(t, buf.String(), "first=b") + assert.Contains(t, buf.String(), "second=c") +} + func TestLogWithLimitContentLength(t *testing.T) { maxLen := atomic.LoadUint32(&maxContentLength) atomic.StoreUint32(&maxContentLength, 10)