From 7c015dd400f7a74a7090164bcff106a976dd1da8 Mon Sep 17 00:00:00 2001 From: rsteube Date: Wed, 10 Jan 2024 16:06:53 +0100 Subject: [PATCH] exec: full argument logging --- context.go | 8 ++++---- defaultActions.go | 1 - internal/log/log.go | 32 ++++++++++++++++++++++++++++++++ log.go | 31 ++----------------------------- pkg/execlog/execlog.go | 33 +++++++++++++++++++++++++++++++++ pkg/util/format.go | 25 +++++++++++++++++++++++++ 6 files changed, 96 insertions(+), 34 deletions(-) create mode 100644 internal/log/log.go create mode 100644 pkg/execlog/execlog.go create mode 100644 pkg/util/format.go diff --git a/context.go b/context.go index 2eed94a3a..9be28d40f 100644 --- a/context.go +++ b/context.go @@ -9,9 +9,9 @@ import ( "github.com/rsteube/carapace/internal/env" "github.com/rsteube/carapace/internal/shell/zsh" + "github.com/rsteube/carapace/pkg/execlog" "github.com/rsteube/carapace/pkg/util" "github.com/rsteube/carapace/third_party/github.com/drone/envsubst" - "github.com/rsteube/carapace/third_party/golang.org/x/sys/execabs" "github.com/spf13/cobra" ) @@ -88,16 +88,16 @@ func (c Context) Envsubst(s string) (string, error) { // Command returns the Cmd struct to execute the named program with the given arguments. // Env and Dir are set using the Context. // See exec.Command for most details. -func (c Context) Command(name string, arg ...string) *execabs.Cmd { +func (c Context) Command(name string, arg ...string) *execlog.Cmd { if c.mockedReplies != nil { if m, err := json.Marshal(append([]string{name}, arg...)); err == nil { if reply, exists := c.mockedReplies[string(m)]; exists { - return execabs.Command("echo", reply) + return execlog.Command("echo", reply) // TODO use mock } } } - cmd := execabs.Command(name, arg...) + cmd := execlog.Command(name, arg...) cmd.Env = c.Env cmd.Dir = c.Dir return cmd diff --git a/defaultActions.go b/defaultActions.go index dc7d28cf0..9b192860c 100644 --- a/defaultActions.go +++ b/defaultActions.go @@ -66,7 +66,6 @@ func ActionExecCommandE(name string, arg ...string) func(f func(output []byte, e cmd := c.Command(name, arg...) cmd.Stdout = &stdout cmd.Stderr = &stderr - LOG.Printf("executing %#v", cmd.String()) if err := cmd.Run(); err != nil { if exitErr, ok := err.(*exec.ExitError); ok { exitErr.Stderr = stderr.Bytes() // seems this needs to be set manually due to stdout being collected? diff --git a/internal/log/log.go b/internal/log/log.go new file mode 100644 index 000000000..a36a846a0 --- /dev/null +++ b/internal/log/log.go @@ -0,0 +1,32 @@ +package log + +import ( + "fmt" + "io/ioutil" + "log" + "os" + + "github.com/rsteube/carapace/internal/env" + "github.com/rsteube/carapace/internal/uid" + "github.com/rsteube/carapace/pkg/ps" +) + +var LOG = log.New(ioutil.Discard, "", log.Flags()) + +func init() { + if !env.Log() { + return + } + + tmpdir := fmt.Sprintf("%v/carapace", os.TempDir()) + if err := os.MkdirAll(tmpdir, os.ModePerm); err != nil { + log.Fatal(err.Error()) + } + + file := fmt.Sprintf("%v/%v.log", tmpdir, uid.Executable()) + if logfileWriter, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o666); err != nil { + log.Fatal(err.Error()) + } else { + LOG = log.New(logfileWriter, ps.DetermineShell()+" ", log.Flags()|log.Lmsgprefix|log.Lmicroseconds) + } +} diff --git a/log.go b/log.go index 0f951052b..d4cc10ec1 100644 --- a/log.go +++ b/log.go @@ -1,32 +1,5 @@ package carapace -import ( - "fmt" - "io/ioutil" - "log" - "os" +import "github.com/rsteube/carapace/internal/log" - "github.com/rsteube/carapace/internal/env" - "github.com/rsteube/carapace/internal/uid" - "github.com/rsteube/carapace/pkg/ps" -) - -var LOG = log.New(ioutil.Discard, "", log.Flags()) - -func init() { - if !env.Log() { - return - } - - tmpdir := fmt.Sprintf("%v/carapace", os.TempDir()) - if err := os.MkdirAll(tmpdir, os.ModePerm); err != nil { - log.Fatal(err.Error()) - } - - file := fmt.Sprintf("%v/%v.log", tmpdir, uid.Executable()) - if logfileWriter, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o666); err != nil { - log.Fatal(err.Error()) - } else { - LOG = log.New(logfileWriter, ps.DetermineShell()+" ", log.Flags()|log.Lmsgprefix|log.Lmicroseconds) - } -} +var LOG = log.LOG diff --git a/pkg/execlog/execlog.go b/pkg/execlog/execlog.go new file mode 100644 index 000000000..1044e1d60 --- /dev/null +++ b/pkg/execlog/execlog.go @@ -0,0 +1,33 @@ +package execlog + +import ( + "github.com/rsteube/carapace/internal/log" + "github.com/rsteube/carapace/pkg/util" + "github.com/rsteube/carapace/third_party/golang.org/x/sys/execabs" +) + +type Cmd struct { + *execabs.Cmd +} + +// Command is like exec.Command but logs args on execution. +func Command(name string, arg ...string) *Cmd { + cmd := &Cmd{ + execabs.Command(name, arg...), + } + return cmd +} + +func (c *Cmd) Run() error { + log.LOG.Printf("executing %#v", util.FormatCmd(c.Args...)) + return c.Cmd.Run() +} + +func (c *Cmd) Start() error { + log.LOG.Printf("executing %#v", util.FormatCmd(c.Args...)) + return c.Cmd.Start() +} + +func LookPath(file string) (string, error) { + return execabs.LookPath(file) +} diff --git a/pkg/util/format.go b/pkg/util/format.go new file mode 100644 index 000000000..69c551307 --- /dev/null +++ b/pkg/util/format.go @@ -0,0 +1,25 @@ +package util + +import ( + "fmt" + "strings" +) + +func FormatCmd(args ...string) string { + replacer := strings.NewReplacer( + "$", "\\$", + "`", "\\`", + ) + + formatted := make([]string, 0, len(args)) + for _, arg := range args { + switch { + case arg == "", + strings.ContainsAny(arg, `"' `+"\n\r\t"): + formatted = append(formatted, replacer.Replace(fmt.Sprintf("%#v", arg))) + default: + formatted = append(formatted, arg) + } + } + return strings.Join(formatted, " ") +}