Golang应用程序在终端退出时如何发送邮件

Golang应用程序在终端退出时如何发送邮件 大家好,

我正在尝试编写一个代码,让我的Go语言应用程序使用“github.com/jordan-wright/email”这个库来发送电子邮件。

当运行该应用程序的终端或iTERM被关闭时,将发送邮件。

以下是我能够获取到的所有信息,并据此编写了以下代码。

每当终端或iTerm退出时,会发送一个SIGHUP信号,如果我们处理这个信号,就可以用它来执行一些操作。

package main
import (
    "fmt"
    "github.com/jordan-wright/email"
    "io/ioutil"
    "net/smtp"
    "os"
    "os/signal"
    "syscall"
)



func main() {


    c := make(chan os.Signal)
    done := make(chan bool)
    signal.Notify(c, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGHUP)

    go handleInterrupts(c,done)
    go execute1(done)
   
    //performing  some operation .......
}

func handleInterrupts(c chan os.Signal,done chan bool) {
    //signal := <-c
    signal := <-c
    fmt.Println("Signal is :=", signal.String())
    done <- true
}


func execute1(done chan bool) {
    <- done

    e := email.NewEmail()
    e.From = "Harshit Singhvi <harshitsinghvi@XXXXX.com>"
    e.To = []string{"harshit_singhvi@XXXX.com"}
    e.Subject = "Test Email"
    e.Text = []byte("Text Body is, of course, supported!")
    e.AttachFile("files/files.go")
    e.HTML = []byte("<h1>Demo</h1>")
    auth := smtp.PlainAuth("", "XXXXXX", "XXXXX", "XXXXX")
    e.Send("mail.XXXXX.com:25",auth)

    ioutil.WriteFile("output.log", []byte("Hello"), 0644)
    os.Exit(1)
}

当使用Ctrl + C中断程序时,邮件可以成功发送。

然而,每当终端关闭时,代码确实捕获到了SIGHUP信号,但邮件却从未发送出去。

请问有谁能告诉我,当运行上述代码的终端或iTerm被退出或突然终止时,我该如何发送电子邮件?


更多关于Golang应用程序在终端退出时如何发送邮件的实战教程也可以访问 https://www.itying.com/category-94-b0.html

21 回复

是的,这是由系统决定的,而不是你的程序。

更多关于Golang应用程序在终端退出时如何发送邮件的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


嗯。你尝试过在退出部分使用 defer 吗?

你尝试过使用 nohupdisownscreen 来运行你的应用吗?

因此,在这种情况下,无法控制运行Go程序的父程序(即终端)。

这只是我脑海中的一个问题,我正在尝试把它编码实现。

您可以监控进程的PID,但这更多是运维而非编程范畴的问题。

感谢您的回复 @NobbZ

我们能否在子进程终止前执行一段时间的操作。

我正在通过简单的 go run 命令运行我的应用程序。

go run main.go

你好,乔治,这里不能使用 monit,因为我只想在我的程序运行时终端被突然关闭的情况下发送邮件。

@samfrmd : 我想在终端被意外关闭时执行一个特定的操作。你认为 defer 在这里能起作用吗?

当父进程终止时,其子进程也会随之终止。某些终端模拟器可能会给 shell 一些时间进行清理关闭,而另一些则不会。

Monit 用于需要持续运行的应用。在这种情况下,应用仅在用户需要时才会执行,因此我认为无法为此类场景配置 Monit。

您的程序在持续运行时是存活的。一旦被系统终止,程序及其所有子进程都将被关闭,因此无法处理未来的函数。

为什么您需要那个程序在终端意外关闭时发送邮件?

为什么它不需要在正常关机或电源中断(或电源恢复)时发送邮件?

我不明白这样做有什么意义,如果终端关闭了,应用程序也会随之关闭。通过文件、进程ID或其他任何方式监控它们,都可以触发事件并发送邮件…

我的应用程序是一个菜单驱动的应用程序,需要在不同层级获取用户输入,因此我无法通过 nohup 将其作为后台进程运行。

是否有其他方法来处理父进程(在这种情况下是终端)的关闭?

不。

如果终端被突然终止,你的程序将如何被终止取决于你的终端,大多数终端会像执行 kill -9 $pid 一样杀死它们的子进程。你的程序没有机会处理这些事件。

// 代码示例:处理终端退出时的邮件发送逻辑
func handleTerminalExit() {
    // 当运行Golang应用程序的终端退出时发送邮件
    // 具体实现逻辑...
}

请了解 nohupdisownscreen 命令。这些命令可以确保你的应用程序在终端中按下 ctrl+c 后继续运行。

正如我上面所说,你可以使用专门为此设计的工具来监控你的应用程序。例如在Linux上,有一个名为monit的程序,它可以根据一组规则来启动和重启一个应用程序或进程,而无需考虑你是如何或从哪里启动的,或者进程为何关闭。你还可以在事件发生时发送电子邮件等等。但这属于应用程序管理范畴,而非编程问题。

package main

import (
    "fmt"
    "log"
    "net/smtp"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/jordan-wright/email"
)

func main() {
    // 创建信号通道
    sigChan := make(chan os.Signal, 1)
    done := make(chan bool, 1)
    
    // 注册需要捕获的信号
    signal.Notify(sigChan, 
        syscall.SIGHUP,  // 终端关闭
        syscall.SIGINT,  // Ctrl+C
        syscall.SIGTERM, // 终止信号
        syscall.SIGQUIT, // Ctrl+\
    )

    // 启动信号处理goroutine
    go func() {
        sig := <-sigChan
        fmt.Printf("接收到信号: %v\n", sig)
        
        // 发送邮件
        if err := sendTerminationEmail(); err != nil {
            log.Printf("发送邮件失败: %v", err)
        } else {
            fmt.Println("邮件发送成功")
        }
        
        done <- true
    }()

    // 模拟应用程序运行
    fmt.Println("应用程序运行中...")
    fmt.Println("尝试关闭终端或按Ctrl+C触发邮件发送")
    
    // 等待信号
    <-done
    fmt.Println("应用程序退出")
}

func sendTerminationEmail() error {
    // 创建邮件
    e := email.NewEmail()
    e.From = "应用程序 <sender@example.com>"
    e.To = []string{"recipient@example.com"}
    e.Subject = "应用程序终端关闭通知"
    e.Text = []byte(fmt.Sprintf("应用程序于 %s 因终端关闭而退出", time.Now().Format(time.RFC3339)))
    e.HTML = []byte(fmt.Sprintf(`
        <h1>应用程序终止通知</h1>
        <p>应用程序于 <strong>%s</strong> 因终端关闭而退出</p>
        <p>请检查应用程序状态</p>
    `, time.Now().Format(time.RFC3339)))

    // 配置SMTP认证(根据实际情况修改)
    auth := smtp.PlainAuth("", "username", "password", "smtp.example.com")
    
    // 设置超时并发送邮件
    return e.Send("smtp.example.com:587", auth)
}

对于终端关闭时邮件发送失败的问题,可以添加以下改进:

package main

import (
    "context"
    "fmt"
    "log"
    "net/smtp"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/jordan-wright/email"
)

func main() {
    // 使用带缓冲的通道防止阻塞
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)

    // 创建带超时的context
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    // 等待信号
    go func() {
        sig := <-sigChan
        log.Printf("接收到终止信号: %v", sig)
        
        // 在goroutine中发送邮件,避免阻塞信号处理
        go func() {
            if err := sendEmailWithRetry(ctx); err != nil {
                log.Printf("最终发送邮件失败: %v", err)
            }
            os.Exit(0)
        }()
        
        // 给邮件发送留出时间
        time.Sleep(2 * time.Second)
    }()

    // 主程序逻辑
    for {
        select {
        case <-ctx.Done():
            return
        default:
            time.Sleep(1 * time.Second)
        }
    }
}

func sendEmailWithRetry(ctx context.Context) error {
    maxRetries := 3
    for i := 0; i < maxRetries; i++ {
        select {
        case <-ctx.Done():
            return ctx.Err()
        default:
            if err := sendEmail(); err == nil {
                return nil
            }
            time.Sleep(time.Duration(i+1) * time.Second)
        }
    }
    return fmt.Errorf("发送邮件重试%d次后失败", maxRetries)
}

func sendEmail() error {
    e := email.NewEmail()
    e.From = "监控系统 <monitor@example.com>"
    e.To = []string{"admin@example.com"}
    e.Subject = "终端关闭警报"
    e.Text = []byte(fmt.Sprintf("终端于 %s 关闭", time.Now().Format("2006-01-02 15:04:05")))
    
    auth := smtp.PlainAuth("", "user", "pass", "smtp.example.com")
    return e.Send("smtp.example.com:587", auth)
}

关键改进点:

  1. 使用带缓冲的信号通道
  2. 添加超时控制
  3. 实现重试机制
  4. 在独立的goroutine中发送邮件避免阻塞
  5. 确保有足够的时间完成邮件发送
回到顶部