Golang实现后台进程运行及日志记录指南
Golang实现后台进程运行及日志记录指南 我想以后台进程的方式运行一个Perl脚本,并将其输出重定向到一个文件。
这是一个 print 脚本的示例:
#!/usr/bin/perl
$i = 10; while($i--) {print time()."\n"; sleep 5;}
以下是我运行它的方式:
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.OpenFile("/tmp/out.log", os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
fmt.Println("failed to open log file")
return
}
attr := os.ProcAttr{
Env: os.Environ(),
Files: []*os.File{
nil,
file,
file,
},
}
process, err := os.StartProcess("/tmp/print", []string{"/tmp/print"}, &attr)
if err != nil {
fmt.Println("failed to start process: %v", err)
return
}
err = process.Release()
if err != nil {
fmt.Println("failed to release: %v", err)
}
}
这个方法可以运行,但是 out.log 文件在脚本执行完成之前不会被写入内容。如果我使用 os.Stdout 而不是文件,那么输出会立即逐行打印出来。
由于这个脚本预计会运行很长时间,我希望能在日志文件中看到实际的输出。有什么方法可以实现这一点吗?
更多关于Golang实现后台进程运行及日志记录指南的实战教程也可以访问 https://www.itying.com/category-94-b0.html
你说得对。 我也用刷新标准输出的bash和python脚本测试过,代码按预期工作。 非常感谢!
更多关于Golang实现后台进程运行及日志记录指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Perl中,开启自动刷新:$| = 1 参见 perlvar - Perl 预定义变量 - Perldoc 浏览器
与Go代码无关,看起来没问题。实际问题是你的Perl脚本。我不是这门语言的专家,但我搜索了一些细节,我的猜测是,print 实际上不会立即将数据写入文件。由于你一直保持文件打开,数据会等待,直到资源被释放,以便以最少的系统调用次数将所有行写入文件。在这种情况下,你可以手动添加缓冲区 flush,这样你的打印内容就会实时添加到文件中。或者,每次你想写入内容时,重新打开文件进行追加。你可以查看这篇文章,并尝试将其添加到你的Perl代码中。
在Go中实现后台进程并实时记录日志,需要正确处理文件缓冲和进程输出流。以下是改进后的实现方案:
package main
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"sync"
"time"
)
func main() {
// 创建日志文件(使用追加模式)
file, err := os.OpenFile("/tmp/out.log",
os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
fmt.Printf("failed to open log file: %v\n", err)
return
}
defer file.Close()
// 创建带缓冲的写入器
writer := bufio.NewWriter(file)
defer writer.Flush()
// 创建命令
cmd := exec.Command("/tmp/print")
// 获取标准输出管道
stdout, err := cmd.StdoutPipe()
if err != nil {
fmt.Printf("failed to get stdout pipe: %v\n", err)
return
}
// 获取标准错误管道
stderr, err := cmd.StderrPipe()
if err != nil {
fmt.Printf("failed to get stderr pipe: %v\n", err)
return
}
// 启动进程
if err := cmd.Start(); err != nil {
fmt.Printf("failed to start process: %v\n", err)
return
}
// 使用WaitGroup等待goroutine完成
var wg sync.WaitGroup
wg.Add(2)
// 实时读取并写入标准输出
go func() {
defer wg.Done()
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
line := scanner.Text()
writer.WriteString(line + "\n")
writer.Flush() // 立即刷新缓冲区
fmt.Printf("STDOUT: %s\n", line) // 可选:控制台输出
}
}()
// 实时读取并写入标准错误
go func() {
defer wg.Done()
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
line := scanner.Text()
writer.WriteString("ERROR: " + line + "\n")
writer.Flush() // 立即刷新缓冲区
fmt.Printf("STDERR: %s\n", line) // 可选:控制台输出
}
}()
// 等待进程完成
if err := cmd.Wait(); err != nil {
fmt.Printf("process finished with error: %v\n", err)
}
// 等待所有goroutine完成
wg.Wait()
fmt.Println("Process completed, logs saved to /tmp/out.log")
}
如果需要完全后台运行(脱离终端),可以使用以下方法:
package main
import (
"fmt"
"os"
"os/exec"
"syscall"
)
func daemonize() {
// 创建守护进程
cmd := exec.Command("/tmp/print")
// 分离进程组
cmd.SysProcAttr = &syscall.SysProcAttr{
Setsid: true,
}
// 重定向输出到文件
logFile, _ := os.OpenFile("/tmp/out.log",
os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
defer logFile.Close()
cmd.Stdout = logFile
cmd.Stderr = logFile
// 启动并释放
if err := cmd.Start(); err != nil {
fmt.Printf("failed to start daemon: %v\n", err)
return
}
fmt.Printf("Daemon started with PID: %d\n", cmd.Process.Pid)
// 立即退出父进程,子进程继续在后台运行
os.Exit(0)
}
func main() {
daemonize()
}
对于长时间运行的进程,建议添加日志轮转功能:
package main
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"time"
)
type RotatingWriter struct {
currentFile *os.File
filePath string
maxSize int64
currentSize int64
}
func NewRotatingWriter(path string, maxSize int64) (*RotatingWriter, error) {
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return nil, err
}
stat, _ := f.Stat()
return &RotatingWriter{
currentFile: f,
filePath: path,
maxSize: maxSize,
currentSize: stat.Size(),
}, nil
}
func (rw *RotatingWriter) Write(p []byte) (n int, err error) {
if rw.currentSize+int64(len(p)) > rw.maxSize {
rw.rotate()
}
n, err = rw.currentFile.Write(p)
rw.currentSize += int64(n)
return
}
func (rw *RotatingWriter) rotate() {
rw.currentFile.Close()
// 重命名旧日志文件
timestamp := time.Now().Format("20060102_150405")
backupPath := rw.filePath + "." + timestamp
os.Rename(rw.filePath, backupPath)
// 创建新日志文件
f, _ := os.OpenFile(rw.filePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
rw.currentFile = f
rw.currentSize = 0
}
func main() {
writer, _ := NewRotatingWriter("/tmp/out.log", 10*1024*1024) // 10MB轮转
cmd := exec.Command("/tmp/print")
stdout, _ := cmd.StdoutPipe()
stderr, _ := cmd.StderrPipe()
cmd.Start()
// 同时复制stdout和stderr
go io.Copy(writer, stdout)
go io.Copy(writer, stderr)
cmd.Wait()
writer.currentFile.Close()
}
关键点:
- 使用
exec.Command替代os.StartProcess以获得更好的控制 - 通过管道实时读取进程输出
- 使用
bufio.Writer并手动调用Flush()确保立即写入 - 对于守护进程,使用
Setsid: true分离进程组 - 考虑日志轮转避免文件过大

