Merge branch 'master' into wtsi-hgi-8087-arv-cli-request-body-from-file
[arvados.git] / services / keepstore / logging_router.go
1 package main
2
3 // LoggingRESTRouter
4 // LoggingResponseWriter
5
6 import (
7         "log"
8         "net/http"
9         "strings"
10         "time"
11 )
12
13 // LoggingResponseWriter has anonymous fields ResponseWriter and ResponseBody
14 type LoggingResponseWriter struct {
15         Status int
16         Length int
17         http.ResponseWriter
18         ResponseBody string
19         sentHdr      time.Time
20 }
21
22 // CloseNotify implements http.CloseNotifier.
23 func (resp *LoggingResponseWriter) CloseNotify() <-chan bool {
24         wrapped, ok := resp.ResponseWriter.(http.CloseNotifier)
25         if !ok {
26                 // If upstream doesn't implement CloseNotifier, we can
27                 // satisfy the interface by returning a channel that
28                 // never sends anything (the interface doesn't
29                 // guarantee that anything will ever be sent on the
30                 // channel even if the client disconnects).
31                 return nil
32         }
33         return wrapped.CloseNotify()
34 }
35
36 // WriteHeader writes header to ResponseWriter
37 func (resp *LoggingResponseWriter) WriteHeader(code int) {
38         if resp.sentHdr == zeroTime {
39                 resp.sentHdr = time.Now()
40         }
41         resp.Status = code
42         resp.ResponseWriter.WriteHeader(code)
43 }
44
45 var zeroTime time.Time
46
47 func (resp *LoggingResponseWriter) Write(data []byte) (int, error) {
48         if resp.Length == 0 && len(data) > 0 && resp.sentHdr == zeroTime {
49                 resp.sentHdr = time.Now()
50         }
51         resp.Length += len(data)
52         if resp.Status >= 400 {
53                 resp.ResponseBody += string(data)
54         }
55         return resp.ResponseWriter.Write(data)
56 }
57
58 // LoggingRESTRouter is used to add logging capabilities to mux.Router
59 type LoggingRESTRouter struct {
60         router http.Handler
61 }
62
63 func (loggingRouter *LoggingRESTRouter) ServeHTTP(wrappedResp http.ResponseWriter, req *http.Request) {
64         t0 := time.Now()
65         resp := LoggingResponseWriter{http.StatusOK, 0, wrappedResp, "", zeroTime}
66         loggingRouter.router.ServeHTTP(&resp, req)
67         statusText := http.StatusText(resp.Status)
68         if resp.Status >= 400 {
69                 statusText = strings.Replace(resp.ResponseBody, "\n", "", -1)
70         }
71         now := time.Now()
72         tTotal := now.Sub(t0)
73         tLatency := resp.sentHdr.Sub(t0)
74         tResponse := now.Sub(resp.sentHdr)
75         log.Printf("[%s] %s %s %d %.6fs %.6fs %.6fs %d %d \"%s\"", req.RemoteAddr, req.Method, req.URL.Path[1:], req.ContentLength, tTotal.Seconds(), tLatency.Seconds(), tResponse.Seconds(), resp.Status, resp.Length, statusText)
76
77 }