package middleware import ( "fmt" "net/http" "runtime/debug" "strings" "git.dev.m-and-m.ovh/mderasse/gocommon/commonctx" "github.com/sirupsen/logrus" ) type recoveryFuncSignature func(w http.ResponseWriter, r *http.Request) type recoveryMiddleware struct { handler http.Handler respFunc recoveryFuncSignature } func (rm *recoveryMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { // log the error log := commonctx.GetLoggerWithFields(r.Context(), logrus.Fields{ "error": err, "stacktrace": fmt.Sprintf("%v\n%s", err, getStackrace()), }) if log == nil { fmt.Print("Impossible to log the panic as the logger is not part of the context\n") } else { log.Error("A Panic happened and has been caught") } // execute the recovery function rm.respFunc(w, r) } }() rm.handler.ServeHTTP(w, r) } func getStackrace() string { stackLines := strings.Split(string(debug.Stack()), "\n") // removing a few first line as they are either that function or the middleware return strings.Join(stackLines[9:], "\n") } // NewRecoveryMiddleware will declare a new middleware on the provided handler. It will recover any panic and log it. // By default, the middleware will return a default error object in json with a 500 Http code. That can be overide by providing a recoveryFunc. func NewRecoveryMiddleware(h http.Handler, recoveryFunc recoveryFuncSignature) http.Handler { recovery := &recoveryMiddleware{ handler: h, respFunc: recoveryFunc, } if recovery.respFunc == nil { recovery.respFunc = func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusInternalServerError) w.Write([]byte( fmt.Sprintf("{\"code\":%d,\"message\":\"An error happened during the execution of your request.\"}", http.StatusInternalServerError), )) } } return recovery }