mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-01 14:47:38 +00:00
17c8beca18
This simplifies logs' investigation, since it allows copying some text from the previous query output
116 lines
2.4 KiB
Go
116 lines
2.4 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/mattn/go-isatty"
|
|
)
|
|
|
|
func isTerminal() bool {
|
|
return isatty.IsTerminal(os.Stdout.Fd()) && isatty.IsTerminal(os.Stderr.Fd())
|
|
}
|
|
|
|
func readWithLess(r io.Reader) error {
|
|
if !isTerminal() {
|
|
// Just write everything to stdout if no terminal is available.
|
|
_, err := io.Copy(os.Stdout, r)
|
|
if err != nil && !isErrPipe(err) {
|
|
return fmt.Errorf("error when forwarding data to stdout: %w", err)
|
|
}
|
|
if err := os.Stdout.Sync(); err != nil {
|
|
return fmt.Errorf("cannot sync data to stdout: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
pr, pw, err := os.Pipe()
|
|
if err != nil {
|
|
return fmt.Errorf("cannot create pipe: %w", err)
|
|
}
|
|
defer func() {
|
|
_ = pr.Close()
|
|
_ = pw.Close()
|
|
}()
|
|
|
|
// Ignore Ctrl+C in the current process, so 'less' could handle it properly
|
|
cancel := ignoreSignals(os.Interrupt)
|
|
defer cancel()
|
|
|
|
// Start 'less' process
|
|
path, err := exec.LookPath("less")
|
|
if err != nil {
|
|
return fmt.Errorf("cannot find 'less' command: %w", err)
|
|
}
|
|
p, err := os.StartProcess(path, []string{"less", "-F", "-X"}, &os.ProcAttr{
|
|
Env: append(os.Environ(), "LESSCHARSET=utf-8"),
|
|
Files: []*os.File{pr, os.Stdout, os.Stderr},
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("cannot start 'less' process: %w", err)
|
|
}
|
|
|
|
// Close pr after 'less' finishes in a parallel goroutine
|
|
// in order to unblock forwarding data to stopped 'less' below.
|
|
waitch := make(chan *os.ProcessState)
|
|
go func() {
|
|
// Wait for 'less' process to finish.
|
|
ps, err := p.Wait()
|
|
if err != nil {
|
|
fatalf("unexpected error when waiting for 'less' process: %w", err)
|
|
}
|
|
_ = pr.Close()
|
|
waitch <- ps
|
|
}()
|
|
|
|
// Forward data from r to 'less'
|
|
_, err = io.Copy(pw, r)
|
|
_ = pw.Sync()
|
|
_ = pw.Close()
|
|
|
|
// Wait until 'less' finished
|
|
ps := <-waitch
|
|
|
|
// Verify 'less' status.
|
|
if !ps.Success() {
|
|
return fmt.Errorf("'less' finished with unexpected code %d", ps.ExitCode())
|
|
}
|
|
|
|
if err != nil && !isErrPipe(err) {
|
|
return fmt.Errorf("error when forwarding data to 'less': %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func isErrPipe(err error) bool {
|
|
return errors.Is(err, syscall.EPIPE) || errors.Is(err, io.ErrClosedPipe)
|
|
}
|
|
|
|
func ignoreSignals(sigs ...os.Signal) func() {
|
|
ch := make(chan os.Signal, 1)
|
|
signal.Notify(ch, sigs...)
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for {
|
|
_, ok := <-ch
|
|
if !ok {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
return func() {
|
|
signal.Stop(ch)
|
|
close(ch)
|
|
wg.Wait()
|
|
}
|
|
}
|