diff --git a/cmd/cgnolink/main.go b/cmd/cgnolink/main.go index b11807f..8bf4187 100644 --- a/cmd/cgnolink/main.go +++ b/cmd/cgnolink/main.go @@ -6,6 +6,8 @@ import ( ) func main() { + cgnolink.ConfigureLogger() + conf := cgnolink.NewConfig() pool := database.Pool(conf) diff --git a/err/err.go b/err/err.go deleted file mode 100644 index 3d61cb7..0000000 --- a/err/err.go +++ /dev/null @@ -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 - } -} diff --git a/errors/errors.go b/errors/errors.go new file mode 100644 index 0000000..122b338 --- /dev/null +++ b/errors/errors.go @@ -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 +} diff --git a/go.mod b/go.mod index 3280081..55f5c12 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 54d7f00..4811d70 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/link/handlers.go b/link/handlers.go index 6403f76..0e64a7a 100644 --- a/link/handlers.go +++ b/link/handlers.go @@ -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) diff --git a/link/service.go b/link/service.go index 34e5b01..80c45c5 100644 --- a/link/service.go +++ b/link/service.go @@ -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 diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..97a85f7 --- /dev/null +++ b/logger.go @@ -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}) +} diff --git a/middleware/logger.go b/middleware/logger.go new file mode 100644 index 0000000..b34af3c --- /dev/null +++ b/middleware/logger.go @@ -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 + } + } +} diff --git a/server.go b/server.go index c62ea7c..b658279 100644 --- a/server.go +++ b/server.go @@ -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) + } +}