构建中间件,当发生panic错误时拦截panic并Recovery,飞书通知堆栈信息
包括 httpRequest、stack错误堆栈信息
Log middleware
package middleware
import (
"fmt"
"net"
"net/http/httputil"
"os"
"runtime/debug"
"strings"
"net/http"
"time"
"github.com/gin-gonic/gin"
"xxxx.com/infra/common/util/feishu"
"xxxx.com/infra/saplat/initialize"
"go.uber.org/zap"
)
const (
FSBot = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxx"
)
var (
AccessLogger = initialize.AccessLogger
ErrorLogger = initialize.ErrorLogger
)
// 记录access log的中间件
func AccessLoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
clientId := c.ClientIP()
method := c.Request.Method
userAgent := c.Request.UserAgent()
c.Next()
latency := time.Since(start)
// 记录access log
AccessLogger.Info(path,
zap.Int("status", c.Writer.Status()),
zap.String("method", method),
zap.String("path", path),
zap.String("query", query),
zap.String("client_ip", clientId),
zap.String("user_agent", userAgent),
zap.Duration("latency", latency),
)
// 错误处理和日志记录
if len(c.Errors) > 0 {
ErrorLogger.Error("Encountered error",
zap.String("path", path),
zap.String("client_ip", clientId),
zap.Error(c.Errors.ByType(gin.ErrorTypePrivate).Last().Err), // 获取最后一个错误
zap.Stack("stack"),
)
}
}
}
func RecoveryLoggerMiddleware(logger *zap.Logger, stack bool) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// 打印错误堆栈信息到控制台
fmt.Printf("panic: %v\n", err)
debug.PrintStack()
// Check for a broken connection, as it is not really a
// condition that warrants a panic stack trace.
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}
var errMsg string
if str, ok := err.(string); ok {
errMsg = str
}
errMsg = time.Now().Local().String() + "\n" + errMsg + "\n"
httpRequest, _ := httputil.DumpRequest(c.Request, false)
errMsg = errMsg + "\n" + string(httpRequest) + "\n"
if brokenPipe {
logger.Error(c.Request.URL.Path,
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
v4.SendTextMsgByAppID("chat_id", FSBot, "text", errMsg)
// If the connection is dead, we can't write a status to it.
c.Error(err.(error)) // nolint: errcheck
c.Abort()
return
}
if stack {
logger.Error("[Recovery from panic and Panic Stack]",
zap.Time("time", time.Now()),
zap.Any("error", err),
zap.String("request", string(httpRequest)),
zap.Stack("stack"), // 打印出错时的堆栈跟踪
)
errMsg = errMsg + "\n" + zap.Stack("stack").String + "\n"
v4.SendTextMsgByAppID("chat_id", FSBot, "text", errMsg)
} else {
logger.Error("[Recovery from panic]",
zap.Time("time", time.Now()),
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
v4.SendTextMsgByAppID("chat_id", FSBot, "text", errMsg)
}
c.AbortWithStatus(http.StatusInternalServerError)
}
}()
c.Next()
}
}
引入middleware
r := gin.Default()
/*
替代gin.Recovery() 实现panic拦截以及日志记录
*/
r.Use(middleware.AccessLoggerMiddleware(), middleware.RecoveryLoggerMiddleware(ErrorLogger, true))