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  }