Actually it’s about os.Stdout and os.Stderr :D
Have you ever get into a situation when writing unit test that need to assert the output of function? Well when you encounter it, you’ll in trouble. Today I’ll tell you about how to capture output from os.Stdout and os.Stderr which are primarily used by standard log utilities like: log.Println, fmt.Println…
When you take a closer look at os.Stdout and os.Stderr, they have type *os.File so Golang support us a file pipeline method for read continuously from the reader which is piped from the writer which have data written to. I’ve published a blog explaining the io Pipeline so you can have deep understand about pipeline.
It’s time to dive into code, to init a os file pipeline we use the Go function os.Pipe
Simple, right? Now we created a pipe between reader and writer which have type *os.File, what come in to writer will come out from reader, remember that.
We need a io pipeline which have type *os.File because we want to replace standard os.Stdout and os.Stderr, which have type *os.File also, by this. So we can actually write and read the output from the program, that’s the point.
Capturing Program Output
First we’ll replace the standard os output with pipeline:
That’s it, all we need to do is replace global variables os.Stdout and os.Stderr with new writer and set the output for log package, log package default uses os.Stderr, but it just copy the address of os.Stderr, so if we change the address of os.Stderr itself, the output address in log package remains no change, luckily log package provide a method calls SetOutput to change the address of output object, so we can use it.
Second we’ll read the output and save it:
Because read and write can not stay in a same goroutine so we fork a new goroutine for read and save all the output into a buffer and send the output string to channel for main goroutine get it
Finally we’ll call the function we want to capture the ouput. Full working code is shown below:
Expert Note: because os.Stdout and os.Stderr is global variable of os package, when we temporary change it address to the pipeline to capture output, we need to set it back to origin when captureOutput returned
This program is not thread safe. How do we make it thread safe? Please do it as an exercise by yourself. Have fun!