From f902ecae24717f1409d17224d766fc8c17f4fc98 Mon Sep 17 00:00:00 2001 From: Cory LaNou Date: Wed, 21 Jan 2015 13:58:06 -0700 Subject: [PATCH] real common log format logging --- httpd/filters.go | 38 ++++++++-------- httpd/handler.go | 4 +- httpd/response_logger.go | 93 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 19 deletions(-) create mode 100644 httpd/response_logger.go diff --git a/httpd/filters.go b/httpd/filters.go index 97094f15c7..75f847244f 100644 --- a/httpd/filters.go +++ b/httpd/filters.go @@ -72,6 +72,9 @@ func authorize(inner http.Handler, h *Handler, requireAuthentication bool) http. } func cors(inner http.Handler) http.Handler { + // I think in general we should take the standard path, and if they need custom config + // allow to put that in the config. + // TODO corylanou: incorporate this appropriately //w.Header().Add("Access-Control-Allow-Origin", "*") //w.Header().Add("Access-Control-Max-Age", "2592000") @@ -112,16 +115,15 @@ func logging(inner http.Handler, name string, weblog *log.Logger) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() - inner.ServeHTTP(w, r) + record := &responseLogger{ + w: w, + } - weblog.Printf( - "%s %s %s %s %s", - r.RemoteAddr, - r.Method, - r.RequestURI, - name, - time.Since(start), - ) + inner.ServeHTTP(record, r) + + logLine := buildLogLine(record, r, start) + + weblog.Println(logLine) }) } @@ -129,17 +131,17 @@ func recovery(inner http.Handler, name string, weblog *log.Logger) http.Handler return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() - inner.ServeHTTP(w, r) + record := &responseLogger{ + w: w, + } + + inner.ServeHTTP(record, r) if err := recover(); err != nil { - weblog.Printf( - "%s %s %s %s %s", - r.Method, - r.RequestURI, - name, - time.Since(start), - err, - ) + logLine := buildLogLine(record, r, start) + logLine = fmt.Sprintf(`%s [err:%s]`, logLine, err) + + weblog.Println(logLine) } }) } diff --git a/httpd/handler.go b/httpd/handler.go index 6c9ae3210e..e6ea10e5f5 100644 --- a/httpd/handler.go +++ b/httpd/handler.go @@ -209,7 +209,9 @@ func (h *Handler) serveMetastore(w http.ResponseWriter, r *http.Request) { } // servePing returns a simple response to let the client know the server is running. -func (h *Handler) servePing(w http.ResponseWriter, r *http.Request) {} +func (h *Handler) servePing(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) +} // serveDataNodes returns a list of all data nodes in the cluster. func (h *Handler) serveDataNodes(w http.ResponseWriter, r *http.Request) { diff --git a/httpd/response_logger.go b/httpd/response_logger.go new file mode 100644 index 0000000000..3169d7f55d --- /dev/null +++ b/httpd/response_logger.go @@ -0,0 +1,93 @@ +package httpd + +import ( + "fmt" + "net" + "net/http" + "time" +) + +type loggingResponseWriter interface { + http.ResponseWriter + Status() int + Size() int +} + +// responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status +// code and body size +type responseLogger struct { + w http.ResponseWriter + status int + size int +} + +func (l *responseLogger) Header() http.Header { + return l.w.Header() +} + +func (l *responseLogger) Write(b []byte) (int, error) { + if l.status == 0 { + // The status will be StatusOK if WriteHeader has not been called yet + l.status = http.StatusOK + } + size, err := l.w.Write(b) + l.size += size + return size, err +} + +func (l *responseLogger) WriteHeader(s int) { + l.w.WriteHeader(s) + l.status = s +} + +func (l *responseLogger) Status() int { + return l.status +} + +func (l *responseLogger) Size() int { + return l.size +} + +// Common Log Format: http://en.wikipedia.org/wiki/Common_Log_Format +// 127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 +// A "-" in a field indicates missing data.[citation needed] + +// 127.0.0.1 is the IP address of the client (remote host) which made the request to the server. +// user-identifier is the RFC 1413 identity of the client. +// frank is the userid of the person requesting the document. +// [10/Oct/2000:13:55:36 -0700] is the date, time, and time zone when the server finished processing the request, by default in strftime format %d/%b/%Y:%H:%M:%S %z. +// "GET /apache_pb.gif HTTP/1.0" is the request line from the client. The method GET, /apache_pb.gif the resource requested, and HTTP/1.0 the HTTP protocol. +// 200 is the HTTP status code returned to the client. 2xx is a successful response, 3xx a redirection, 4xx a client error, and 5xx a server error. +// 2326 is the size of the object returned to the client, measured in bytes. + +func buildLogLine(l *responseLogger, r *http.Request, start time.Time) string { + username := "-" + url := r.URL + if url.User != nil { + if name := url.User.Username(); name != "" { + username = name + } + } + + host, _, err := net.SplitHostPort(r.RemoteAddr) + + if err != nil { + host = r.RemoteAddr + } + + uri := url.RequestURI() + return fmt.Sprintf( + "%s %s %s %s %s %s %s %d %d %s %s", + host, + "-", + username, + fmt.Sprintf("[%s]", start.Format("02/Jan/2006:15:04:05 -0700")), + r.Method, + uri, + r.Proto, + l.Status(), + l.Size(), + r.Referer(), + r.UserAgent(), + ) +}