Add logging across app and centralized error handling
This commit is contained in:
parent
3bbeab1199
commit
4c3f095109
10 changed files with 115 additions and 62 deletions
|
|
@ -6,6 +6,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
cgnolink.ConfigureLogger()
|
||||||
|
|
||||||
conf := cgnolink.NewConfig()
|
conf := cgnolink.NewConfig()
|
||||||
|
|
||||||
pool := database.Pool(conf)
|
pool := database.Pool(conf)
|
||||||
|
|
|
||||||
47
err/err.go
47
err/err.go
|
|
@ -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
25
errors/errors.go
Normal 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
1
go.mod
|
|
@ -13,6 +13,7 @@ require (
|
||||||
github.com/knadh/koanf v0.15.0
|
github.com/knadh/koanf v0.15.0
|
||||||
github.com/labstack/echo/v4 v4.2.1
|
github.com/labstack/echo/v4 v4.2.1
|
||||||
github.com/mitchellh/copystructure v1.1.1 // indirect
|
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/sys v0.0.0-20210309074719-68d13333faf2 // indirect
|
||||||
golang.org/x/text v0.3.5 // indirect
|
golang.org/x/text v0.3.5 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
|
|
|
||||||
3
go.sum
3
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/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.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
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/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/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=
|
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-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-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-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-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/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=
|
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package link
|
package link
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "cgnolink/err"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/jackc/pgx/v4/pgxpool"
|
"github.com/jackc/pgx/v4/pgxpool"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
|
@ -16,7 +15,7 @@ func redirectHandler(c echo.Context, pool *pgxpool.Pool) error {
|
||||||
|
|
||||||
link, err := serv.GetById(linkId)
|
link, err := serv.GetById(linkId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MapErrToHTTPErr(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Redirect(http.StatusSeeOther, link.RedirectURL.String())
|
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}}
|
serv := PgService{rep: PgRepository{pool: pool}}
|
||||||
|
|
||||||
if err = serv.Create(entity); err != nil {
|
if err = serv.Create(entity); err != nil {
|
||||||
return MapErrToHTTPErr(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.NoContent(http.StatusCreated)
|
return c.NoContent(http.StatusCreated)
|
||||||
|
|
@ -49,7 +48,7 @@ func retrievalByIdHandler(c echo.Context, pool *pgxpool.Pool) error {
|
||||||
|
|
||||||
l, err := serv.GetById(linkId)
|
l, err := serv.GetById(linkId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MapErrToHTTPErr(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, MapEntityToModel(l))
|
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)
|
links, err := serv.GetAll(limit, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MapErrToHTTPErr(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
models := make([]ResourceModel, len(links))
|
models := make([]ResourceModel, len(links))
|
||||||
|
|
@ -97,7 +96,7 @@ func removalHandler(c echo.Context, pool *pgxpool.Pool) error {
|
||||||
serv := PgService{rep: PgRepository{pool: pool}}
|
serv := PgService{rep: PgRepository{pool: pool}}
|
||||||
|
|
||||||
if err := serv.DeleteById(linkId); err != nil {
|
if err := serv.DeleteById(linkId); err != nil {
|
||||||
return MapErrToHTTPErr(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.NoContent(http.StatusNoContent)
|
return c.NoContent(http.StatusNoContent)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package link
|
package link
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "cgnolink/err"
|
apperrors "cgnolink/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
|
|
@ -18,15 +18,15 @@ type PgService struct {
|
||||||
func (s *PgService) Create(link *Link) error {
|
func (s *PgService) Create(link *Link) error {
|
||||||
existingLink, err := s.rep.FindById(link.Id)
|
existingLink, err := s.rep.FindById(link.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UnknownError{}
|
return apperrors.UnknownError{Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if existingLink != nil {
|
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 {
|
if err = s.rep.Save(link); err != nil {
|
||||||
return UnknownError{}
|
return apperrors.UnknownError{Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -35,11 +35,11 @@ func (s *PgService) Create(link *Link) error {
|
||||||
func (s *PgService) GetById(id string) (*Link, error) {
|
func (s *PgService) GetById(id string) (*Link, error) {
|
||||||
link, err := s.rep.FindById(id)
|
link, err := s.rep.FindById(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, UnknownError{}
|
return nil, apperrors.UnknownError{Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if link == nil {
|
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
|
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) {
|
func (s *PgService) GetAll(limit int, offset int) (Links, error) {
|
||||||
links, err := s.rep.FindAll(limit, offset)
|
links, err := s.rep.FindAll(limit, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, UnknownError{}
|
return nil, apperrors.UnknownError{Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
return links, nil
|
return links, nil
|
||||||
|
|
@ -56,7 +56,7 @@ func (s *PgService) GetAll(limit int, offset int) (Links, error) {
|
||||||
|
|
||||||
func (s *PgService) DeleteById(id string) error {
|
func (s *PgService) DeleteById(id string) error {
|
||||||
if err := s.rep.DeleteById(id); err != nil {
|
if err := s.rep.DeleteById(id); err != nil {
|
||||||
return UnknownError{}
|
return apperrors.UnknownError{Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
13
logger.go
Normal file
13
logger.go
Normal 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
34
middleware/logger.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
server.go
25
server.go
|
|
@ -1,17 +1,22 @@
|
||||||
package cgnolink
|
package cgnolink
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
apperrors "cgnolink/errors"
|
||||||
"cgnolink/link"
|
"cgnolink/link"
|
||||||
|
appmiddleware "cgnolink/middleware"
|
||||||
"github.com/jackc/pgx/v4/pgxpool"
|
"github.com/jackc/pgx/v4/pgxpool"
|
||||||
"github.com/knadh/koanf"
|
"github.com/knadh/koanf"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewServer(conf *koanf.Koanf, pool *pgxpool.Pool) *echo.Echo {
|
func NewServer(conf *koanf.Koanf, pool *pgxpool.Pool) *echo.Echo {
|
||||||
server := echo.New()
|
server := echo.New()
|
||||||
|
|
||||||
|
server.HTTPErrorHandler = errorHandler(server)
|
||||||
|
|
||||||
addMiddleware(server, conf)
|
addMiddleware(server, conf)
|
||||||
addHandlers(server, pool)
|
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) {
|
func addMiddleware(s *echo.Echo, conf *koanf.Koanf) {
|
||||||
s.Use(middleware.CORS())
|
s.Use(middleware.CORS())
|
||||||
|
|
||||||
s.Use(middleware.Logger())
|
s.Use(appmiddleware.Logger())
|
||||||
|
|
||||||
s.Use(middleware.JWTWithConfig(middleware.JWTConfig{
|
s.Use(middleware.JWTWithConfig(middleware.JWTConfig{
|
||||||
Skipper: func(ctx echo.Context) bool {
|
Skipper: func(ctx echo.Context) bool {
|
||||||
|
|
@ -43,3 +48,21 @@ func addHandlers(s *echo.Echo, pool *pgxpool.Pool) {
|
||||||
|
|
||||||
link.AddHandlers(s, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue