golib/gweb/ginlog/recover_log.go

75 lines
1.9 KiB
Go

//
// recover_log.go
// Copyright (C) 2022 tiglog <me@tiglog.com>
//
// Distributed under terms of the MIT license.
//
package ginlog
import (
"errors"
"net"
"net/http"
"net/http/httputil"
"os"
"strings"
"time"
"git.hexq.cn/tiglog/golib/logger"
"github.com/gin-gonic/gin"
)
func GinRecover(logfile string) gin.HandlerFunc {
log := logger.New(logger.NewRotateBySize(logfile), logger.ErrorLevel)
defer log.Sync()
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.
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
var se *os.SyscallError
if errors.As(ne, &se) {
seStr := strings.ToLower(se.Error())
if strings.Contains(seStr, "broken pipe") ||
strings.Contains(seStr, "connection reset by peer") {
brokenPipe = true
}
}
}
httpRequest, _ := httputil.DumpRequest(c.Request, false)
headers := strings.Split(string(httpRequest), "\r\n")
for idx, header := range headers {
current := strings.Split(header, ":")
if current[0] == "Authorization" {
headers[idx] = current[0] + ": *"
}
}
headersToStr := strings.Join(headers, "\r\n")
if brokenPipe {
log.Error(c.Request.URL.String(),
logger.Any("err", err),
logger.String("headers", headersToStr),
logger.Stack("stack"),
)
// If the connection is dead, we can't write a status to it.
c.Error(err.(error)) // nolint: errcheck
c.Abort()
} else {
log.Error(c.Request.URL.String(),
logger.Any("err", err),
logger.String("headers", headersToStr),
logger.Stack("stack"),
logger.String("panicRecoveredTime", time.Now().Format(time.RFC3339)),
)
c.AbortWithStatus(http.StatusInternalServerError)
}
}
}()
c.Next()
}
}