package middlewares // Borrowed from https://gist.github.com/elithrar/887d162dfd0c539b700ab4049c76e22b import ( "io" "net/http" "strings" "time" ) type logFunc func(string, ...interface{}) type LoggingMiddleware struct { handler http.Handler logFunc logFunc excludePrefixes []string } func NewLoggingMiddleware(logFunc logFunc, excludePrefixes []string) func(http.Handler) http.Handler { return func(h http.Handler) http.Handler { return &LoggingMiddleware{ handler: h, logFunc: logFunc, excludePrefixes: excludePrefixes, } } } func (lg *LoggingMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { ww := wrapWriter(w) start := time.Now() lg.handler.ServeHTTP(ww, r) end := time.Now() duration := end.Sub(start) path := strings.ToLower(r.URL.Path) for _, prefix := range lg.excludePrefixes { if strings.HasPrefix(path, prefix) { return } } lg.logFunc( "[request] status=%d, method=%s, uri=%s, duration=%v, bytes=%d, addr=%s, user=%s", ww.Status(), r.Method, r.URL.String(), duration, ww.BytesWritten(), readUserIP(r), readUserID(r), ) } func readUserIP(r *http.Request) string { ip := r.Header.Get("X-Real-Ip") if ip == "" { ip = r.Header.Get("X-Forwarded-For") } if ip == "" { ip = r.RemoteAddr } return ip } func readUserID(r *http.Request) string { if user := GetPrincipal(r); user != nil { return user.ID } return "-" } // The below writer-wrapping code has been lifted from // https://github.com/zenazn/goji/blob/master/web/middleware/logger.go - because // it does exactly what is needed, and it's unlikely to change in any // significant way that makes copying worse-off than importing. MIT licensed // and (c) Carl Jackson. // writerProxy is a proxy around an http.ResponseWriter that allows you to hook // into various parts of the response process. type writerProxy interface { http.ResponseWriter // Status returns the HTTP status of the request, or 0 if one has not // yet been sent. Status() int // BytesWritten returns the total number of bytes sent to the client. BytesWritten() int // Tee causes the response body to be written to the given io.Writer in // addition to proxying the writes through. Only one io.Writer can be // tee'd to at once: setting a second one will overwrite the first. // Writes will be sent to the proxy before being written to this // io.Writer. It is illegal for the tee'd writer to be modified // concurrently with writes. Tee(io.Writer) // Unwrap returns the original proxied target. Unwrap() http.ResponseWriter } // wrapWriter wraps an http.ResponseWriter, returning a proxy that allows you to // hook into various parts of the response process. func wrapWriter(w http.ResponseWriter) writerProxy { return &basicWriter{ResponseWriter: w} } // basicWriter wraps a http.ResponseWriter that implements the minimal // http.ResponseWriter interface. type basicWriter struct { http.ResponseWriter wroteHeader bool code int bytes int tee io.Writer } func (b *basicWriter) WriteHeader(code int) { if !b.wroteHeader { b.code = code b.wroteHeader = true b.ResponseWriter.WriteHeader(code) } } func (b *basicWriter) Write(buf []byte) (int, error) { b.WriteHeader(http.StatusOK) n, err := b.ResponseWriter.Write(buf) if b.tee != nil { _, err2 := b.tee.Write(buf[:n]) // Prefer errors generated by the proxied writer. if err == nil { err = err2 } } b.bytes += n return n, err } func (b *basicWriter) maybeWriteHeader() { if !b.wroteHeader { b.WriteHeader(http.StatusOK) } } func (b *basicWriter) Status() int { return b.code } func (b *basicWriter) BytesWritten() int { return b.bytes } func (b *basicWriter) Tee(w io.Writer) { b.tee = w } func (b *basicWriter) Unwrap() http.ResponseWriter { return b.ResponseWriter }