feat: logger, middleware

This commit is contained in:
2026-03-24 22:50:13 +01:00
parent 8e9dc0b581
commit 7d81d1505a
4 changed files with 54 additions and 11 deletions
+5 -2
View File
@@ -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
// about the request including the HTTP method and URL.
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
// 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
// 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}
// Write the response using the writeJSON() helper. If this happens to return an
+15 -7
View File
@@ -13,6 +13,7 @@ import (
"github.com/joho/godotenv"
_ "github.com/lib/pq"
"greenlight.debuggingjon.dev/internal/data"
"greenlight.debuggingjon.dev/internal/jsonlog"
)
// 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.
type application struct {
config config
logger *log.Logger
logger *jsonlog.Logger
models data.Models
}
func main() {
logger := log.New(os.Stdout, "", log.Ldate|log.Ltime)
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()
if err != nil {
logger.Fatal(err)
logger.PrintFatal(err, nil)
}
dsn := os.Getenv("DATABASE_DSN")
@@ -73,7 +77,7 @@ func main() {
// application immediately.
db, err := openDB(cfg)
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
@@ -85,7 +89,7 @@ func main() {
}()
// Also log a message to say that the connection pool has been successfully
// established.
logger.Printf("database connection pool established")
logger.PrintInfo("database connection pool established", nil)
app := &application{
config: cfg,
@@ -98,13 +102,17 @@ func main() {
Addr: fmt.Sprintf(":%d", cfg.port),
Handler: app.routes(),
IdleTimeout: time.Minute,
ErrorLog: log.New(logger, "", 0),
ReadTimeout: 10 * 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()
logger.Fatal(err)
logger.PrintFatal(err, nil)
}
func openDB(cfg config) (*sql.DB, error) {
+32
View File
@@ -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)
})
}
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"github.com/julienschmidt/httprouter"
)
func (app *application) routes() *httprouter.Router {
func (app *application) routes() http.Handler {
router := httprouter.New()
// 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.MethodDelete, "/v1/movies/:id", app.deleteMovieHandler)
return router
return app.recoverPanic(router)
}