diff --git a/console.go b/console.go index cc6d623e..61fa9529 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]int + // FieldsExclude defines contextual fields to not display in output. FieldsExclude []string @@ -185,7 +190,12 @@ func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer } fields = append(fields, field) } - sort.Strings(fields) + + if len(w.FieldsOrder) > 0 { + w.orderFields(fields) + } else { + sort.Strings(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 { @@ -318,6 +328,32 @@ 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) { + if w.fieldIsOrdered == nil { + w.fieldIsOrdered = make(map[string]int) + for i, fieldName := range w.FieldsOrder { + w.fieldIsOrdered[fieldName] = i + } + } + 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 + } + if iOrdered { + return true + } + if jOrdered { + return false + } + return fields[i] < fields[j] + }) +} + // 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"}}