Files
2026-09-16 00:23:49 +08:00

54 lines
1.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package middleware
import (
"errors"
"fmt"
"log/slog"
"net"
"net/http"
"os"
"runtime/debug"
"strings"
"github.com/gin-gonic/gin"
)
// Recovery 替代 gin.Recovery():panic 恢复语义保持一致(客户端断开类 panic 仅记录并中止,
// 其余 panic 返回 500 Internal Server Error),但 panic 与堆栈统一走 slog 结构化输出,
// 与全站 JSON 日志(含 request_id / trace_id)对齐,避免默认实现写 stderr 非结构化日志。
func Recovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
ctx := c.Request.Context()
if isBrokenPipe(r) {
// 连接已断开,无法再写响应:仅记录并中止。
slog.ErrorContext(ctx, "GIN_PANIC_BROKEN_PIPE",
"method", c.Request.Method, "path", c.Request.URL.Path, "recover", fmt.Sprint(r))
c.Abort()
return
}
slog.ErrorContext(ctx, "GIN_PANIC",
"method", c.Request.Method, "path", c.Request.URL.Path, "ip", c.ClientIP(),
"recover", fmt.Sprint(r), "stack", string(debug.Stack()))
c.AbortWithStatus(http.StatusInternalServerError)
}
}()
c.Next()
}
}
// isBrokenPipe 判定 panic 是否源于客户端断开(broken pipe / connection reset),与 gin.Recovery 判定一致。
func isBrokenPipe(r any) bool {
ne, ok := r.(*net.OpError)
if !ok {
return false
}
var se *os.SyscallError
if !errors.As(ne, &se) {
return false
}
seStr := strings.ToLower(se.Error())
return strings.Contains(seStr, "broken pipe") || strings.Contains(seStr, "connection reset by peer")
}