From 596e4de248043f3b03017f7c5f5a06454ceab11a Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin Date: Sat, 5 Oct 2024 21:25:43 +0200 Subject: [PATCH] app/vlogscli: preserve the original order of fields in the displayed responses --- app/vlogscli/json_prettifier.go | 96 ++++++++++++++++++++++++++++++--- docs/VictoriaLogs/CHANGELOG.md | 2 + 2 files changed, 91 insertions(+), 7 deletions(-) diff --git a/app/vlogscli/json_prettifier.go b/app/vlogscli/json_prettifier.go index f11917919..bbaddf3bc 100644 --- a/app/vlogscli/json_prettifier.go +++ b/app/vlogscli/json_prettifier.go @@ -46,15 +46,11 @@ func (jp *jsonPrettifier) closePipesWithError(err error) { func (jp *jsonPrettifier) prettifyJSONLines() error { for jp.d.More() { - var v any - if err := jp.d.Decode(&v); err != nil { + kvs, err := readNextJSONObject(jp.d) + if err != nil { return err } - line, err := json.MarshalIndent(v, "", " ") - if err != nil { - panic(fmt.Errorf("BUG: cannot marshal %v to JSON: %w", v, err)) - } - if _, err := fmt.Fprintf(jp.pw, "%s\n", line); err != nil { + if err := writeJSONObject(jp.pw, kvs); err != nil { return err } } @@ -71,3 +67,89 @@ func (jp *jsonPrettifier) Close() error { func (jp *jsonPrettifier) Read(p []byte) (int, error) { return jp.pr.Read(p) } + +func readNextJSONObject(d *json.Decoder) ([]kv, error) { + t, err := d.Token() + if err != nil { + return nil, fmt.Errorf("cannot read '{': %w", err) + } + delim, ok := t.(json.Delim) + if !ok || delim.String() != "{" { + return nil, fmt.Errorf("unexpected token read; got %q; want '{'", delim) + } + + var kvs []kv + for { + // Read object key + t, err := d.Token() + if err != nil { + return nil, fmt.Errorf("cannot read JSON object key or closing brace: %w", err) + } + delim, ok := t.(json.Delim) + if ok { + if delim.String() == "}" { + return kvs, nil + } + return nil, fmt.Errorf("unexpected delimiter read; got %q; want '}'", delim) + } + key, ok := t.(string) + if !ok { + return nil, fmt.Errorf("unexpected token read for object key: %v; want string or '}'", t) + } + + // read object value + t, err = d.Token() + if err != nil { + return nil, fmt.Errorf("cannot read JSON object value: %w", err) + } + value, ok := t.(string) + if !ok { + return nil, fmt.Errorf("unexpected token read for oject value: %v; want string", t) + } + + kvs = append(kvs, kv{ + key: key, + value: value, + }) + } +} + +func writeJSONObject(w io.Writer, kvs []kv) error { + if len(kvs) == 0 { + fmt.Fprintf(w, "{}\n") + return nil + } + + fmt.Fprintf(w, "{\n") + if err := writeJSONObjectKeyValue(w, kvs[0]); err != nil { + return err + } + for _, kv := range kvs[1:] { + fmt.Fprintf(w, ",\n") + if err := writeJSONObjectKeyValue(w, kv); err != nil { + return err + } + } + fmt.Fprintf(w, "\n}\n") + return nil +} + +func writeJSONObjectKeyValue(w io.Writer, kv kv) error { + key := getJSONString(kv.key) + value := getJSONString(kv.value) + _, err := fmt.Fprintf(w, " %s: %s", key, value) + return err +} + +func getJSONString(s string) string { + data, err := json.Marshal(s) + if err != nil { + panic(fmt.Errorf("unexpected error when marshaling string to JSON: %w", err)) + } + return string(data) +} + +type kv struct { + key string + value string +} diff --git a/docs/VictoriaLogs/CHANGELOG.md b/docs/VictoriaLogs/CHANGELOG.md index e1a8b80df..aa5bbafdc 100644 --- a/docs/VictoriaLogs/CHANGELOG.md +++ b/docs/VictoriaLogs/CHANGELOG.md @@ -18,6 +18,8 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta * FEATURE: [vlogscli](https://docs.victoriametrics.com/victorialogs/querying/vlogscli/): preserve `less` output after the exit from scrolling mode. This should help re-using previous query results in subsequent queries. * FEATURE: add [`len` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#len-pipe) for calculating the length for the given [log field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) value in bytes. +* BUGFIX: [vlogscli](https://docs.victoriametrics.com/victorialogs/querying/vlogscli/): preserve the original order of fields in the displayed query responses. Previously fields were sorted by name. + ## [v0.33.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v0.33.0-victorialogs) Released at 2024-10-01