diff --git a/common/middleware/logging.go b/common/middleware/logging.go new file mode 100644 index 000000000..27207b862 --- /dev/null +++ b/common/middleware/logging.go @@ -0,0 +1,45 @@ +package middleware + +import ( + "bufio" + "fmt" + "net" + "net/http" + "time" + + log "github.com/Sirupsen/logrus" +) + +// Logging middleware logs each HTTP request method, path, response code and duration. +var Logging = Func(func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + begin := time.Now() + i := &interceptor{ResponseWriter: w, statusCode: http.StatusOK} + next.ServeHTTP(i, r) + log.Infof("%s %s (%d) %s", r.Method, r.RequestURI, i.statusCode, time.Since(begin)) + }) +}) + +// interceptor implements WriteHeader to intercept status codes. WriteHeader +// may not be called on success, so initialize statusCode with the status you +// want to report on success, i.e. http.StatusOK. +// +// interceptor also implements net.Hijacker, to let the downstream Handler +// hijack the connection. This is needed by the app-mapper's proxy. +type interceptor struct { + http.ResponseWriter + statusCode int +} + +func (i *interceptor) WriteHeader(code int) { + i.statusCode = code + i.ResponseWriter.WriteHeader(code) +} + +func (i *interceptor) Hijack() (net.Conn, *bufio.ReadWriter, error) { + hj, ok := i.ResponseWriter.(http.Hijacker) + if !ok { + return nil, nil, fmt.Errorf("interceptor: can't cast parent ResponseWriter to Hijacker") + } + return hj.Hijack() +} diff --git a/common/middleware/middleware.go b/common/middleware/middleware.go new file mode 100644 index 000000000..8c1574b38 --- /dev/null +++ b/common/middleware/middleware.go @@ -0,0 +1,30 @@ +package middleware + +import ( + "net/http" +) + +// Interface is the shared contract for all middlesware, and allows middlesware +// to wrap handlers. +type Interface interface { + Wrap(http.Handler) http.Handler +} + +// Func is to Interface as http.HandlerFunc is to http.Handler +type Func func(http.Handler) http.Handler + +// Wrap implements Interface +func (m Func) Wrap(next http.Handler) http.Handler { + return m(next) +} + +// Merge produces a middleware that applies multiple middlesware in turn; +// ie Merge(f,g,h).Wrap(handler) == f.Wrap(g.Wrap(h.Wrap(handler))) +func Merge(middlesware ...Interface) Interface { + return Func(func(next http.Handler) http.Handler { + for i := len(middlesware) - 1; i >= 0; i-- { + next = middlesware[i].Wrap(next) + } + return next + }) +} diff --git a/prog/app.go b/prog/app.go index 937d4c04c..cdcff684b 100644 --- a/prog/app.go +++ b/prog/app.go @@ -20,6 +20,7 @@ import ( "github.com/weaveworks/scope/app" "github.com/weaveworks/scope/app/multitenant" + "github.com/weaveworks/scope/common/middleware" "github.com/weaveworks/scope/common/weave" "github.com/weaveworks/scope/common/xfer" "github.com/weaveworks/scope/probe/docker" @@ -110,10 +111,11 @@ func pipeRouterFactory(userIDer multitenant.UserIDer, pipeRouterURL, consulInf s // Main runs the app func appMain() { var ( - window = flag.Duration("window", 15*time.Second, "window") - listen = flag.String("http.address", ":"+strconv.Itoa(xfer.AppPort), "webserver listen address") - logLevel = flag.String("log.level", "info", "logging threshold level: debug|info|warn|error|fatal|panic") - logPrefix = flag.String("log.prefix", "", "prefix for each log line") + window = flag.Duration("window", 15*time.Second, "window") + listen = flag.String("http.address", ":"+strconv.Itoa(xfer.AppPort), "webserver listen address") + logLevel = flag.String("log.level", "info", "logging threshold level: debug|info|warn|error|fatal|panic") + logPrefix = flag.String("log.prefix", "", "prefix for each log line") + logRequests = flag.Bool("log.requests", false, "Log individual HTTP requests") weaveAddr = flag.String("weave.addr", app.DefaultWeaveURL, "Address on which to contact WeaveDNS") weaveHostname = flag.String("weave.hostname", app.DefaultHostname, "Hostname to advertise in WeaveDNS") @@ -189,6 +191,9 @@ func appMain() { } handler := router(collector, controlRouter, pipeRouter) + if *logRequests { + handler = middleware.Logging.Wrap(handler) + } go func() { log.Infof("listening on %s", *listen) log.Info(http.ListenAndServe(*listen, handler))