Golang中如何分离标准错误管道中的错误和警告信息

Golang中如何分离标准错误管道中的错误和警告信息 大家好,

我想知道是否有办法区分由 cmd.StderrPipe 函数捕获的警告和错误?我正在使用 Go 运行一个 JS 脚本,需要分别捕获所有日志(信息、警告、错误)。StdoutPipe 和 StderrPipe 对于捕获日志非常有用,但我找不到在 StderrPipe 中区分警告和错误的方法。

谢谢

3 回复

感谢 @skillian

问题是日志没有被标记为 [ERROR] 或 [WARN]。我只捕获了日志的消息部分,因此无法将它们区分开来。我认为这个问题没有解决方案。

再次感谢

更多关于Golang中如何分离标准错误管道中的错误和警告信息的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在一个管道中,没有用于区分警告或错误的“子管道”。它只是数据,例如从文件中读取。如果你能修改那个JS脚本,使其以更结构化的形式(如JSON)输出消息,你就可以将消息解析成单独的警告和错误对象列表。如果无法配置/修改脚本来实现这一点,你需要编写一些代码来解析输出并将其区分为警告或错误(例如,可能只需读取每一行,如果以“[WARNING]”开头,则为警告,或以“[ERROR]”开头,则为错误),或者也许已经有人编写了处理解析的包,你可以直接使用它。

在Go中,标准错误管道(stderr)本身不区分警告和错误——它只是一个输出流。要区分警告和错误,您需要在应用层解析stderr的内容。以下是几种实现方法:

方法1:基于内容模式匹配

package main

import (
    "bufio"
    "fmt"
    "io"
    "os/exec"
    "strings"
)

func main() {
    cmd := exec.Command("node", "your_script.js")
    
    stdout, _ := cmd.StdoutPipe()
    stderr, _ := cmd.StderrPipe()
    
    cmd.Start()
    
    // 处理标准输出(通常为info日志)
    go func() {
        scanner := bufio.NewScanner(stdout)
        for scanner.Scan() {
            line := scanner.Text()
            fmt.Printf("INFO: %s\n", line)
        }
    }()
    
    // 处理标准错误,根据内容分类
    go func() {
        scanner := bufio.NewScanner(stderr)
        for scanner.Scan() {
            line := scanner.Text()
            
            // 根据关键词判断日志级别
            switch {
            case strings.Contains(strings.ToLower(line), "warning"):
                fmt.Printf("WARNING: %s\n", line)
            case strings.Contains(strings.ToLower(line), "error"):
                fmt.Printf("ERROR: %s\n", line)
            default:
                fmt.Printf("STDERR: %s\n", line)
            }
        }
    }()
    
    cmd.Wait()
}

方法2:使用结构化日志格式(推荐)

如果控制输出格式,可以使用JSON等结构化格式:

package main

import (
    "bufio"
    "encoding/json"
    "fmt"
    "os/exec"
)

type LogEntry struct {
    Level   string `json:"level"`
    Message string `json:"message"`
    Time    string `json:"time"`
}

func main() {
    cmd := exec.Command("node", "script_with_json_logs.js")
    stderr, _ := cmd.StderrPipe()
    
    cmd.Start()
    
    scanner := bufio.NewScanner(stderr)
    for scanner.Scan() {
        var entry LogEntry
        if err := json.Unmarshal([]byte(scanner.Text()), &entry); err == nil {
            // 成功解析JSON日志
            fmt.Printf("%s: %s\n", entry.Level, entry.Message)
        } else {
            // 非结构化日志
            fmt.Printf("RAW: %s\n", scanner.Text())
        }
    }
    
    cmd.Wait()
}

方法3:使用多管道重定向

通过shell将不同级别的日志重定向到不同流:

package main

import (
    "bufio"
    "fmt"
    "os/exec"
    "strings"
)

func main() {
    // 使用shell将stderr中的警告和错误分离
    script := `
    exec 2> >(while read line; do
        if [[ "$line" =~ [Ww]arning ]]; then
            echo "WARNING_STREAM:$line" >&3
        else
            echo "ERROR_STREAM:$line" >&4
        fi
    done)
    node your_script.js
    `
    
    cmd := exec.Command("bash", "-c", script)
    
    // 创建额外的文件描述符
    warningPipe, _ := cmd.StderrPipe()
    // 需要额外的管道处理,这里简化展示概念
    
    cmd.Start()
    
    scanner := bufio.NewScanner(warningPipe)
    for scanner.Scan() {
        line := scanner.Text()
        if strings.HasPrefix(line, "WARNING_STREAM:") {
            fmt.Printf("WARNING: %s\n", strings.TrimPrefix(line, "WARNING_STREAM:"))
        }
    }
    
    cmd.Wait()
}

方法4:使用专门的日志库

在Go端使用日志解析库:

package main

import (
    "bufio"
    "fmt"
    "os/exec"
    "regexp"
)

var (
    warningRegex = regexp.MustCompile(`(?i)\[?(WARN|WARNING)\]?`)
    errorRegex   = regexp.MustCompile(`(?i)\[?(ERROR|ERR|FAILED)\]?`)
    infoRegex    = regexp.MustCompile(`(?i)\[?(INFO|DEBUG)\]?`)
)

func classifyLogLine(line string) string {
    switch {
    case warningRegex.MatchString(line):
        return "WARNING"
    case errorRegex.MatchString(line):
        return "ERROR"
    case infoRegex.MatchString(line):
        return "INFO"
    default:
        return "UNKNOWN"
    }
}

func main() {
    cmd := exec.Command("node", "your_script.js")
    stderr, _ := cmd.StderrPipe()
    
    cmd.Start()
    
    scanner := bufio.NewScanner(stderr)
    for scanner.Scan() {
        line := scanner.Text()
        level := classifyLogLine(line)
        fmt.Printf("[%s] %s\n", level, line)
    }
    
    cmd.Wait()
}

关键点

  1. stderr本身不区分警告和错误,它只是Unix/Linux系统的一个标准错误输出流
  2. 需要应用层解析:根据日志内容的关键词、格式或前缀进行区分
  3. 最佳实践:让被调用的进程输出结构化日志(如JSON),便于准确解析
  4. 考虑使用成熟的日志库:如logrus、zap等,它们通常包含日志级别标记

最可靠的方案是修改JS脚本,使其输出带明确级别标记的日志,然后在Go中根据这些标记进行分类处理。

回到顶部