Reading os.OpenFile in Golang while still being written?

Issue

I have code that is writing to a logfile while executing a system command. E.g.

    logfile, err := os.OpenFile(THIS_LOG_FILE, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
    if err != nil {
        return err
    }
    cmd.Stderr = logfile
    cmd.Stdout = logfile

    go func() {
        err := cmd.Run()
        if err != nil {
            // WANT TO LOG ERROR HERE
        }
    }()

At the "// WANT TO LOG" line, I’d like to output the content to the standard logger, in addition to the previously assigned logfile destination. Is there a way to capture this in memory? Or should I just write everything to an in-memory buffer and flush at the end?

To clarify, in capturing the output of the command in memory, I can parse it and take action in the running program (handling errors/etc). When I write to the log file, that information is lost.

My issue is that, theoretically, I could read that back in from the file I just wrote, but that seems wasteful (and prone to failure if the command failed).

Solution

If I understand correctly, you want to write the content of stdout/stderror to a file while executing a shell command.

Since stdout and stderror are implemented the ReadCloser interface, you can merge them by io.MultiReader and perform io.Copy from source to destination.

The following snippet implements the pipeline

package main

import (
    "io"
    "log"
    "os"
    "os/exec"
)

func main() {
    // prepare the command
    cmd := exec.Command("your-shell-command.sh")

    // get the stdout and stderr stream
    erc, err := cmd.StderrPipe()
    if err != nil {
        log.Fatalln("Failed to get stderr reader: ", err)
    }
    orc, err := cmd.StdoutPipe()
    if err != nil {
        log.Fatalln("Failed to get stdout reader: ", err)
    }

    // combine stdout and stderror ReadCloser
    rc := io.MultiReader(erc, orc)

    // Prepare the writer
    f, err := os.Create("output.log")
    if err != nil {
        log.Fatalln("Failed to create file")
    }
    defer f.Close()
    // Command.Start starts a new go routine
    if err := cmd.Start(); err != nil {
        log.Println("Failed to start the command")
    }

    // add the TeeReader.
    var buf bytes.Buffer 
    tr := io.TeeReader(rc, &buf) 
    if _, err := io.Copy(f, tr); err != nil { 
        logger.Fatalf("Failed to stream to file: %s", err) 
    }


    if err := cmd.Wait(); err != nil {
        log.Println("Failed to wait the command to execute: ", err)
    }
}

Answered By – kha

Answer Checked By – Willingham (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.