Home > Software design >  Prepend to stdout for a subprocess's output
Prepend to stdout for a subprocess's output

Time:08-24

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

https://go.dev/play/p/zFsahhqjoIJ

  •  Tags:  
  • go
  • Related