In the last video, we laid the foundation for improving our application logging. In this video, we’ll be expanding upon this to provide more in-depth logging for our application whenever someone hits an endpoint of our app.
Now, we could, in theory, update the comments package and add logging whenever someone calls a particular function. However, this approach leads to a lot of duplication and we can improve upon this approach by defining a middleware function that we can wrap around all of our endpoints.
Let’s open up the handler.go
file. Now, the first thing we want to do is to tidy things up a little. Let’s start by refactoring this package and separating our the comment handler functions into a new file within the package:
package http
import (
"encoding/json"
"net/http"
"strconv"
"github.com/TutorialEdge/go-rest-api-course/internal/comment"
"github.com/gorilla/mux"
)
// GetComment - retrieve a comment by ID
func (h *Handler) GetComment(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
vars := mux.Vars(r)
id := vars["id"]
i, err := strconv.ParseUint(id, 10, 64)
if err != nil {
sendErrorResponse(w, "Unable to parse UINT from ID", err)
}
comment, err := h.Service.GetComment(uint(i))
if err != nil {
sendErrorResponse(w, "Error Retrieving Comment By ID", err)
}
if err := json.NewEncoder(w).Encode(comment); err != nil {
panic(err)
}
}
// GetAllComments - retrieves all comments from the comment service
func (h *Handler) GetAllComments(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
comments, err := h.Service.GetAllComments()
if err != nil {
sendErrorResponse(w, "Failed to retrieve all comments", err)
}
if err := json.NewEncoder(w).Encode(comments); err != nil {
panic(err)
}
}
// PostComment - adds a new comment
func (h *Handler) PostComment(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
var comment comment.Comment
if err := json.NewDecoder(r.Body).Decode(&comment); err != nil {
sendErrorResponse(w, "Failed to decode JSON Body", err)
}
comment, err := h.Service.PostComment(comment)
if err != nil {
sendErrorResponse(w, "Failed to post new comment", err)
}
if err := json.NewEncoder(w).Encode(comment); err != nil {
panic(err)
}
}
// UpdateComment - updates a comment by ID
func (h *Handler) UpdateComment(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
vars := mux.Vars(r)
id := vars["id"]
commentID, err := strconv.ParseUint(id, 10, 64)
if err != nil {
sendErrorResponse(w, "Failed to parse uint from ID", err)
}
var comment comment.Comment
if err := json.NewDecoder(r.Body).Decode(&comment); err != nil {
sendErrorResponse(w, "Failed to decode JSON Body", err)
}
comment, err = h.Service.UpdateComment(uint(commentID), comment)
if err != nil {
sendErrorResponse(w, "Failed to update comment", err)
}
if err := json.NewEncoder(w).Encode(comment); err != nil {
panic(err)
}
}
// DeleteComment - deletes a comment by ID
func (h *Handler) DeleteComment(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
vars := mux.Vars(r)
id := vars["id"]
commentID, err := strconv.ParseUint(id, 10, 64)
if err != nil {
sendErrorResponse(w, "Failed to parse uint from ID", err)
}
err = h.Service.DeleteComment(uint(commentID))
if err != nil {
sendErrorResponse(w, "Failed to delete comment by comment ID", err)
}
if err := json.NewEncoder(w).Encode(Response{Message: "Successfully Deleted"}); err != nil {
panic(err)
}
}
Next, let’s create a new middleware function within handler.go
which we can then tell our gorilla mux router to use for every request:
// LoggingMiddleware - a handy middleware function that logs out incoming requests
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Info("Endpoint hit!")
next.ServeHTTP(w, r)
})
}
And then in the SetupRoutes
function we’ll want to add this call:
h.Router.Use(LoggingMiddleware)
Awesome, let’s try and run this now and see what happens:
$ docker-compose up --build
We should see in our output:
{"level":"info","msg":"Endpoint hit!","time":"2021-03-06T10:47:49Z"}
Let’s improve on this middleware function a little and add some additional context to the log output:
// LoggingMiddleware - a handy middleware function that logs out incoming requests
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.WithFields(
log.Fields{
"Method": r.Method,
"Path": r.URL.Path,
}).
Info("handled request")
next.ServeHTTP(w, r)
})
}
When we rerun this and hit an endpoint, we should now see this information logged out for us:
{"Method":"GET","Path":"/api/comment","level":"info","msg":"handled request","time":"2021-03-06T10:51:41Z"}
Perfect, in the next video, we are going to look at how we can use this same concept of middleware functions to add basic authentication to some of our endpoints!