diff --git a/app/vlogscli/less_wrapper.go b/app/vlogscli/less_wrapper.go index d7a0d5637..cecb25149 100644 --- a/app/vlogscli/less_wrapper.go +++ b/app/vlogscli/less_wrapper.go @@ -17,7 +17,7 @@ func isTerminal() bool { return isatty.IsTerminal(os.Stdout.Fd()) && isatty.IsTerminal(os.Stderr.Fd()) } -func readWithLess(r io.Reader) error { +func readWithLess(r io.Reader, wrapLongLines bool) error { if !isTerminal() { // Just write everything to stdout if no terminal is available. _, err := io.Copy(os.Stdout, r) @@ -48,7 +48,11 @@ func readWithLess(r io.Reader) error { if err != nil { return fmt.Errorf("cannot find 'less' command: %w", err) } - p, err := os.StartProcess(path, []string{"less", "-F", "-X"}, &os.ProcAttr{ + opts := []string{"less", "-F", "-X"} + if !wrapLongLines { + opts = append(opts, "-S") + } + p, err := os.StartProcess(path, opts, &os.ProcAttr{ Env: append(os.Environ(), "LESSCHARSET=utf-8"), Files: []*os.File{pr, os.Stdout, os.Stderr}, }) diff --git a/app/vlogscli/main.go b/app/vlogscli/main.go index 8799ab199..febb1cb27 100644 --- a/app/vlogscli/main.go +++ b/app/vlogscli/main.go @@ -91,6 +91,7 @@ func runReadlineLoop(rl *readline.Instance, incompleteLine *string) { } outputMode := outputModeJSONMultiline + wrapLongLines := false s := "" for { line, err := rl.ReadLine() @@ -99,7 +100,7 @@ func runReadlineLoop(rl *readline.Instance, incompleteLine *string) { case io.EOF: if s != "" { // This is non-interactive query execution. - executeQuery(context.Background(), rl, s, outputMode) + executeQuery(context.Background(), rl, s, outputMode, wrapLongLines) } return case readline.ErrInterrupt: @@ -163,6 +164,18 @@ func runReadlineLoop(rl *readline.Instance, incompleteLine *string) { s = "" continue } + if s == `\wrap_long_lines` { + if wrapLongLines { + wrapLongLines = false + fmt.Fprintf(rl, "wrapping of long lines is disabled\n") + } else { + wrapLongLines = true + fmt.Fprintf(rl, "wrapping of long lines is enabled\n") + } + historyLines = pushToHistory(rl, historyLines, s) + s = "" + continue + } if line != "" && !strings.HasSuffix(line, ";") { // Assume the query is incomplete and allow the user finishing the query on the next line s += "\n" @@ -172,7 +185,7 @@ func runReadlineLoop(rl *readline.Instance, incompleteLine *string) { // Execute the query ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) - executeQuery(ctx, rl, s, outputMode) + executeQuery(ctx, rl, s, outputMode, wrapLongLines) cancel() historyLines = pushToHistory(rl, historyLines, s) @@ -259,13 +272,14 @@ func printCommandsHelp(w io.Writer) { \m - multiline json output mode \c - compact output \logfmt - logfmt output mode +\wrap_long_lines - toggles wrapping long lines \tail - live tail results See https://docs.victoriametrics.com/victorialogs/querying/vlogscli/ for more details `) } -func executeQuery(ctx context.Context, output io.Writer, qStr string, outputMode outputMode) { +func executeQuery(ctx context.Context, output io.Writer, qStr string, outputMode outputMode, wrapLongLines bool) { if strings.HasPrefix(qStr, `\tail `) { tailQuery(ctx, output, qStr, outputMode) return @@ -279,7 +293,7 @@ func executeQuery(ctx context.Context, output io.Writer, qStr string, outputMode _ = respBody.Close() }() - if err := readWithLess(respBody); err != nil { + if err := readWithLess(respBody, wrapLongLines); err != nil { fmt.Fprintf(output, "error when reading query response: %s\n", err) return } diff --git a/docs/VictoriaLogs/CHANGELOG.md b/docs/VictoriaLogs/CHANGELOG.md index 2c5074877..c96e5f1b0 100644 --- a/docs/VictoriaLogs/CHANGELOG.md +++ b/docs/VictoriaLogs/CHANGELOG.md @@ -18,6 +18,7 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta * FEATURE: [`join` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#join-pipe): add an ability to add prefix to all the log field names from the joined query, by using `| join by () () prefix "some_prefix"` syntax. * FEATURE: [`_time` filter](https://docs.victoriametrics.com/victorialogs/logsql/#time-filter): allow specifying offset without time range. For example, `_time:offset 1d` matches all the logs until `now-1d` in the [`_time` field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#time-field). This is useful when building graphs for time ranges with some offset in the past. * FEATURE: [`/select/logsql/tail` HTTP endpoint](): support for `offset` query arg, which can be used for delayed emission of matching logs during live tailing. Thanks to @Fusl for the initial idea and implementation in [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7428). +* FEATURE: [vlogscli](https://docs.victoriametrics.com/victorialogs/querying/vlogscli/): allow enabling and disabling wrapping of long lines, which do not fit screen width, with `\wrap_long_lines` command. * BUGFIX: [HTTP querying APIs](https://docs.victoriametrics.com/victorialogs/querying/#http-api): properly take into account the `end` query arg when calculating time range for [`_time:duration` filter](https://docs.victoriametrics.com/victorialogs/logsql/#time-filter). Previously the `_time:duration` filter was treated as `_time:[now-duration, now)`, while it should be treated as `_time:[end-duration, end)`.