github.com/pusher/oauth2_proxy@v3.2.0+incompatible/logging_handler.go (about) 1 // largely adapted from https://github.com/gorilla/handlers/blob/master/handlers.go 2 // to add logging of request duration as last value (and drop referrer) 3 4 package main 5 6 import ( 7 "bufio" 8 "errors" 9 "fmt" 10 "io" 11 "net" 12 "net/http" 13 "net/url" 14 "text/template" 15 "time" 16 ) 17 18 const ( 19 defaultRequestLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}" 20 ) 21 22 // responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status 23 // code and body size 24 type responseLogger struct { 25 w http.ResponseWriter 26 status int 27 size int 28 upstream string 29 authInfo string 30 } 31 32 // Header returns the ResponseWriter's Header 33 func (l *responseLogger) Header() http.Header { 34 return l.w.Header() 35 } 36 37 // Support Websocket 38 func (l *responseLogger) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error) { 39 if hj, ok := l.w.(http.Hijacker); ok { 40 return hj.Hijack() 41 } 42 return nil, nil, errors.New("http.Hijacker is not available on writer") 43 } 44 45 // ExtractGAPMetadata extracts and removes GAP headers from the ResponseWriter's 46 // Header 47 func (l *responseLogger) ExtractGAPMetadata() { 48 upstream := l.w.Header().Get("GAP-Upstream-Address") 49 if upstream != "" { 50 l.upstream = upstream 51 l.w.Header().Del("GAP-Upstream-Address") 52 } 53 authInfo := l.w.Header().Get("GAP-Auth") 54 if authInfo != "" { 55 l.authInfo = authInfo 56 l.w.Header().Del("GAP-Auth") 57 } 58 } 59 60 // Write writes the response using the ResponseWriter 61 func (l *responseLogger) Write(b []byte) (int, error) { 62 if l.status == 0 { 63 // The status will be StatusOK if WriteHeader has not been called yet 64 l.status = http.StatusOK 65 } 66 l.ExtractGAPMetadata() 67 size, err := l.w.Write(b) 68 l.size += size 69 return size, err 70 } 71 72 // WriteHeader writes the status code for the Response 73 func (l *responseLogger) WriteHeader(s int) { 74 l.ExtractGAPMetadata() 75 l.w.WriteHeader(s) 76 l.status = s 77 } 78 79 // Status returns the response status code 80 func (l *responseLogger) Status() int { 81 return l.status 82 } 83 84 // Size returns teh response size 85 func (l *responseLogger) Size() int { 86 return l.size 87 } 88 89 func (l *responseLogger) Flush() { 90 if flusher, ok := l.w.(http.Flusher); ok { 91 flusher.Flush() 92 } 93 } 94 95 // logMessageData is the container for all values that are available as variables in the request logging format. 96 // All values are pre-formatted strings so it is easy to use them in the format string. 97 type logMessageData struct { 98 Client, 99 Host, 100 Protocol, 101 RequestDuration, 102 RequestMethod, 103 RequestURI, 104 ResponseSize, 105 StatusCode, 106 Timestamp, 107 Upstream, 108 UserAgent, 109 Username string 110 } 111 112 // loggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends 113 type loggingHandler struct { 114 writer io.Writer 115 handler http.Handler 116 enabled bool 117 logTemplate *template.Template 118 } 119 120 // LoggingHandler provides an http.Handler which logs requests to the HTTP server 121 func LoggingHandler(out io.Writer, h http.Handler, v bool, requestLoggingTpl string) http.Handler { 122 return loggingHandler{ 123 writer: out, 124 handler: h, 125 enabled: v, 126 logTemplate: template.Must(template.New("request-log").Parse(requestLoggingTpl)), 127 } 128 } 129 130 func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { 131 t := time.Now() 132 url := *req.URL 133 logger := &responseLogger{w: w} 134 h.handler.ServeHTTP(logger, req) 135 if !h.enabled { 136 return 137 } 138 h.writeLogLine(logger.authInfo, logger.upstream, req, url, t, logger.Status(), logger.Size()) 139 } 140 141 // Log entry for req similar to Apache Common Log Format. 142 // ts is the timestamp with which the entry should be logged. 143 // status, size are used to provide the response HTTP status and size. 144 func (h loggingHandler) writeLogLine(username, upstream string, req *http.Request, url url.URL, ts time.Time, status int, size int) { 145 if username == "" { 146 username = "-" 147 } 148 if upstream == "" { 149 upstream = "-" 150 } 151 if url.User != nil && username == "-" { 152 if name := url.User.Username(); name != "" { 153 username = name 154 } 155 } 156 157 client := req.Header.Get("X-Real-IP") 158 if client == "" { 159 client = req.RemoteAddr 160 } 161 162 if c, _, err := net.SplitHostPort(client); err == nil { 163 client = c 164 } 165 166 duration := float64(time.Now().Sub(ts)) / float64(time.Second) 167 168 h.logTemplate.Execute(h.writer, logMessageData{ 169 Client: client, 170 Host: req.Host, 171 Protocol: req.Proto, 172 RequestDuration: fmt.Sprintf("%0.3f", duration), 173 RequestMethod: req.Method, 174 RequestURI: fmt.Sprintf("%q", url.RequestURI()), 175 ResponseSize: fmt.Sprintf("%d", size), 176 StatusCode: fmt.Sprintf("%d", status), 177 Timestamp: ts.Format("02/Jan/2006:15:04:05 -0700"), 178 Upstream: upstream, 179 UserAgent: fmt.Sprintf("%q", req.UserAgent()), 180 Username: username, 181 }) 182 183 h.writer.Write([]byte("\n")) 184 }