# HG changeset patch # User Christophe de Vienne <christophe@cdevienne.info> # Date 1602692921 -7200 # Wed Oct 14 18:28:41 2020 +0200 # Node ID 1ed99df25b0ed1e16c0dab292edbcde5c2e01627 # Parent 9363ce22972fae5993e1538d91fae4bf08482c40 Add panic and log middlewares diff --git a/go.mod b/go.mod --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ github.com/golang-migrate/migrate/v4 v4.12.2 github.com/jmoiron/sqlx v1.2.1-0.20200615141059-0794cb1f47ee github.com/json-iterator/go v1.1.6 + github.com/justinas/alice v1.2.0 github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 github.com/orus-io/go-flags v1.4.0 github.com/rs/zerolog v1.19.0 diff --git a/go.sum b/go.sum --- a/go.sum +++ b/go.sum @@ -347,6 +347,8 @@ github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= +github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= @@ -445,6 +447,7 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= diff --git a/log_middlewares.go b/log_middlewares.go new file mode 100644 --- /dev/null +++ b/log_middlewares.go @@ -0,0 +1,41 @@ +package orusapi + +import ( + "net/http" + "time" + + "github.com/justinas/alice" + "github.com/rs/zerolog" + "github.com/rs/zerolog/hlog" +) + +// LogStack ... +func LogStack(log zerolog.Logger, status4XXLogLevel zerolog.Level) []alice.Constructor { + return []alice.Constructor{ + hlog.NewHandler(log), + CatchPanics, + hlog.RemoteAddrHandler("ip"), + hlog.UserAgentHandler("user_agent"), + hlog.RefererHandler("referer"), + hlog.RequestIDHandler("req_id", "Request-Id"), + hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) { + log := hlog.FromRequest(r) + var event *zerolog.Event + if status >= http.StatusInternalServerError { + event = log.Error() + } else if status >= http.StatusBadRequest { + event = log.WithLevel(status4XXLogLevel) + } else { + event = log.Info() + } + event. + Dict("request", zerolog.Dict(). + Str("method", r.Method). + Str("url", r.URL.String())). + Int("status", status). + Int("size", size). + Dur("duration", duration). + Msg(http.StatusText(status)) + }), + } +} diff --git a/panic_middleware.go b/panic_middleware.go new file mode 100644 --- /dev/null +++ b/panic_middleware.go @@ -0,0 +1,42 @@ +package orusapi + +import ( + "fmt" + "net/http" + "runtime/debug" + + "github.com/getsentry/sentry-go" + jsoniter "github.com/json-iterator/go" + "github.com/rs/zerolog" +) + +// CatchPanics catches the panics and log them as errors +func CatchPanics(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + defer func() { + if r := recover(); r != nil { + log := zerolog.Ctx(req.Context()) + + exc := zerolog.Dict(). + Str("value", fmt.Sprintf("%#v", r)). + Str("type", fmt.Sprintf("%T", r)) + + stack := sentry.NewStacktrace() + stack.Frames = stack.Frames[:len(stack.Frames)-1] + b, err := jsoniter.Marshal(stack) + if err != nil { + log.Err(err).Msg("Could not marshal stacktrace, fallback to plain debug.Stack()") + b = debug.Stack() + exc = exc.Str("plainstack", string(b)) + } else { + exc = exc.RawJSON("stacktrace", b) + } + + log.Error().Dict("exception", exc).Msg("Caught a panic") + + rw.WriteHeader(http.StatusInternalServerError) + } + }() + next.ServeHTTP(rw, req) + }) +}