grim/convey

Port from logrus to log/slog
default tip
5 months ago, Elliott Sales de Andrade
c588f9b3f559
Port from logrus to log/slog

This doesn't really take much advantage of structured logging beyond what is already done (`id` and `idColor`), and consequently the log handler does not try to do any handling of anything more than that (i.e., grouping, or arbitrary attributes beyond those defined).

One should maybe have a `Context` available to pass in, but there isn't one, and anyway, the log handler doesn't use it, so I've passed in a `TODO` instead.

Everything else is just normal import/rename changes.

Testing Done:
Ran `go run . run`

Reviewed at https://reviews.imfreedom.org/r/2871/
// Convey
// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package exec provides utilities for executing external commands.
package exec
import (
"bufio"
"fmt"
"io"
"log/slog"
"os/exec"
"sync"
"time"
"keep.imfreedom.org/grim/convey/logging"
)
// Run runs the command specified in argv and logs stderr and stdout to a new
// log adapter.
func Run(name string, cmdv []string, timeout time.Duration) error {
var (
logger = logging.NewAdapter(name)
outCollector = newLogCollector(logger, slog.LevelInfo)
errCollector = newLogCollector(logger, slog.LevelError)
)
return run(name, cmdv, nil, timeout, &sync.WaitGroup{}, outCollector, errCollector)
}
// RunWithStdin runs the command specified in argv and passes stdin to the
// process's stdin. stderr and stdout are logged to a new log adapter.
func RunWithStdin(name string, cmdv []string, stdin io.Reader, timeout time.Duration) error {
var (
logger = logging.NewAdapter(name)
outCollector = newLogCollector(logger, slog.LevelInfo)
errCollector = newLogCollector(logger, slog.LevelError)
)
return run(name, cmdv, stdin, timeout, &sync.WaitGroup{}, outCollector, errCollector)
}
// RunOutput works just like Run but returns stdout and stderr instead of
// logging it.
func RunOutput(name string, cmdv []string, timeout time.Duration) (string, string, error) {
var (
wg = &sync.WaitGroup{}
outCollector = newStringCollector(wg)
errCollector = newStringCollector(wg)
)
err := run(name, cmdv, nil, timeout, wg, outCollector.handler, errCollector.handler)
wg.Wait()
return outCollector.output, errCollector.output, err
}
func run(name string, cmdv []string, stdin io.Reader, timeout time.Duration, wg *sync.WaitGroup, outHandler, errHandler collector) error {
logger := logging.NewAdapter(name)
logger.Debug(fmt.Sprintf("running command \"%v\"", cmdv))
cmd := exec.Command(cmdv[0], cmdv[1:]...) // nolint: gas
// setup the stdout wrapper
outReader, err := cmd.StdoutPipe()
if err != nil {
return err
}
// setup the stderr wrapper
errReader, err := cmd.StderrPipe()
if err != nil {
return err
}
if stdin != nil {
stdinPipe, err := cmd.StdinPipe()
if err != nil {
return err
}
go func() {
defer stdinPipe.Close()
io.Copy(stdinPipe, stdin)
}()
}
// at this point we're committed to running the log handlers so increment
// the waitgroup
wg.Add(2)
// Pass output to the handlers
go outHandler(bufio.NewScanner(outReader))
go errHandler(bufio.NewScanner(errReader))
// now run the command
err = cmd.Start()
if err != nil {
return err
}
var timer *time.Timer
if timeout != 0 {
timer = time.AfterFunc(timeout, func() {
// kill process, unblock the cmd.Wait() below
killErr := cmd.Process.Kill()
if killErr != nil {
fmt.Printf("error killing process: %s\n", killErr)
}
// log that the process timed out
logger.Error(fmt.Sprintf("command %v timed out after %v", cmdv, timeout))
})
}
err = cmd.Wait()
// if cmd.Wait() returns before the timeout, we'll have a timer stop
if timer != nil {
timer.Stop()
}
return err
}