feat(middlewares): Add stacktrace in recovery log
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Matthieu 'JP' DERASSE 2023-01-15 19:42:39 +00:00
parent 7bd178c462
commit 2259c587e1
Signed by: mderasse
GPG Key ID: 55141C777B16A705
6 changed files with 33 additions and 11 deletions

View File

@ -13,5 +13,18 @@ func AddMainLogger(ctx context.Context, logger *logrus.Entry) context.Context {
} }
func GetLogger(ctx context.Context) *logrus.Entry { func GetLogger(ctx context.Context) *logrus.Entry {
return ctx.Value(LoggerKey).(*logrus.Entry) if log := ctx.Value(LoggerKey); log != nil {
return log.(*logrus.Entry)
}
return nil
}
func GetLoggerWithFields(ctx context.Context, fields logrus.Fields) *logrus.Entry {
if log := ctx.Value(LoggerKey); log != nil {
log := log.(*logrus.Entry)
log = log.WithFields(fields)
AddMainLogger(ctx, log)
return log
}
return nil
} }

View File

@ -19,7 +19,7 @@ import (
const envPrefix = "LOG_" const envPrefix = "LOG_"
// CONFIG_FILE is the default file that will be searched to apply configuration from the filesystem. // CONFIG_FILE is the default file that will be searched to apply configuration from the filesystem.
const defaultConfigFile = "log.yaml" const defaultConfigFile = "conf/log.yaml"
// SECRET_NAME is the default name of the secret that will be searched in vault. // SECRET_NAME is the default name of the secret that will be searched in vault.
const defaultSecretName = "log" const defaultSecretName = "log"

View File

@ -11,7 +11,6 @@ import (
type ConfigStruct struct { type ConfigStruct struct {
Host string `yaml:"host"` Host string `yaml:"host"`
Port int `yaml:"port"` Port int `yaml:"port"`
ExtrasFields map[string]interface{} `yaml:"extra_fields"`
} }
// IsValid will check that the Gelf configuration is valid. // IsValid will check that the Gelf configuration is valid.

View File

@ -19,7 +19,7 @@ func NewHook(c *ConfigStruct) (logrus.Hook, error) {
hook := graylog.NewAsyncGraylogHook( hook := graylog.NewAsyncGraylogHook(
fmt.Sprintf("%s:%d", c.Host, c.Port), fmt.Sprintf("%s:%d", c.Host, c.Port),
c.ExtrasFields, nil,
) )
defer hook.Flush() defer hook.Flush()

View File

@ -3,8 +3,11 @@ package middleware
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"runtime/debug"
"strings"
"git.dev.m-and-m.ovh/mderasse/gocommon/commonctx" "git.dev.m-and-m.ovh/mderasse/gocommon/commonctx"
"github.com/sirupsen/logrus"
) )
type recoveryFuncSignature func(w http.ResponseWriter, r *http.Request) type recoveryFuncSignature func(w http.ResponseWriter, r *http.Request)
@ -17,13 +20,14 @@ type recoveryMiddleware struct {
func (rm *recoveryMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (rm *recoveryMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
// log the error // log the error
log := commonctx.GetLogger(r.Context()) log := commonctx.GetLoggerWithFields(r.Context(), logrus.Fields{
"error": err,
"stacktrace": fmt.Sprintf("%v\n%s", err, getStackrace()),
})
if log == nil { if log == nil {
fmt.Print("Impossible to log the panic as the logger is not part of the context\n") fmt.Print("Impossible to log the panic as the logger is not part of the context\n")
} else { } else {
// Probably have to catch a stacktrace + Info about the error
log.Error("A Panic happened and has been caught") log.Error("A Panic happened and has been caught")
} }
// execute the recovery function // execute the recovery function
@ -33,9 +37,15 @@ func (rm *recoveryMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request)
rm.handler.ServeHTTP(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. // 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. // 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) *recoveryMiddleware { func NewRecoveryMiddleware(h http.Handler, recoveryFunc recoveryFuncSignature) http.Handler {
recovery := &recoveryMiddleware{ recovery := &recoveryMiddleware{
handler: h, handler: h,
respFunc: recoveryFunc, respFunc: recoveryFunc,

View File

@ -18,7 +18,7 @@ import (
const envPrefix = "TRACING_" const envPrefix = "TRACING_"
// CONFIG_FILE is the default file that will be searched to apply configuration from the filesystem. // CONFIG_FILE is the default file that will be searched to apply configuration from the filesystem.
const defaultConfigFile = "tracing.yaml" const defaultConfigFile = "conf/tracing.yaml"
// SECRET_NAME is the default name of the secret that will be searched in vault. // SECRET_NAME is the default name of the secret that will be searched in vault.
const defaultSecretName = "tracing" const defaultSecretName = "tracing"