github.com/thiagoyeds/go-cloud@v0.26.0/server/requestlog/requestlog.go (about) 1 // Copyright 2018 The Go Cloud Development Kit Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package requestlog provides an http.Handler that logs information 16 // about requests. 17 package requestlog // import "gocloud.dev/server/requestlog" 18 19 import ( 20 "bufio" 21 "errors" 22 "io" 23 "io/ioutil" 24 "net" 25 "net/http" 26 "time" 27 28 "go.opencensus.io/trace" 29 ) 30 31 // Logger wraps the Log method. Log must be safe to call from multiple 32 // goroutines. Log must not hold onto an Entry after it returns. 33 type Logger interface { 34 Log(*Entry) 35 } 36 37 // A Handler emits request information to a Logger. 38 type Handler struct { 39 log Logger 40 h http.Handler 41 } 42 43 // NewHandler returns a handler that emits information to log and calls 44 // h.ServeHTTP. 45 func NewHandler(log Logger, h http.Handler) *Handler { 46 return &Handler{ 47 log: log, 48 h: h, 49 } 50 } 51 52 // ServeHTTP calls its underlying handler's ServeHTTP method, then calls 53 // Log after the handler returns. 54 // 55 // ServeHTTP will always consume the request body up to the first error, 56 // even if the underlying handler does not. 57 func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 58 start := time.Now() 59 sc := trace.FromContext(r.Context()).SpanContext() 60 ent := &Entry{ 61 Request: cloneRequestWithoutBody(r), 62 ReceivedTime: start, 63 RequestMethod: r.Method, 64 RequestURL: r.URL.String(), 65 RequestHeaderSize: headerSize(r.Header), 66 UserAgent: r.UserAgent(), 67 Referer: r.Referer(), 68 Proto: r.Proto, 69 RemoteIP: ipFromHostPort(r.RemoteAddr), 70 TraceID: sc.TraceID, 71 SpanID: sc.SpanID, 72 } 73 if addr, ok := r.Context().Value(http.LocalAddrContextKey).(net.Addr); ok { 74 ent.ServerIP = ipFromHostPort(addr.String()) 75 } 76 r2 := new(http.Request) 77 *r2 = *r 78 rcc := &readCounterCloser{r: r.Body} 79 r2.Body = rcc 80 w2 := &responseStats{w: w} 81 82 h.h.ServeHTTP(w2, r2) 83 84 ent.Latency = time.Since(start) 85 if rcc.err == nil && rcc.r != nil && !w2.hijacked { 86 // If the handler hasn't encountered an error in the Body (like EOF), 87 // then consume the rest of the Body to provide an accurate rcc.n. 88 io.Copy(ioutil.Discard, rcc) 89 } 90 ent.RequestBodySize = rcc.n 91 ent.Status = w2.code 92 if ent.Status == 0 { 93 ent.Status = http.StatusOK 94 } 95 ent.ResponseHeaderSize, ent.ResponseBodySize = w2.size() 96 h.log.Log(ent) 97 } 98 99 func cloneRequestWithoutBody(r *http.Request) *http.Request { 100 r = r.Clone(r.Context()) 101 r.Body = nil 102 return r 103 } 104 105 // Entry records information about a completed HTTP request. 106 type Entry struct { 107 // Request is the http request that has been completed. 108 // 109 // This request's Body is always nil, regardless of the actual request body. 110 Request *http.Request 111 112 ReceivedTime time.Time 113 RequestBodySize int64 114 115 Status int 116 ResponseHeaderSize int64 117 ResponseBodySize int64 118 Latency time.Duration 119 TraceID trace.TraceID 120 SpanID trace.SpanID 121 122 // Deprecated. This value is available by evaluating Request.Referer(). 123 Referer string 124 // Deprecated. This value is available directing in Request.Proto. 125 Proto string 126 // Deprecated. This value is available directly in Request.Method. 127 RequestMethod string 128 // Deprecated. This value is available directly in Request.URL. 129 RequestURL string 130 // Deprecated. This value is available by evaluating Request.Header. 131 RequestHeaderSize int64 132 // Deprecated. This value is available by evaluating Request.Header. 133 UserAgent string 134 // Deprecated. This value is available by evaluating Request.RemoteAddr.. 135 RemoteIP string 136 // Deprecated. This value is available by evaluating reading the 137 // http.LocalAddrContextKey value from the context returned by Request.Context(). 138 ServerIP string 139 } 140 141 func ipFromHostPort(hp string) string { 142 h, _, err := net.SplitHostPort(hp) 143 if err != nil { 144 return "" 145 } 146 if len(h) > 0 && h[0] == '[' { 147 return h[1 : len(h)-1] 148 } 149 return h 150 } 151 152 type readCounterCloser struct { 153 r io.ReadCloser 154 n int64 155 err error 156 } 157 158 func (rcc *readCounterCloser) Read(p []byte) (n int, err error) { 159 if rcc.err != nil { 160 return 0, rcc.err 161 } 162 n, rcc.err = rcc.r.Read(p) 163 rcc.n += int64(n) 164 return n, rcc.err 165 } 166 167 func (rcc *readCounterCloser) Close() error { 168 rcc.err = errors.New("read from closed reader") 169 return rcc.r.Close() 170 } 171 172 type writeCounter int64 173 174 func (wc *writeCounter) Write(p []byte) (n int, err error) { 175 *wc += writeCounter(len(p)) 176 return len(p), nil 177 } 178 179 func headerSize(h http.Header) int64 { 180 var wc writeCounter 181 h.Write(&wc) 182 return int64(wc) + 2 // for CRLF 183 } 184 185 type responseStats struct { 186 w http.ResponseWriter 187 hsize int64 188 wc writeCounter 189 code int 190 hijacked bool 191 } 192 193 func (r *responseStats) Header() http.Header { 194 return r.w.Header() 195 } 196 197 func (r *responseStats) WriteHeader(statusCode int) { 198 if r.code != 0 { 199 return 200 } 201 r.hsize = headerSize(r.w.Header()) 202 r.w.WriteHeader(statusCode) 203 r.code = statusCode 204 } 205 206 func (r *responseStats) Write(p []byte) (n int, err error) { 207 if r.code == 0 { 208 r.WriteHeader(http.StatusOK) 209 } 210 n, err = r.w.Write(p) 211 r.wc.Write(p[:n]) 212 return 213 } 214 215 func (r *responseStats) size() (hdr, body int64) { 216 if r.code == 0 { 217 return headerSize(r.w.Header()), 0 218 } 219 // Use the header size from the time WriteHeader was called. 220 // The Header map can be mutated after the call to add HTTP Trailers, 221 // which we don't want to count. 222 return r.hsize, int64(r.wc) 223 } 224 225 func (r *responseStats) Hijack() (_ net.Conn, _ *bufio.ReadWriter, err error) { 226 defer func() { 227 if err == nil { 228 r.hijacked = true 229 } 230 }() 231 if hj, ok := r.w.(http.Hijacker); ok { 232 return hj.Hijack() 233 } 234 return nil, nil, errors.New("underlying ResponseWriter does not support hijacking") 235 }