MENU

【Golang】进程监控器的错误报告发送

2025 年 12 月 08 日 •

引子

在上一篇Golang实战记中,我做了一个进程监控器,当服务器上的进程结束了,就会向电子邮箱发送一封邮件。

在这个基础上,加入一些必要的功能是值得考虑的,比如:

  1. 日志信息发送:程序运行过程的输出。
  2. 错误信息发送:如果程序运行到一半出错了,也可以将错误信息发送给邮箱。

日志功能的实现

为了使程序能够在更多的系统上运行,我没有采用命令名 > 文件名的方法,我们有更加灵活的实现方法,就是把标准输出和标准错误输出导入到日志里:

// Prepare the Log
raw_log_file := pkg.GetLogFile(info)
log_file, err := os.OpenFile(raw_log_file, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
if err != nil {
    panic(err)
}
defer log_file.Close()

...

// Output into the Log
cmd := exec.Command(command[0], command[1:]...)
cmd.Stdout = log_file
cmd.Stderr = log_file
err = cmd.Start()
if err != nil {
    panic(err)
}

其实,到这里我们已经解决了两个需求,因为我们将运行的输出和错误输出都定向到了日志中。

更加智能的错误信息

目前的主函数是这一个样子的:

package main

import (
    "fmt"
    "os"
    "os/exec"
    "profiler/pkg"
)

func main() {
    // Init
    var info pkg.Information
    pkg.GetTime(&info, "start")
    email_info := pkg.InitPrivateEmailInformation()

    // Prepare the Log
    raw_log_file := pkg.GetLogFile(info)
    log_file, err := os.OpenFile(raw_log_file, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
    if err != nil {
        panic(err)
    }
    defer log_file.Close()

    // Get the Command Name
    command := pkg.BuildCommand(os.Args, info)
    pkg.GetCommandName(&info)

    // Start the Process
    cmd := exec.Command(command[0], command[1:]...)
    cmd.Stdout = log_file
    cmd.Stderr = log_file
    err = cmd.Start()
    if err != nil {
        panic(err)
    }

    // Wait till end
    err = cmd.Wait()
    if err != nil {
        panic(err)
    }

    pkg.GetTime(&info, "end")
    content := pkg.InformationToString(info)
    fmt.Println(content)
    pkg.SendEmail(content, email_info.Sender, email_info.Recver, email_info.Port, email_info.Smtp_server, email_info.Password, raw_log_file)
}

那么接下来,我们可以实现更多的功能,比如:

  1. 可选择的错误信息发送:如果错误发生在调试阶段,那么就没必要发送邮件了;如果错误发生在无人看管的运行阶段,就需要发送邮件。
  2. 更加智能的信息:如果运行出错了,可以在邮件的标题中说明出错了。

对于错误信息发送,实现很简单,因为运行过程如果出错了,那么就会导致cmd.Wait()的返回err不为nil,那么就可以将这部分的代码修改为:

// Wait till end
err = cmd.Wait()

pkg.GetTime(&info, "end")
content := pkg.InformationToString(info)
fmt.Println(content)
pkg.SendEmail(content, email_info.Sender, email_info.Recver, email_info.Port, email_info.Smtp_server, email_info.Password, raw_log_file, err != nil)

然后将SendEmail()进行修改:

func SendEmail(content string, sender string, recver string, port int, smtp_server string, password string, attach string, is_failed bool) {
    e := email.NewEmail()
    e.From = sender
    e.To = []string{recver}
    if is_failed {
        e.Subject = "Error from Server Profiler"
    } else {
        e.Subject = "Success Information from Server Profiler"
    }
    e.Text = []byte(content)
    _, attach_err := e.AttachFile(attach)
    if attach_err != nil {
        panic(attach_err)
    }
    err := e.Send(smtp_server+":"+fmt.Sprint(port), smtp.PlainAuth("", sender, password, smtp_server))
    if err != nil {
        panic(err)
    }
}

就完成了功能的实现。

交叉编译

Golang的一个非常出色的特性就是简单的交叉编译功能,我使用MacOS或者Windows,如果想编译Linux下的可执行程序,只需要:

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build

这也是我喜欢Golang胜过C与C++的一个原因——便捷。