You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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"
}

关键修改点说明

  1. 管道中转输出:用StdoutPipe()和StderrPipe()替代直接绑定文件,让父进程掌控输出流向
  2. 并发安全:用sync.Mutex保护日志文件的切换和写入,避免goroutine写入时文件被切换导致的错误
  3. 目录创建:增加os.MkdirAll确保日志目录存在,避免文件创建失败
  4. 直接执行二进制:去掉sh -c,直接执行./ledplayer更高效且减少不必要的shell开销

内容的提问来源于stack exchange,提问作者Sithu Soe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 16:57:55