| # httpsnoop |
| |
| Package httpsnoop provides an easy way to capture http related metrics (i.e. |
| response time, bytes written, and http status code) from your application's |
| http.Handlers. |
| |
| Doing this requires non-trivial wrapping of the http.ResponseWriter interface, |
| which is also exposed for users interested in a more low-level API. |
| |
| [](https://godoc.org/github.com/felixge/httpsnoop) |
| [](https://travis-ci.org/felixge/httpsnoop) |
| |
| ## Usage Example |
| |
| ```go |
| // myH is your app's http handler, perhaps a http.ServeMux or similar. |
| var myH http.Handler |
| // wrappedH wraps myH in order to log every request. |
| wrappedH := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| m := httpsnoop.CaptureMetrics(myH, w, r) |
| log.Printf( |
| "%s %s (code=%d dt=%s written=%d)", |
| r.Method, |
| r.URL, |
| m.Code, |
| m.Duration, |
| m.Written, |
| ) |
| }) |
| http.ListenAndServe(":8080", wrappedH) |
| ``` |
| |
| ## Why this package exists |
| |
| Instrumenting an application's http.Handler is surprisingly difficult. |
| |
| However if you google for e.g. "capture ResponseWriter status code" you'll find |
| lots of advise and code examples that suggest it to be a fairly trivial |
| undertaking. Unfortunately everything I've seen so far has a high chance of |
| breaking your application. |
| |
| The main problem is that a `http.ResponseWriter` often implements additional |
| interfaces such as `http.Flusher`, `http.CloseNotifier`, `http.Hijacker`, `http.Pusher`, and |
| `io.ReaderFrom`. So the naive approach of just wrapping `http.ResponseWriter` |
| in your own struct that also implements the `http.ResponseWriter` interface |
| will hide the additional interfaces mentioned above. This has a high change of |
| introducing subtle bugs into any non-trivial application. |
| |
| Another approach I've seen people take is to return a struct that implements |
| all of the interfaces above. However, that's also problematic, because it's |
| difficult to fake some of these interfaces behaviors when the underlying |
| `http.ResponseWriter` doesn't have an implementation. It's also dangerous, |
| because an application may choose to operate differently, merely because it |
| detects the presence of these additional interfaces. |
| |
| This package solves this problem by checking which additional interfaces a |
| `http.ResponseWriter` implements, returning a wrapped version implementing the |
| exact same set of interfaces. |
| |
| Additionally this package properly handles edge cases such as `WriteHeader` not |
| being called, or called more than once, as well as concurrent calls to |
| `http.ResponseWriter` methods, and even calls happening after the wrapped |
| `ServeHTTP` has already returned. |
| |
| Unfortunately this package is not perfect either. It's possible that it is |
| still missing some interfaces provided by the go core (let me know if you find |
| one), and it won't work for applications adding their own interfaces into the |
| mix. You can however use `httpsnoop.Unwrap(w)` to access the underlying |
| `http.ResponseWriter` and type-assert the result to its other interfaces. |
| |
| However, hopefully the explanation above has sufficiently scared you of rolling |
| your own solution to this problem. httpsnoop may still break your application, |
| but at least it tries to avoid it as much as possible. |
| |
| Anyway, the real problem here is that smuggling additional interfaces inside |
| `http.ResponseWriter` is a problematic design choice, but it probably goes as |
| deep as the Go language specification itself. But that's okay, I still prefer |
| Go over the alternatives ;). |
| |
| ## Performance |
| |
| ``` |
| BenchmarkBaseline-8 20000 94912 ns/op |
| BenchmarkCaptureMetrics-8 20000 95461 ns/op |
| ``` |
| |
| As you can see, using `CaptureMetrics` on a vanilla http.Handler introduces an |
| overhead of ~500 ns per http request on my machine. However, the margin of |
| error appears to be larger than that, therefor it should be reasonable to |
| assume that the overhead introduced by `CaptureMetrics` is absolutely |
| negligible. |
| |
| ## License |
| |
| MIT |