Add logging across app and centralized error handling

This commit is contained in:
Andrey Chervyakov 2021-03-15 19:03:30 +06:00
parent 3bbeab1199
commit 4c3f095109
10 changed files with 115 additions and 62 deletions

View file

@ -6,6 +6,8 @@ import (
)
func main() {
cgnolink.ConfigureLogger()
conf := cgnolink.NewConfig()
pool := database.Pool(conf)

View file

@ -1,47 +0,0 @@
package err
import (
"github.com/labstack/echo/v4"
"net/http"
)
type NotFoundError struct {
Message string
}
func (err NotFoundError) Error() string {
return err.Message
}
type UnknownError struct {
Message string
}
func (err UnknownError) Error() string {
return err.Message
}
type AlreadyExistsError struct {
Message string
}
func (err AlreadyExistsError) Error() string {
return err.Message
}
func MapErrToHTTPErr(err interface{}) *echo.HTTPError {
switch v := err.(type) {
case NotFoundError:
return echo.NewHTTPError(http.StatusNotFound, v.Message)
case UnknownError:
if v.Message != "" {
return echo.NewHTTPError(http.StatusInternalServerError, v.Message)
} else {
return echo.NewHTTPError(http.StatusInternalServerError)
}
case AlreadyExistsError:
return echo.NewHTTPError(http.StatusBadRequest, v.Message)
default:
return nil
}
}

25
errors/errors.go Normal file
View file

@ -0,0 +1,25 @@
package errors
type NotFoundError struct {
Message string
}
func (err NotFoundError) Error() string {
return err.Message
}
type UnknownError struct {
Err error
}
func (err UnknownError) Error() string {
return err.Err.Error()
}
type AlreadyExistsError struct {
Message string
}
func (err AlreadyExistsError) Error() string {
return err.Message
}

1
go.mod
View file

@ -13,6 +13,7 @@ require (
github.com/knadh/koanf v0.15.0
github.com/labstack/echo/v4 v4.2.1
github.com/mitchellh/copystructure v1.1.1 // indirect
github.com/rs/zerolog v1.20.0
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 // indirect
golang.org/x/text v0.3.5 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect

3
go.sum
View file

@ -237,6 +237,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
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=
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
@ -363,6 +365,7 @@ golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View file

@ -1,7 +1,6 @@
package link
import (
. "cgnolink/err"
"encoding/json"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/labstack/echo/v4"
@ -16,7 +15,7 @@ func redirectHandler(c echo.Context, pool *pgxpool.Pool) error {
link, err := serv.GetById(linkId)
if err != nil {
return MapErrToHTTPErr(err)
return err
}
return c.Redirect(http.StatusSeeOther, link.RedirectURL.String())
@ -36,7 +35,7 @@ func creationHandler(c echo.Context, pool *pgxpool.Pool) error {
serv := PgService{rep: PgRepository{pool: pool}}
if err = serv.Create(entity); err != nil {
return MapErrToHTTPErr(err)
return err
}
return c.NoContent(http.StatusCreated)
@ -49,7 +48,7 @@ func retrievalByIdHandler(c echo.Context, pool *pgxpool.Pool) error {
l, err := serv.GetById(linkId)
if err != nil {
return MapErrToHTTPErr(err)
return err
}
return c.JSON(http.StatusOK, MapEntityToModel(l))
@ -80,7 +79,7 @@ func allRetrievalHandler(c echo.Context, pool *pgxpool.Pool) error {
links, err := serv.GetAll(limit, offset)
if err != nil {
return MapErrToHTTPErr(err)
return err
}
models := make([]ResourceModel, len(links))
@ -97,7 +96,7 @@ func removalHandler(c echo.Context, pool *pgxpool.Pool) error {
serv := PgService{rep: PgRepository{pool: pool}}
if err := serv.DeleteById(linkId); err != nil {
return MapErrToHTTPErr(err)
return err
}
return c.NoContent(http.StatusNoContent)

View file

@ -1,7 +1,7 @@
package link
import (
. "cgnolink/err"
apperrors "cgnolink/errors"
)
type Service interface {
@ -18,15 +18,15 @@ type PgService struct {
func (s *PgService) Create(link *Link) error {
existingLink, err := s.rep.FindById(link.Id)
if err != nil {
return UnknownError{}
return apperrors.UnknownError{Err: err}
}
if existingLink != nil {
return AlreadyExistsError{Message: "Link with given ID already exists."}
return apperrors.AlreadyExistsError{Message: "Link with given ID already exists."}
}
if err = s.rep.Save(link); err != nil {
return UnknownError{}
return apperrors.UnknownError{Err: err}
}
return nil
@ -35,11 +35,11 @@ func (s *PgService) Create(link *Link) error {
func (s *PgService) GetById(id string) (*Link, error) {
link, err := s.rep.FindById(id)
if err != nil {
return nil, UnknownError{}
return nil, apperrors.UnknownError{Err: err}
}
if link == nil {
return nil, NotFoundError{Message: "Link with given ID was not found."}
return nil, apperrors.NotFoundError{Message: "Link with given ID was not found."}
}
return link, nil
@ -48,7 +48,7 @@ func (s *PgService) GetById(id string) (*Link, error) {
func (s *PgService) GetAll(limit int, offset int) (Links, error) {
links, err := s.rep.FindAll(limit, offset)
if err != nil {
return nil, UnknownError{}
return nil, apperrors.UnknownError{Err: err}
}
return links, nil
@ -56,7 +56,7 @@ func (s *PgService) GetAll(limit int, offset int) (Links, error) {
func (s *PgService) DeleteById(id string) error {
if err := s.rep.DeleteById(id); err != nil {
return UnknownError{}
return apperrors.UnknownError{Err: err}
}
return nil

13
logger.go Normal file
View file

@ -0,0 +1,13 @@
package cgnolink
import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"os"
"time"
)
func ConfigureLogger() {
zerolog.TimeFieldFormat = time.RFC3339Nano
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
}

34
middleware/logger.go Normal file
View file

@ -0,0 +1,34 @@
package middleware
import (
"github.com/labstack/echo/v4"
"github.com/rs/zerolog/log"
"time"
)
func Logger() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (err error) {
req := c.Request()
res := c.Response()
start := time.Now()
if err = next(c); err != nil {
c.Error(err)
}
stop := time.Now()
log.Info().
Str("remote_ip", c.RealIP()).
Str("host", req.Host).
Str("uri", req.RequestURI).
Str("method", req.Method).
Str("path", req.URL.Path).
Str("user_agent", req.UserAgent()).
Int("status", res.Status).
Str("latency", stop.Sub(start).String()).
Send()
return
}
}
}

View file

@ -1,17 +1,22 @@
package cgnolink
import (
apperrors "cgnolink/errors"
"cgnolink/link"
appmiddleware "cgnolink/middleware"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/knadh/koanf"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/rs/zerolog/log"
"net/http"
)
func NewServer(conf *koanf.Koanf, pool *pgxpool.Pool) *echo.Echo {
server := echo.New()
server.HTTPErrorHandler = errorHandler(server)
addMiddleware(server, conf)
addHandlers(server, pool)
@ -21,7 +26,7 @@ func NewServer(conf *koanf.Koanf, pool *pgxpool.Pool) *echo.Echo {
func addMiddleware(s *echo.Echo, conf *koanf.Koanf) {
s.Use(middleware.CORS())
s.Use(middleware.Logger())
s.Use(appmiddleware.Logger())
s.Use(middleware.JWTWithConfig(middleware.JWTConfig{
Skipper: func(ctx echo.Context) bool {
@ -43,3 +48,21 @@ func addHandlers(s *echo.Echo, pool *pgxpool.Pool) {
link.AddHandlers(s, pool)
}
func errorHandler(s *echo.Echo) echo.HTTPErrorHandler {
return func(err error, c echo.Context) {
mappedErr := err
switch v := err.(type) {
case apperrors.NotFoundError:
mappedErr = echo.NewHTTPError(http.StatusNotFound, v.Error())
case apperrors.UnknownError:
log.Err(v.Err).Send()
mappedErr = echo.NewHTTPError(http.StatusInternalServerError)
case apperrors.AlreadyExistsError:
mappedErr = echo.NewHTTPError(http.StatusBadRequest, v.Error())
}
s.DefaultHTTPErrorHandler(mappedErr, c)
}
}