Is there a way to prepend to stdout
of subprocesses? stdout
is of type io.Writer and I understand that it is immutable.
One way I thought to show the pid in each log was to add a logrus hook to each of the subprocess. But still want to know if there is a way to modify the stdout
.
This test runs 3 subprocesses and sends their stdout
& stderr
to first program's stdout
and stderr
.
package main
import (
"os"
"os/exec"
"testing"
"time"
"github.com/sirupsen/logrus"
)
func Crasher(t *testing.T) {
logrus.Info("huhaha")
// This runs test with lots of logs.
}
func startProcess(pid int) {
cmd := exec.Command(os.Args[0], "-test.run=^TestCrasher$")
cmd.Env = append(os.Environ(), "BE_CRASHER=1")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
logrus.Infof("Starting process with pid %d", pid)
_ = cmd.Run()
}
func TestCrasher(t *testing.T) {
if os.Getenv("BE_CRASHER") == "1" {
Crasher(t)
return
}
for pid := 0; pid < 3; pid {
go startProcess(pid)
}
time.Sleep(5 * time.Second)
}
Output:
=== RUN TestCrasher
time="2009-11-10T23:00:00Z" level=info msg="Starting process with pid 0"
time="2009-11-10T23:00:00Z" level=info msg="Starting process with pid 2"
time="2009-11-10T23:00:00Z" level=info msg="Starting process with pid 1"
time="2009-11-10T23:00:00Z" level=info msg=huhaha
time="2009-11-10T23:00:00Z" level=info msg=huhaha
time="2009-11-10T23:00:00Z" level=info msg=huhaha
PASS
PASS
PASS
--- PASS: TestCrasher (5.00s)
PASS
What I want:
time="2009-11-10T23:00:00Z" level=info msg="Starting process with pid 0"
time="2009-11-10T23:00:00Z" level=info msg="Starting process with pid 2"
time="2009-11-10T23:00:00Z" level=info msg="Starting process with pid 1"
pid0 time="2009-11-10T23:00:00Z" level=info msg=huhaha
pid1 time="2009-11-10T23:00:00Z" level=info msg=huhaha
pid2 time="2009-11-10T23:00:00Z" level=info msg=huhaha
Playground: https://go.dev/play/p/fKkLGtcigtm
CodePudding user response:
Are you sure you want it to prepend smth to the output? May be what you want is to have pid=id
string in the output?
It could be done by logrus
without any hacking. Send child ID using environment or command line, and use Logger.WithFields
to add whatever fields you need to see in every line.
Example here: https://go.dev/play/p/M7wJ9VvlPOO Output:
=== RUN TestCrasher
time="2009-11-10T23:00:00Z" level=info msg="Starting process with pid 2"
time="2009-11-10T23:00:00Z" level=info msg="Starting process with pid 1"
time="2009-11-10T23:00:00Z" level=info msg="Starting process with pid 0"
time="2009-11-10T23:00:00Z" level=info msg=huhaha pid=2
time="2009-11-10T23:00:00Z" level=warning msg=foo-bla-blaaa pid=2
time="2009-11-10T23:00:00Z" level=info msg=huhaha pid=1
time="2009-11-10T23:00:00Z" level=warning msg=foo-bla-blaaa pid=1
time="2009-11-10T23:00:00Z" level=info msg=huhaha pid=0
time="2009-11-10T23:00:00Z" level=warning msg=foo-bla-blaaa pid=0
PASS
PASS
PASS
--- PASS: TestCrasher (5.00s)
PASS
You can filter ouput per process by pid=
string.
CodePudding user response:
You may set cmd.Stdout / cmd.Stderr
to something else than os.Stdout / os.Stderr
:
// create a pipe to collect the output, and process it in a separate goroutine
r, w := io.Pipe()
// choose a way to signal this goroutine has completed
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
// choose whatever way you see fit to process the output:
// for example, instanciate a Scanner and read output line by line
s := bufio.NewScanner(r)
for s.Scan() {
// for some reason , the output contains garbled characters in the playground, haven't debugged why
fmt.Printf("pid=%d %s\n", pid, s.Text())
}
}()
// you can process just Stderr (log output) or both Stderr and Stdout
cmd.Stdout = w
cmd.Stderr = w