feat: logger, middleware
This commit is contained in:
@@ -15,14 +15,17 @@ func (app *application) failedValidationResponse(w http.ResponseWriter, r *http.
|
|||||||
// book we'll upgrade this to use structured logging, and record additional information
|
// book we'll upgrade this to use structured logging, and record additional information
|
||||||
// about the request including the HTTP method and URL.
|
// about the request including the HTTP method and URL.
|
||||||
func (app *application) logError(r *http.Request, err error) {
|
func (app *application) logError(r *http.Request, err error) {
|
||||||
app.logger.Println(err)
|
app.logger.PrintError(err, map[string]string{
|
||||||
|
"request_method": r.Method,
|
||||||
|
"request_url": r.URL.String(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// The errorResponse() method is a generic helper for sending JSON-formatted error
|
// The errorResponse() method is a generic helper for sending JSON-formatted error
|
||||||
// messages to the client with a given status code. Note that we're using an interface{}
|
// messages to the client with a given status code. Note that we're using an interface{}
|
||||||
// type for the message parameter, rather than just a string type, as this gives us
|
// type for the message parameter, rather than just a string type, as this gives us
|
||||||
// more flexibility over the values that we can include in the response.
|
// more flexibility over the values that we can include in the response.
|
||||||
func (app *application) errorResponse(w http.ResponseWriter, r *http.Request, status int, message interface{}) {
|
func (app *application) errorResponse(w http.ResponseWriter, r *http.Request, status int, message any) {
|
||||||
env := envelope{"error": message}
|
env := envelope{"error": message}
|
||||||
|
|
||||||
// Write the response using the writeJSON() helper. If this happens to return an
|
// Write the response using the writeJSON() helper. If this happens to return an
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
"greenlight.debuggingjon.dev/internal/data"
|
"greenlight.debuggingjon.dev/internal/data"
|
||||||
|
"greenlight.debuggingjon.dev/internal/jsonlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Declare a string containing the application version number. Later in the book we'll
|
// Declare a string containing the application version number. Later in the book we'll
|
||||||
@@ -41,17 +42,20 @@ type config struct {
|
|||||||
// logger, but it will grow to include a lot more as our build progresses.
|
// logger, but it will grow to include a lot more as our build progresses.
|
||||||
type application struct {
|
type application struct {
|
||||||
config config
|
config config
|
||||||
logger *log.Logger
|
logger *jsonlog.Logger
|
||||||
models data.Models
|
models data.Models
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
logger := log.New(os.Stdout, "", log.Ldate|log.Ltime)
|
|
||||||
var cfg config
|
var cfg config
|
||||||
|
|
||||||
|
// Initialize a new jsonlog.Logger which writes any messages *at or above* the INFO
|
||||||
|
// severity level to the standard out stream.
|
||||||
|
logger := jsonlog.New(os.Stdout, jsonlog.LevelInfo)
|
||||||
|
|
||||||
err := godotenv.Load()
|
err := godotenv.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal(err)
|
logger.PrintFatal(err, nil)
|
||||||
}
|
}
|
||||||
dsn := os.Getenv("DATABASE_DSN")
|
dsn := os.Getenv("DATABASE_DSN")
|
||||||
|
|
||||||
@@ -73,7 +77,7 @@ func main() {
|
|||||||
// application immediately.
|
// application immediately.
|
||||||
db, err := openDB(cfg)
|
db, err := openDB(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal(err)
|
logger.PrintFatal(err, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defer a call to db.Close() so that the connection pool is closed before the
|
// Defer a call to db.Close() so that the connection pool is closed before the
|
||||||
@@ -85,7 +89,7 @@ func main() {
|
|||||||
}()
|
}()
|
||||||
// Also log a message to say that the connection pool has been successfully
|
// Also log a message to say that the connection pool has been successfully
|
||||||
// established.
|
// established.
|
||||||
logger.Printf("database connection pool established")
|
logger.PrintInfo("database connection pool established", nil)
|
||||||
|
|
||||||
app := &application{
|
app := &application{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
@@ -98,13 +102,17 @@ func main() {
|
|||||||
Addr: fmt.Sprintf(":%d", cfg.port),
|
Addr: fmt.Sprintf(":%d", cfg.port),
|
||||||
Handler: app.routes(),
|
Handler: app.routes(),
|
||||||
IdleTimeout: time.Minute,
|
IdleTimeout: time.Minute,
|
||||||
|
ErrorLog: log.New(logger, "", 0),
|
||||||
ReadTimeout: 10 * time.Second,
|
ReadTimeout: 10 * time.Second,
|
||||||
WriteTimeout: 30 * time.Second,
|
WriteTimeout: 30 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Printf("starting %s server on %s", cfg.env, srv.Addr)
|
logger.PrintInfo("starting server", map[string]string{
|
||||||
|
"addr": srv.Addr,
|
||||||
|
"env": cfg.env,
|
||||||
|
})
|
||||||
err = srv.ListenAndServe()
|
err = srv.ListenAndServe()
|
||||||
logger.Fatal(err)
|
logger.PrintFatal(err, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func openDB(cfg config) (*sql.DB, error) {
|
func openDB(cfg config) (*sql.DB, error) {
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (app *application) recoverPanic(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Create a deferred function (which will always be run in the event of a panic
|
||||||
|
// as Go unwinds the stack).
|
||||||
|
defer func() {
|
||||||
|
// Use the builtin recover function to check if there has been a panic or
|
||||||
|
// not.
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
// If there was a panic, set a "Connection: close" header on the
|
||||||
|
// response. This acts as a trigger to make Go's HTTP server
|
||||||
|
// automatically close the current connection after a response has been
|
||||||
|
// sent.
|
||||||
|
w.Header().Set("Connection", "close")
|
||||||
|
// The value returned by recover() has the type interface{}, so we use
|
||||||
|
// fmt.Errorf() to normalize it into an error and call our
|
||||||
|
// serverErrorResponse() helper. In turn, this will log the error using
|
||||||
|
// our custom Logger type at the ERROR level and send the client a 500
|
||||||
|
// Internal Server Error response.
|
||||||
|
app.serverErrorResponse(w, r, fmt.Errorf("%s", err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (app *application) routes() *httprouter.Router {
|
func (app *application) routes() http.Handler {
|
||||||
router := httprouter.New()
|
router := httprouter.New()
|
||||||
|
|
||||||
// Convert the notFoundResponse() helper to a http.Handler using the
|
// Convert the notFoundResponse() helper to a http.Handler using the
|
||||||
@@ -24,5 +24,5 @@ func (app *application) routes() *httprouter.Router {
|
|||||||
router.HandlerFunc(http.MethodPatch, "/v1/movies/:id", app.updateMovieHandler)
|
router.HandlerFunc(http.MethodPatch, "/v1/movies/:id", app.updateMovieHandler)
|
||||||
router.HandlerFunc(http.MethodDelete, "/v1/movies/:id", app.deleteMovieHandler)
|
router.HandlerFunc(http.MethodDelete, "/v1/movies/:id", app.deleteMovieHandler)
|
||||||
|
|
||||||
return router
|
return app.recoverPanic(router)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user