2

I’m playing around with net/http after reading about the routing upgrades in 1.22, the goal here is to only use stdlib, so I'm intentionally avoiding frameworks and routers that are not part of the stdlib such as gin or gorilla/chi.

...

func main() {
    mux := http.NewServeMux()

    mux.Handle("GET /hello/{$}", handler1)
    mux.Handle("GET /headers/{$}", handler2)


    http.ListenAndServe(port, mux)
}

does what I expect it to do:

GET requests to /hello/ or /hello route to handler 1

GET requests to /headers/ or /headers route to handler 2

POST requests to handled paths return a HTTP 405

unhandled paths return a HTTP 404

However, in the case of a 404 or 405 these are simply responses and are not recorded in stdout, is there a simple way to also add log these bad requests without adding extra handlers/middleware?

Writing a handler as a catch all such as mux.Handle("/", badHandler) would require extra complexity to deal with 404 vs 405, if there is an alternative I'd like to avoid it.

building middleware to spy on a responseWriter, checking the statusCode, and logging on 404/405 seems like it would also potentially work depending on what is made public by the http package (though I haven't looked into this option yet), but is more complex of a solution I would otherwise hope is available.

3
  • 1
    Why can't you add a middleware? Commented Jul 13, 2024 at 19:57
  • No reason preventing me from middleware, I'm just looking for an alternative solution, as I believed there was a simpler method I was overlooking. Wrapping the ResponseWriter in a middleware does present some issues however which are well documented in this GitHub Issue I found recently. Seems like it is a short coming of the stdlib Commented Jul 13, 2024 at 22:17
  • 2
    @AndrewSirolly See http.ResponseController. Commented Jul 14, 2024 at 0:48

2 Answers 2

8

Use middleware. It's not very complex and is the only option as of Go 1.22.

type logWriter struct {
    http.ResponseWriter
    code int
}

func (lw *logWriter) WriteHeader(code int) {
    lw.code = code
    lw.ResponseWriter.WriteHeader(code)
}

// Unwrap supports http.ResponseController.
func (lw *logWriter) Unwrap() http.ResponseWriter { return lw.ResponseWriter }

func wrap(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        lw := &logWriter{w, http.StatusOK}
        next.ServeHTTP(lw, r)
        fmt.Printf("%d: %s\n", lw.code, r.URL.Path)
    })
}

Use the middleware like this:

http.ListenAndServe(port, wrap(mux))

Run the middleware on the PlayGround


Wrapping the response writer using middleware was a problem in the past because the wrapper can block access to optional interfaces such as http.Flusher and http.Hijacker. The http.ResponseController type was added to Go 1.20 to address this shortcoming. If the response writer wrapper implements the Unwrap() http.ResponseWriter method (as done in this answer), then the application can access the optional interfaces through the response controller.

Sign up to request clarification or add additional context in comments.

3 Comments

There's some well discussed shortcomings when using middleware for this in this GitHub discussion which I found after asking the question.
@AndrewSirolly The response controller addresses the shortcoming. My answer supports the response controller.
This is the most elegant solution I have seen thus far.
1

As thoroughly discussed in this Github Issue of the official Go repository, there is no built in capability to add logging without middleware to intercept the WriteHeader() function call as of go 1.22

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.