Go应用收集外部Go二进制日志,每10秒切换日志文件失效求助
问题分析与解决方案
你的问题核心在于:子进程启动后,其stdout/stderr已经绑定到初始日志文件的文件描述符,后续修改cmd.Stdout/cmd.Stderr不会改变子进程的输出目标。子进程在启动时会复制父进程的文件句柄,之后父进程修改cmd结构体的字段对运行中的子进程完全无效。
解决思路
放弃直接将子进程输出绑定到文件,改用管道中转:
- 父进程创建管道,将子进程的stdout/stderr绑定到管道的写入端
- 启动goroutine持续读取管道输出,将内容写入当前日志文件
- 每10秒切换日志文件时,只需更新goroutine中使用的目标文件(加锁避免并发写入冲突)
修改后的代码
package main import ( "bufio" "fmt" "log" "os" "os/exec" "sync" "time" ) func main() { ticker := time.NewTicker(time.Second * 10) defer ticker.Stop() logFolderPath := "./log/" // 确保日志目录存在 if err := os.MkdirAll(logFolderPath, 0755); err != nil { fmt.Println("Error creating log folder:", err) return } // 初始化当前日志文件与锁 var currentLogFile *os.File var mu sync.Mutex var err error currentLogFile, err = os.OpenFile(logFolderPath+generateFilename(), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { fmt.Println("Error opening log file:", err) return } defer currentLogFile.Close() cmd := exec.Command("./ledplayer") // 无需sh -c,直接执行二进制更高效 // 创建管道用于子进程输出 stdoutPipe, err := cmd.StdoutPipe() if err != nil { fmt.Println("Error creating stdout pipe:", err) return } stderrPipe, err := cmd.StderrPipe() if err != nil { fmt.Println("Error creating stderr pipe:", err) return } // 启动goroutine读取stdout并写入当前日志 go func() { scanner := bufio.NewScanner(stdoutPipe) for scanner.Scan() { mu.Lock() _, _ = fmt.Fprintln(currentLogFile, scanner.Text()) mu.Unlock() } }() // 启动goroutine读取stderr并写入当前日志 go func() { scanner := bufio.NewScanner(stderrPipe) for scanner.Scan() { mu.Lock() _, _ = fmt.Fprintln(currentLogFile, scanner.Text()) mu.Unlock() } }() // 启动子进程 err = cmd.Start() if err != nil { fmt.Println("Error starting command:", err) return } fmt.Println("Command started successfully. Log stored in", generateFilename()) // 处理日志切换 for range ticker.C { mu.Lock() // 关闭旧文件 if err := currentLogFile.Close(); err != nil { fmt.Println("Error closing log file:", err) mu.Unlock() return } // 创建新文件 newFilename := generateFilename() newLogFile, err := os.OpenFile(logFolderPath+newFilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { fmt.Println("Error opening new log file:", err) mu.Unlock() return } // 更新当前文件 currentLogFile = newLogFile mu.Unlock() fmt.Println("Log file switched to", newFilename) } // 等待子进程结束 err = cmd.Wait() if err != nil { fmt.Println("Error waiting for command:", err) } } func generateFilename() string { return time.Now().Format("2006-01-02_15-04_05") + "_ledplayer.log" }
关键修改点说明
- 管道中转输出:用
StdoutPipe()和StderrPipe()替代直接绑定文件,让父进程掌控输出流向 - 并发安全:用
sync.Mutex保护日志文件的切换和写入,避免goroutine写入时文件被切换导致的错误 - 目录创建:增加
os.MkdirAll确保日志目录存在,避免文件创建失败 - 直接执行二进制:去掉
sh -c,直接执行./ledplayer更高效且减少不必要的shell开销
内容的提问来源于stack exchange,提问作者Sithu Soe
相关产品推荐
相关产品推荐

