如何在实时输出的同时将命令的标准输出和标准错误重定向到控制台和日志文件?

How can I redirect the stdout and stderr of a command to both the console and a log file while outputting in real time?

下面的代码完全符合我的要求,只是它只打印到控制台。

cmd := exec.Command("php", "randomcommand.php")

cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil {
    log.Fatal(err)
}

randomcommand.php:

// randomcommand.php simply alternates output between stdout and stderr 20 times

$stdout = fopen('php://stdout', 'w+');
$stderr = fopen('php://stderr', 'w+');
for ($i = 1;$i <= 20; $i++) {
    fwrite($stdout, "stdout $i\n");
    fwrite($stderr, "stderr $i\n");
}

输出:

stdout 1
stderr 1
stdout 2
stderr 2
stdout 3
stderr 3
stdout 4
stderr 4
stdout 5
stderr 5
stdout 6
stderr 6
stdout 7
stderr 7
stdout 8
stderr 8
stdout 9
stderr 9
stdout 10
stderr 10
stdout 11
stderr 11
stdout 12
stderr 12
stdout 13
stderr 13
stdout 14
stderr 14
stdout 15
stderr 15
stdout 16
stderr 16
stdout 17
stderr 17
stdout 18
stderr 18
stdout 19
stderr 19
stdout 20
stderr 20

我想要实现的是:

  1. 将命令的标准输出和标准错误实时打印到控制台;
  2. 将命令的 stdout 和 stderr 记录到文件中,与控制台中显示的完全一样;
  3. 遵守写入 stdout 和 stderr 的确切顺序,并且;
  4. 不修改命令本身

我试过将命令 stdout 和 stderr 通过管道传输到扫描仪或使用 io.Copy,每个都在 goroutine 中,但输出的顺序没有保持。可能是因为 goroutine 之间的交替必须与输出交替同时发生,这几乎是不可能的。

我也试过使用 "select" 作为 here 中的示例。但是没有维护输出顺序。

Logstreamer 相同的问题。

我必须尝试的另一个想法是看看我是否能以某种方式从控制台缓冲区读取数据,但这感觉不对。

有谁知道这是否可行且值得追求?

正如我在评论部分所说,这可以使用MultiWriter

来实现
package main

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

func main() {
    // Logging capability
    f, err := os.OpenFile("log.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    if err != nil {
        log.Fatalf("Error opening file: %v", err)
    }
    defer f.Close()
    mwriter := io.MultiWriter(f, os.Stdout)
    cmd := exec.Command("ls")
    cmd.Stderr = mwriter
    cmd.Stdout = mwriter
    err = cmd.Run() //blocks until sub process is complete
    if err != nil {
        panic(err)
    }
}

当您声明您的命令时,在您 运行 它之前,只需指定 Stdout 和 Stderr 使用上面定义的 MultiWriter。此 MultiWriter 实例包含日志文件和标准输出。