Golang中如何在代码异常或错误时发送邮件

Golang中如何在代码异常或错误时发送邮件 我想在应用程序出现任何错误时发送电子邮件。 包含文件名和行号的 .go 文件以及异常信息。

这将对我们有很大帮助。

3 回复

非常感谢。如果存在任何未处理的异常

更多关于Golang中如何在代码异常或错误时发送邮件的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我不推荐这样做。为什么不使用常规的日志记录呢?如果你的问题在于行号,并且有时需要花费很长时间来查找,我也遇到过同样的问题。

以下是我用于HTTP错误响应的代码,但你可以使用类似的方法,并总能立即找到错误的原因。

type ErrorResponse struct {
	Detail string `json:"detail"` // 这是我自己的注释,说明导致错误的原因。
	Error  string `json:"error"`
}

	userpermissions, err := app.Queries.GetPurePermissionsByUserId(context.Background(), user.ID)
	if err != nil {
		utils.RespondWithJSON(w, utils.ErrorResponse{
			Detail: "Error getting permissions for user",
			Error:  err.Error(),
		})
		return
	}

在Go中实现错误时发送邮件,可以通过自定义错误处理中间件或全局错误捕获机制来实现。以下是一个完整的示例,展示了如何在发生panic或错误时发送包含文件名、行号和错误信息的邮件:

package main

import (
    "fmt"
    "log"
    "net/smtp"
    "os"
    "runtime/debug"
    "strings"
    "time"
)

// 邮件配置结构体
type MailConfig struct {
    SMTPHost     string
    SMTPPort     string
    FromEmail    string
    FromPassword string
    ToEmails     []string
}

// 错误信息结构体
type ErrorInfo struct {
    Time     string
    File     string
    Line     int
    Function string
    Error    string
    Stack    string
}

var mailConfig = MailConfig{
    SMTPHost:     "smtp.gmail.com",
    SMTPPort:     "587",
    FromEmail:    "your-email@gmail.com",
    FromPassword: "your-password",
    ToEmails:     []string{"admin@example.com"},
}

// 发送邮件的函数
func sendErrorEmail(errorInfo ErrorInfo) error {
    auth := smtp.PlainAuth("", mailConfig.FromEmail, mailConfig.FromPassword, mailConfig.SMTPHost)

    subject := fmt.Sprintf("Subject: Application Error Alert - %s\n", time.Now().Format("2006-01-02 15:04:05"))
    mime := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
    
    body := fmt.Sprintf(`
<html>
<body>
<h2>应用程序错误报告</h2>
<p><strong>发生时间:</strong> %s</p>
<p><strong>文件位置:</strong> %s:%d</p>
<p><strong>函数名:</strong> %s</p>
<p><strong>错误信息:</strong> %s</p>
<h3>堆栈跟踪:</h3>
<pre>%s</pre>
</body>
</html>
`, errorInfo.Time, errorInfo.File, errorInfo.Line, errorInfo.Function, errorInfo.Error, errorInfo.Stack)

    msg := []byte(subject + mime + body)
    
    err := smtp.SendMail(
        mailConfig.SMTPHost+":"+mailConfig.SMTPPort,
        auth,
        mailConfig.FromEmail,
        mailConfig.ToEmails,
        msg,
    )
    
    return err
}

// 获取调用者信息的函数
func getCallerInfo(skip int) (file string, line int, function string) {
    pc, file, line, ok := runtime.Caller(skip)
    if !ok {
        return "unknown", 0, "unknown"
    }
    
    // 获取函数名
    fn := runtime.FuncForPC(pc)
    if fn != nil {
        function = fn.Name()
    }
    
    // 简化文件路径,只保留最后两级目录
    parts := strings.Split(file, "/")
    if len(parts) > 2 {
        file = strings.Join(parts[len(parts)-2:], "/")
    }
    
    return file, line, function
}

// 全局错误处理函数
func HandlePanic() {
    if r := recover(); r != nil {
        // 获取堆栈信息
        stack := debug.Stack()
        
        // 获取调用者信息(跳过两层:当前函数和panic调用)
        file, line, function := getCallerInfo(3)
        
        errorInfo := ErrorInfo{
            Time:     time.Now().Format("2006-01-02 15:04:05"),
            File:     file,
            Line:     line,
            Function: function,
            Error:    fmt.Sprintf("%v", r),
            Stack:    string(stack),
        }
        
        // 尝试发送邮件
        if err := sendErrorEmail(errorInfo); err != nil {
            log.Printf("发送错误邮件失败: %v", err)
        }
        
        // 记录到日志
        log.Printf("PANIC: %v\nFile: %s:%d\nFunction: %s\nStack:\n%s", 
            r, file, line, function, stack)
        
        // 重新panic,让程序终止或由上层处理
        panic(r)
    }
}

// 错误包装函数,用于普通错误
func HandleError(err error, skip int) error {
    if err != nil {
        file, line, function := getCallerInfo(skip)
        
        errorInfo := ErrorInfo{
            Time:     time.Now().Format("2006-01-02 15:04:05"),
            File:     file,
            Line:     line,
            Function: function,
            Error:    err.Error(),
            Stack:    string(debug.Stack()),
        }
        
        // 异步发送邮件,避免阻塞主流程
        go func() {
            if sendErr := sendErrorEmail(errorInfo); sendErr != nil {
                log.Printf("发送错误邮件失败: %v", sendErr)
            }
        }()
        
        log.Printf("ERROR: %v\nFile: %s:%d\nFunction: %s", 
            err, file, line, function)
    }
    return err
}

// 使用示例
func main() {
    // 设置全局panic处理
    defer HandlePanic()
    
    // 示例1: 触发panic
    func() {
        defer HandlePanic()
        panic("测试panic错误")
    }()
    
    // 示例2: 处理普通错误
    err := someFunction()
    if HandleError(err, 2) != nil {
        // 处理错误逻辑
    }
    
    // 示例3: 在HTTP服务器中使用
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        defer HandlePanic()
        
        // 业务逻辑
        if err := processRequest(r); HandleError(err, 3) != nil {
            http.Error(w, "内部服务器错误", http.StatusInternalServerError)
        }
    })
}

func someFunction() error {
    return fmt.Errorf("模拟业务错误")
}

func processRequest(r *http.Request) error {
    // 模拟处理错误
    return fmt.Errorf("请求处理失败")
}

// 中间件版本(用于HTTP服务)
func ErrorMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if r := recover(); r != nil {
                stack := debug.Stack()
                file, line, function := getCallerInfo(4)
                
                errorInfo := ErrorInfo{
                    Time:     time.Now().Format("2006-01-02 15:04:05"),
                    File:     file,
                    Line:     line,
                    Function: function,
                    Error:    fmt.Sprintf("%v", r),
                    Stack:    string(stack),
                }
                
                go sendErrorEmail(errorInfo)
                
                log.Printf("HTTP PANIC: %v\nFile: %s:%d\nFunction: %s", 
                    r, file, line, function)
                
                http.Error(w, "内部服务器错误", http.StatusInternalServerError)
            }
        }()
        
        next.ServeHTTP(w, r)
    })
}

这个实现提供了以下功能:

  1. 邮件发送:使用标准库的smtp包发送HTML格式的错误邮件
  2. 错误信息收集:自动捕获文件名、行号、函数名和完整堆栈跟踪
  3. 两种错误处理
    • HandlePanic():用于处理panic,会终止程序执行
    • HandleError():用于处理普通错误,异步发送邮件不阻塞主流程
  4. HTTP中间件ErrorMiddleware可以集成到HTTP服务器中
  5. 配置灵活:通过MailConfig结构体配置邮件服务器参数

使用时需要注意:

  • 需要替换邮件配置中的实际SMTP服务器信息
  • 对于Gmail,可能需要启用"不够安全的应用"或使用应用专用密码
  • 生产环境中建议将邮件发送改为异步队列处理,避免影响主程序性能
  • 可以考虑添加邮件发送失败的重试机制和限流控制
回到顶部