If misused, error handling and logging in Go can become too verbose and tangled. If the two are decoupled, you can just pass the error forward (maybe add some context to it if necessary, or have specific error structs) and have it logged or sent to various systems (like New Relic) somewhere else in the code, not where you receive it from a function call.
One of the features I appreciate the most in Echo framework is the HTTPErrorHandler which can be customized to any needs. And combined with the recover middleware, error management becomes an easy task.
package main
import (
"errors"
"fmt"
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/labstack/gommon/log"
)
func main() {
server := echo.New()
server.Use(
middleware.Recover(), // Recover from all panics to always have your server up
middleware.Logger(), // Log everything to stdout
middleware.RequestID(), // Generate a request id on the HTTP response headers for identification
)
server.Debug = false
server.HideBanner = true
server.HTTPErrorHandler = func(err error, c echo.Context) {
// Take required information from error and context and send it to a service like New Relic
fmt.Println(c.Path(), c.QueryParams(), err.Error())
// Call the default handler to return the HTTP response
server.DefaultHTTPErrorHandler(err, c)
}
server.GET("/users", func(c echo.Context) error {
users, err := dbGetUsers()
if err != nil {
return err
}
return c.JSON(http.StatusOK, users)
})
server.GET("/posts", func(c echo.Context) error {
posts, err := dbGetPosts()
if err != nil {
return err
}
return c.JSON(http.StatusOK, posts)
})
log.Fatal(server.Start(":8088"))
}
func dbGetUsers() ([]string, error) {
return nil, errors.New("database error")
}
func dbGetPosts() ([]string, error) {
panic("an unhandled error occurred")
return nil, nil
}
Avoid logging in functions:
server.GET("/users", func(c echo.Context) error {
users, err := dbGetUsers()
if err != nil {
log.Errorf("cannot get users: %s", err)
return err
}
return c.JSON(http.StatusOK, users)
})
Keep the handler functions clean, check your errors and pass them forward.
Golang error handling can be clean if you handle it as a decoupled component instead of mixing it with the main logic.