From b32f95ae75bc918fa66ff1d4d855c01ec8be59e9 Mon Sep 17 00:00:00 2001 From: mAdkins Date: Sat, 2 Mar 2024 09:50:25 -0800 Subject: [PATCH 1/3] Add FieldsOrder functionality --- console.go | 38 ++++++++++++++++++++++++++++++++++++++ console_test.go | 17 +++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/console.go b/console.go index cc6d623e..423d6879 100644 --- a/console.go +++ b/console.go @@ -63,6 +63,11 @@ type ConsoleWriter struct { // PartsExclude defines parts to not display in output. PartsExclude []string + // FieldsOrder defines the order of contextual fields in output. + FieldsOrder []string + + fieldIsOrdered map[string]bool + // FieldsExclude defines contextual fields to not display in output. FieldsExclude []string @@ -183,10 +188,15 @@ func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer case LevelFieldName, TimestampFieldName, MessageFieldName, CallerFieldName: continue } + fields = append(fields, field) } sort.Strings(fields) + if len(w.FieldsOrder) > 0 { + fields = w.orderFields(fields) + } + // Write space only if something has already been written to the buffer, and if there are fields. if buf.Len() > 0 && len(fields) > 0 { buf.WriteByte(' ') @@ -318,6 +328,34 @@ func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, } } +// orderFields takes an array of field names and an array representing field order +// and returns an array with any ordered fields at the beginning, in order, +// and the remaining fields after in their original order. +func (w ConsoleWriter) orderFields(fields []string) []string { + if w.fieldIsOrdered == nil { + w.fieldIsOrdered = make(map[string]bool) + for _, fieldName := range w.FieldsOrder { + w.fieldIsOrdered[fieldName] = true + } + } + hasOrderedField := make(map[string]bool) + otherFields := make([]string, 0, len(fields)) + for _, fieldName := range fields { + if w.fieldIsOrdered[fieldName] { + hasOrderedField[fieldName] = true + } else { + otherFields = append(otherFields, fieldName) + } + } + result := make([]string, 0, len(fields)) + for _, fieldName := range w.FieldsOrder { + if hasOrderedField[fieldName] { + result = append(result, fieldName) + } + } + return append(result, otherFields...) +} + // needsQuote returns true when the string s should be quoted in output. func needsQuote(s string) bool { for i := range s { diff --git a/console_test.go b/console_test.go index acf1ccbf..6945ffe9 100644 --- a/console_test.go +++ b/console_test.go @@ -378,6 +378,23 @@ func TestConsoleWriterConfiguration(t *testing.T) { } }) + t.Run("Sets FieldsOrder", func(t *testing.T) { + buf := &bytes.Buffer{} + w := zerolog.ConsoleWriter{Out: buf, NoColor: true, FieldsOrder: []string{"zebra", "aardvark"}} + + evt := `{"level": "info", "message": "Zoo", "aardvark": "Able", "mussel": "Mountain", "zebra": "Zulu"}` + _, err := w.Write([]byte(evt)) + if err != nil { + t.Errorf("Unexpected error when writing output: %s", err) + } + + expectedOutput := " INF Zoo zebra=Zulu aardvark=Able mussel=Mountain\n" + actualOutput := buf.String() + if actualOutput != expectedOutput { + t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput) + } + }) + t.Run("Sets FieldsExclude", func(t *testing.T) { buf := &bytes.Buffer{} w := zerolog.ConsoleWriter{Out: buf, NoColor: true, FieldsExclude: []string{"foo"}} From 217f4174963d978b87223f4bd7096511afe1f64d Mon Sep 17 00:00:00 2001 From: mAdkins Date: Sat, 2 Mar 2024 09:52:33 -0800 Subject: [PATCH 2/3] Remove redundant blank line --- console.go | 1 - 1 file changed, 1 deletion(-) diff --git a/console.go b/console.go index 423d6879..9fc644a6 100644 --- a/console.go +++ b/console.go @@ -188,7 +188,6 @@ func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer case LevelFieldName, TimestampFieldName, MessageFieldName, CallerFieldName: continue } - fields = append(fields, field) } sort.Strings(fields) From e10beda56f3b6e6834f7bef4c25e74d9f6a5a0b5 Mon Sep 17 00:00:00 2001 From: mAdkins Date: Sat, 2 Mar 2024 16:53:49 -0800 Subject: [PATCH 3/3] Fixes for performance per PR comments --- console.go | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/console.go b/console.go index 9fc644a6..61fa9529 100644 --- a/console.go +++ b/console.go @@ -66,7 +66,7 @@ type ConsoleWriter struct { // FieldsOrder defines the order of contextual fields in output. FieldsOrder []string - fieldIsOrdered map[string]bool + fieldIsOrdered map[string]int // FieldsExclude defines contextual fields to not display in output. FieldsExclude []string @@ -190,10 +190,11 @@ func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer } fields = append(fields, field) } - sort.Strings(fields) if len(w.FieldsOrder) > 0 { - fields = w.orderFields(fields) + w.orderFields(fields) + } else { + sort.Strings(fields) } // Write space only if something has already been written to the buffer, and if there are fields. @@ -330,29 +331,27 @@ func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, // orderFields takes an array of field names and an array representing field order // and returns an array with any ordered fields at the beginning, in order, // and the remaining fields after in their original order. -func (w ConsoleWriter) orderFields(fields []string) []string { +func (w ConsoleWriter) orderFields(fields []string) { if w.fieldIsOrdered == nil { - w.fieldIsOrdered = make(map[string]bool) - for _, fieldName := range w.FieldsOrder { - w.fieldIsOrdered[fieldName] = true + w.fieldIsOrdered = make(map[string]int) + for i, fieldName := range w.FieldsOrder { + w.fieldIsOrdered[fieldName] = i } } - hasOrderedField := make(map[string]bool) - otherFields := make([]string, 0, len(fields)) - for _, fieldName := range fields { - if w.fieldIsOrdered[fieldName] { - hasOrderedField[fieldName] = true - } else { - otherFields = append(otherFields, fieldName) + sort.Slice(fields, func(i, j int) bool { + ii, iOrdered := w.fieldIsOrdered[fields[i]] + jj, jOrdered := w.fieldIsOrdered[fields[j]] + if iOrdered && jOrdered { + return ii < jj } - } - result := make([]string, 0, len(fields)) - for _, fieldName := range w.FieldsOrder { - if hasOrderedField[fieldName] { - result = append(result, fieldName) + if iOrdered { + return true } - } - return append(result, otherFields...) + if jOrdered { + return false + } + return fields[i] < fields[j] + }) } // needsQuote returns true when the string s should be quoted in output.