8

I'm trying to catch and log http error 400 that happens when url params are not encoded properly.

My server implementation looks like:

router := http.NewServeMux()
router.HandleFunc("/", requestHandler)

s := &http.Server{
    Addr:           ":8080",
    Handler:        router,
    ErrorLog:       myLogger,
}
log.Fatal(s.ListenAndServe())

The request never reaches requestHandler, and having ErrorLog: myLogger makes no difference.

serg
  • 109,619
  • 77
  • 317
  • 330
  • 3
    https://github.com/gorilla/handlers provides middleware that can do this for you - wrap the router in `http.ListenAndServe(":8000", handlers.LoggingHandler(os.Stdout, r))` where `r` is a `http.NewServeMux`. – elithrar Aug 23 '14 at 00:37

2 Answers2

8

You need to create a custom Wrapper around the requestHandler that records the StatusCode and inspects it after the requestCall has been processed.

Notice how we wrap the main router itself. with WrapHandler(router)

package main

import (
    "log"
    "net/http"
)

type LogRecord struct {
    http.ResponseWriter
    status int
}

func (r *LogRecord) Write(p []byte) (int, error) {
    return r.ResponseWriter.Write(p)
}

func (r *LogRecord) WriteHeader(status int) {
    r.status = status
    r.ResponseWriter.WriteHeader(status)
}

func WrapHandler(f http.Handler) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        record := &LogRecord{
            ResponseWriter: w,
        }

        f.ServeHTTP(record, r)

        log.Println("Bad Request ", record.status)

        if record.status == http.StatusBadRequest {
            log.Println("Bad Request ", r)
        }
    }
}

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

    s := &http.Server{
        Addr:    ":8080",
        Handler: WrapHandler(router),
    }
    log.Fatal(s.ListenAndServe())
}
fabrizioM
  • 46,639
  • 15
  • 102
  • 119
  • Thanks, looks like you are on the right track, only I'm getting these exceptions: `func(http.ResponseWriter, *http.Request) does not implement http.Handler (missing ServeHTTP method)`, and `cannot call non-function f (type http.Handler)` – serg Aug 22 '14 at 22:56
  • sorry the type is http.HandlerFunc not http.Handler. look at the diff: http://stackoverflow.com/posts/25456451/revisions – fabrizioM Aug 22 '14 at 23:19
  • That solved the exception, but still doesn't log 400. I put logging at the very beginning of this wrapper and it never gets called for 400 requests (for regular requests it is logged fine). Also for regular requests `record.status` is 0 (shouldn't it be somethign like 200?) – serg Aug 22 '14 at 23:26
  • then we have to wrap the router itself. The Mux was not even calling the handler function. this will log everything. – fabrizioM Aug 22 '14 at 23:32
  • Applied your changes, still behaves the same as before. Is it working for you? – serg Aug 22 '14 at 23:37
  • 1
    Here is what I use for testing: `python -c "import urllib2; urllib2.urlopen(\"http://127.0.0.1:8080/?a=b c\").read()"` – serg Aug 22 '14 at 23:49
  • @seg can you post on the playground your complete code ? and an example query for which you would like to log – fabrizioM Aug 22 '14 at 23:49
  • My code is exact copy paste of what you have in your answer. – serg Aug 22 '14 at 23:50
  • I just copied your snapshot into a new file, launched it, and in another terminal run that python script. If there is no spaces in url params it returns 404 and the wrapper is logging as expected. If there is 400 (spaces inside `?a=b c`), then the wrapper never gets called. – serg Aug 23 '14 at 00:13
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/59837/discussion-between-fabriziom-and-serg). – fabrizioM Aug 23 '14 at 00:19
1

Just for anyone else finding this, you can't intercept a 400 error using net/http in Go. See this answer for more details:

How can I customize Golang http 400 responses for parse errors?

Kenny Grant
  • 9,360
  • 2
  • 33
  • 47