Home > Back-end >  How to redirect os.Stdout to io.MultiWriter() in Go?
How to redirect os.Stdout to io.MultiWriter() in Go?

Time:04-11

I am writing a Test function that testing go program interacting with a command line program. That is
os.Stdio -> cmd.Stdin
cmd.Stdin -> os.Stdin

I could use pipe to connect those io, but I would have a log of the data passing the pipe. I tried to use io.MultiWriter but it is not a os.file object and cannot assign to os.Stdout. I have found some sample which use a lot of pipe and io.copy. but as io.copy is not interactive. How could I connect the Stdout to a io.MultiWriter with pipe?

logfile, err := os.Create("stdout.log")
r, w, _ := os.Pipe()
mw := io.MultiWriter(os.Stdout, logfile, w)
cmd.Stdin = r
os.Stdout = mw // <- error in this line

The error message like

cannot use mw (variable of type io.Writer) as type *os.File in assignment:

CodePudding user response:

As an alternative solution, you can mimic the MultiWriter using a separate func to read what has been written to the stdout, capture it, then write it to the file and also to the original stdout.

package main

import (
    "bufio"
    "fmt"
    "os"
    "time"
)

func main() {
    originalStdout := os.Stdout //Backup of the original stdout
    r, w, _ := os.Pipe()
    os.Stdout = w

    //Use a separate goroutine for non-blocking
    go func() {
        f, _ := os.Create("stdout.log")
        defer f.Close()
        scanner := bufio.NewScanner(r)
        for scanner.Scan() {
            s := scanner.Text()   "\r\n"
            f.WriteString(s)
            originalStdout.WriteString(s)
        }
    }()

    //Test
    c := time.NewTicker(time.Second)
    for {
        select {
        case <-c.C:
            fmt.Println(time.Now())
            fmt.Fprintln(os.Stderr, "This is on Stderr")
        }
    }
}

CodePudding user response:

I figure out a work around with pipe, TeeReader and MultiWriter The setup is a test function which test the interaction of go program interact with a python program though stdin and stdout.

main.stdout -> pipe -> TeeReader--> (client.stdin, MultiWriter(log, stdout))

client.stdout -> MultiWriter(pipe --> main.stdin, logfile, stdout )

I will try to add more explanation later

func Test_Interactive(t *testing.T) {
    var tests = []struct {
        file string
    }{
        {"Test_Client.py"},
    }
    for tc, tt := range tests {
        fmt.Println("---------------------------------")
        fmt.Printf("Test %d, Test Client:%v\n", tc 1, tt.file)
        fmt.Println("---------------------------------")
        // Define external program
        client := exec.Command("python3", tt.file)
        // Define log file
        logfile, err := os.Create(tt.file   ".log")
        if err != nil {
            panic(err)
        }
        defer logfile.Close()
        out := os.Stdout
        defer func() { os.Stdout = out }() // Restore original Stdout
        in := os.Stdin
        defer func() { os.Stdin = in }() // Restore original Stdin

        // Create pipe connect os.Stdout to client.Stdin
        gr, gw, _ := os.Pipe()

        // Connect os.Stdout to writer side of pipe
        os.Stdout = gw

        // Create MultiWriter to write to logfile and os.Stdout at the same time
        gmw := io.MultiWriter(out, logfile)

        // Create a tee reader read from reader side of the pipe and flow to the MultiWriter
        // Repleace the cmd.Stdin with TeeReader
        client.Stdin = io.TeeReader(gr, gmw)

        // Create a pipe to connect client.Stdout to os.Stdin
        cr, cw, _ := os.Pipe()

        // Create MultWriter to client stdout
        cmw := io.MultiWriter(cw, logfile, out)
        client.Stdout = cmw

        // Connect os stdin to another end of the pipe
        os.Stdin = cr

        // Start Client
        client.Start()
        // Start main
        go main()
        client.Wait()
    }

}
  •  Tags:  
  • go
  • Related