Golang中如何通过管道执行Goroutine并获取os.Stdout/os.Stderr输出?

Golang中如何通过管道执行Goroutine并获取os.Stdout/os.Stderr输出? 在某些编程语言中(通常是脚本语言,但也有一些编译型语言),可以在执行代码的同时将所有后续输出重定向到自定义流中。

我正在开发一个完全无人值守运行的软件,目前正在用可以远程持久访问的流来替换全局的Stdout/Stderr流,但我希望对这些流进行细分。根据我当前的日志配置,有一个非常简单的方法可以实现这一点,因为记录器是通过工厂方法创建的。示例:

var debug, info, warn, critical = logging.NewLoggers("modules.gnr")

我可以简单地让这个工厂方法根据提供的记录器命名空间写入特定的流,但还有一个额外的细节…我同时在使用Cron(类)任务,并且希望每次任务执行都有自己独立的日志。由于记录器是在包级别创建的,我看不出如果不传递这些级别记录器值(这听起来很糟糕)该如何实现这个需求。

我曾考虑使用Context(包含相关记录器的指针)并传递Context,但我实在不想这么做。有没有其他不那么令人反感的方法来实现这个?

非常感谢任何帮助。虽然我不能说是Go语言的新手,但这绝对是我用Go完成过的最庞大的项目。


更多关于Golang中如何通过管道执行Goroutine并获取os.Stdout/os.Stderr输出?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

这实际上就是我正在做的。问题在于我需要它是"实例特定"的。最终我还是使用了context,并创建了一个单一的自定义Logger抽象,包含Info/Infof、Warn/Warnf等方法。然后是一个辅助方法:

log := logger.GetLoggers(context)

更多关于Golang中如何通过管道执行Goroutine并获取os.Stdout/os.Stderr输出?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这个使用标准输出/标准错误管道的示例可能会有帮助。

	}

	modulo37(pfile) // t.Log is using stdErr and looks confusing
	pfile.Close()

	FileCompare(t,"moduloref.txt",prodFileName,"modulo print")
}
*/

func TestModulo37(t *testing.T) {
	OutputDir()
	prodFileName := "moduloprod.txt"
	pfile, err := os.Create(prodFileName)
	if err != nil {
		t.Errorf("produced file creation %s failed with %v", prodFileName, err)
	}

	// Capture stdout.
	stdout := os.Stdout
	r, w, err := os.Pipe()

您可以在每个包中全局提供日志记录器,并在 init() 函数中创建这些日志记录器,该函数在 main 之前运行。

package somepackage

import (
       "logging"
)

// Variables are global in package
var debug, info, warn, critical logging.Logger

// initalize the loggers
func init() {
    debug, info, warn, critical = logging.NewLoggers("somepackage")
}

// use them in function
func someFunc()  {
    user, err := CurrentUser()
    if err != nil {
         critical.Log("Current user not found")
    } else {
        info.Logf("Current user is %s", user)
    }
    ...

在Go语言中,可以通过管道捕获goroutine的os.Stdout和os.Stderr输出,同时保持日志记录的独立性。以下是几种实现方案:

方案1:使用os.Pipe重定向标准输出

package main

import (
    "io"
    "os"
    "fmt"
    "bytes"
)

func captureOutput(f func()) (string, string, error) {
    // 保存原始标准输出和错误
    oldStdout := os.Stdout
    oldStderr := os.Stderr
    
    // 创建管道
    rOut, wOut, _ := os.Pipe()
    rErr, wErr, _ := os.Pipe()
    
    // 重定向标准输出和错误
    os.Stdout = wOut
    os.Stderr = wErr
    
    // 执行函数
    f()
    
    // 恢复原始标准输出和错误
    os.Stdout = oldStdout
    os.Stderr = oldStderr
    
    // 关闭写入端
    wOut.Close()
    wErr.Close()
    
    // 读取输出
    var bufOut, bufErr bytes.Buffer
    io.Copy(&bufOut, rOut)
    io.Copy(&bufErr, rErr)
    
    return bufOut.String(), bufErr.String(), nil
}

func main() {
    // 示例任务函数
    task := func() {
        fmt.Println("这是标准输出")
        fmt.Fprintln(os.Stderr, "这是标准错误")
        fmt.Println("任务执行完成")
    }
    
    stdout, stderr, _ := captureOutput(task)
    fmt.Printf("捕获的标准输出:\n%s\n", stdout)
    fmt.Printf("捕获的标准错误:\n%s\n", stderr)
}

方案2:针对goroutine的独立日志捕获

package main

import (
    "bytes"
    "context"
    "fmt"
    "io"
    "log"
    "os"
    "sync"
)

type TaskLogger struct {
    Name     string
    Stdout   *bytes.Buffer
    Stderr   *bytes.Buffer
    Original *log.Logger
}

func NewTaskLogger(name string) *TaskLogger {
    return &TaskLogger{
        Name:   name,
        Stdout: &bytes.Buffer{},
        Stderr: &bytes.Buffer{},
    }
}

func (tl *TaskLogger) Capture(f func()) {
    // 创建多写入器,同时写入原始输出和缓冲区
    multiOut := io.MultiWriter(os.Stdout, tl.Stdout)
    multiErr := io.MultiWriter(os.Stderr, tl.Stderr)
    
    // 临时重定向输出
    oldOut := os.Stdout
    oldErr := os.Stderr
    
    rOut, wOut, _ := os.Pipe()
    rErr, wErr, _ := os.Pipe()
    
    os.Stdout = wOut
    os.Stderr = wErr
    
    // 在goroutine中复制输出
    var wg sync.WaitGroup
    wg.Add(2)
    
    go func() {
        io.Copy(multiOut, rOut)
        wg.Done()
    }()
    
    go func() {
        io.Copy(multiErr, rErr)
        wg.Done()
    }()
    
    // 执行任务
    f()
    
    // 恢复并等待复制完成
    os.Stdout = oldOut
    os.Stderr = oldErr
    wOut.Close()
    wErr.Close()
    wg.Wait()
}

func ExecuteTaskWithLogging(taskName string, taskFunc func()) *TaskLogger {
    logger := NewTaskLogger(taskName)
    logger.Capture(taskFunc)
    return logger
}

func main() {
    // 模拟cron任务
    task1 := func() {
        fmt.Println("任务1: 开始执行")
        fmt.Fprintln(os.Stderr, "任务1: 错误信息")
        fmt.Println("任务1: 执行完成")
    }
    
    task2 := func() {
        fmt.Println("任务2: 处理数据")
        fmt.Fprintln(os.Stderr, "任务2: 警告信息")
        fmt.Println("任务2: 处理完成")
    }
    
    // 并行执行任务并捕获日志
    var wg sync.WaitGroup
    var logger1, logger2 *TaskLogger
    
    wg.Add(2)
    go func() {
        defer wg.Done()
        logger1 = ExecuteTaskWithLogging("daily-report", task1)
    }()
    
    go func() {
        defer wg.Done()
        logger2 = ExecuteTaskWithLogging("data-cleanup", task2)
    }()
    
    wg.Wait()
    
    // 输出各自任务的日志
    fmt.Printf("\n任务 '%s' 的输出:\n%s", logger1.Name, logger1.Stdout.String())
    fmt.Printf("任务 '%s' 的错误:\n%s", logger1.Name, logger1.Stderr.String())
    
    fmt.Printf("\n任务 '%s' 的输出:\n%s", logger2.Name, logger2.Stdout.String())
    fmt.Printf("任务 '%s' 的错误:\n%s", logger2.Name, logger2.Stderr.String())
}

方案3:集成到现有日志系统

package main

import (
    "bytes"
    "fmt"
    "io"
    "os"
    "time"
)

// 模拟你的日志记录器
type Logger struct {
    namespace string
    buffer    *bytes.Buffer
}

func NewLogger(namespace string) *Logger {
    return &Logger{
        namespace: namespace,
        buffer:    &bytes.Buffer{},
    }
}

func (l *Logger) Info(msg string) {
    logMsg := fmt.Sprintf("[%s][INFO][%s] %s\n", l.namespace, time.Now().Format("15:04:05"), msg)
    l.buffer.WriteString(logMsg)
    fmt.Print(logMsg)
}

func (l *Logger) Error(msg string) {
    logMsg := fmt.Sprintf("[%s][ERROR][%s] %s\n", l.namespace, time.Now().Format("15:04:05"), msg)
    l.buffer.WriteString(logMsg)
    fmt.Fprint(os.Stderr, logMsg)
}

func ExecuteTask(taskName string, taskFunc func(*Logger)) string {
    // 为每个任务创建独立的记录器
    taskLogger := NewLogger(taskName)
    
    // 捕获标准输出和错误
    oldStdout := os.Stdout
    oldStderr := os.Stderr
    
    rOut, wOut, _ := os.Pipe()
    rErr, wErr, _ := os.Pipe()
    
    os.Stdout = wOut
    os.Stderr = wErr
    
    var capturedOutput bytes.Buffer
    
    // 启动goroutine来捕获输出
    done := make(chan struct{})
    go func() {
        io.Copy(&capturedOutput, rOut)
        io.Copy(&capturedOutput, rErr)
        close(done)
    }()
    
    // 执行任务
    taskFunc(taskLogger)
    
    // 恢复标准输出和错误
    os.Stdout = oldStdout
    os.Stderr = oldStderr
    wOut.Close()
    wErr.Close()
    
    <-done
    
    // 返回组合的日志输出
    return taskLogger.buffer.String() + "\n捕获的输出:\n" + capturedOutput.String()
}

func main() {
    dailyTask := func(logger *Logger) {
        logger.Info("开始每日报告任务")
        fmt.Println("处理报告数据...")
        logger.Info("报告生成完成")
        fmt.Fprintln(os.Stderr, "发现数据不一致")
        logger.Error("数据验证失败")
    }
    
    result := ExecuteTask("modules.gnr.daily-report", dailyTask)
    fmt.Printf("任务执行结果:\n%s\n", result)
}

这些方案提供了不同级别的控制粒度,可以根据具体需求选择。方案1最简单,方案2更适合并行任务执行,方案3则更好地集成到现有的日志系统中。所有方案都避免了传递上下文或修改全局状态的问题。

回到顶部