ente/server/pkg/middleware/recover.go

89 lines
2.6 KiB
Go
Raw Normal View History

2024-03-01 08:07:01 +00:00
package middleware
import (
"errors"
"fmt"
"net"
"net/http"
"net/http/httputil"
"os"
"runtime/debug"
"strings"
"syscall"
"github.com/gin-contrib/requestid"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
// PanicRecover is similar to Gin's CustomRecoveryWithWriter but with custom logger.
// There's no easy way to plugin application logger instance & log custom attributes (like requestID)
func PanicRecover() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// Check for a broken connection, as it is not really a
// condition that warrants a panic stack trace.
//
// Newer versions of gin might fix this (the PR is not yet
// merged as on writing):
// https://github.com/gin-gonic/gin/pull/2150
var brokenPipe bool
// Legacy check, not sure if it ever worked. Retaining this, can
// remove both when the gin PR is merged.
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
}
}
}
// Newer check. Also untested.
if !brokenPipe {
if re, ok := err.(error); ok {
if errors.Is(re, syscall.EPIPE) {
brokenPipe = true
}
}
}
httpRequest, _ := httputil.DumpRequest(c.Request, false)
requestData := strings.Split(string(httpRequest), "\r\n")
for idx, header := range requestData {
current := strings.Split(header, ":")
if current[0] == "Authorization" || current[0] == "X-Auth-Token" {
requestData[idx] = current[0] + ": *"
}
}
reqDataWithoutAuthHeaders := strings.Join(requestData, "\r\n")
var logWithAttributes = log.WithFields(log.Fields{
"request_data": reqDataWithoutAuthHeaders,
"req_id": requestid.Get(c),
"req_uri": c.Request.URL.Path,
"panic": err,
"broken_pipe": brokenPipe,
"stack": string(debug.Stack()),
})
if brokenPipe {
log.Warn("Panic Recovery: Broken pipe")
// If the connection is dead, we can't write a status to it.
c.Error(err.(error)) // nolint: errcheck
c.Abort()
return
}
if fmt.Sprintf("%v", err) == "client disconnected" {
// https://github.com/gin-gonic/gin/issues/2279#issuecomment-768349478
logWithAttributes.Warn("Client request cancelled")
c.Request.Context().Done()
} else {
logWithAttributes.Error("Recovery from Panic")
c.AbortWithStatus(http.StatusInternalServerError)
}
}
}()
c.Next()
}
}