...

Source file src/github.com/gorilla/handlers/logging.go

Documentation: github.com/gorilla/handlers

     1  // Copyright 2013 The Gorilla Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package handlers
     6  
     7  import (
     8  	"io"
     9  	"net"
    10  	"net/http"
    11  	"net/url"
    12  	"strconv"
    13  	"time"
    14  	"unicode/utf8"
    15  
    16  	"github.com/felixge/httpsnoop"
    17  )
    18  
    19  // Logging
    20  
    21  // LogFormatterParams is the structure any formatter will be handed when time to log comes
    22  type LogFormatterParams struct {
    23  	Request    *http.Request
    24  	URL        url.URL
    25  	TimeStamp  time.Time
    26  	StatusCode int
    27  	Size       int
    28  }
    29  
    30  // LogFormatter gives the signature of the formatter function passed to CustomLoggingHandler
    31  type LogFormatter func(writer io.Writer, params LogFormatterParams)
    32  
    33  // loggingHandler is the http.Handler implementation for LoggingHandlerTo and its
    34  // friends
    35  
    36  type loggingHandler struct {
    37  	writer    io.Writer
    38  	handler   http.Handler
    39  	formatter LogFormatter
    40  }
    41  
    42  func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    43  	t := time.Now()
    44  	logger, w := makeLogger(w)
    45  	url := *req.URL
    46  
    47  	h.handler.ServeHTTP(w, req)
    48  	if req.MultipartForm != nil {
    49  		req.MultipartForm.RemoveAll()
    50  	}
    51  
    52  	params := LogFormatterParams{
    53  		Request:    req,
    54  		URL:        url,
    55  		TimeStamp:  t,
    56  		StatusCode: logger.Status(),
    57  		Size:       logger.Size(),
    58  	}
    59  
    60  	h.formatter(h.writer, params)
    61  }
    62  
    63  func makeLogger(w http.ResponseWriter) (*responseLogger, http.ResponseWriter) {
    64  	logger := &responseLogger{w: w, status: http.StatusOK}
    65  	return logger, httpsnoop.Wrap(w, httpsnoop.Hooks{
    66  		Write: func(httpsnoop.WriteFunc) httpsnoop.WriteFunc {
    67  			return logger.Write
    68  		},
    69  		WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
    70  			return logger.WriteHeader
    71  		},
    72  	})
    73  }
    74  
    75  const lowerhex = "0123456789abcdef"
    76  
    77  func appendQuoted(buf []byte, s string) []byte {
    78  	var runeTmp [utf8.UTFMax]byte
    79  	for width := 0; len(s) > 0; s = s[width:] {
    80  		r := rune(s[0])
    81  		width = 1
    82  		if r >= utf8.RuneSelf {
    83  			r, width = utf8.DecodeRuneInString(s)
    84  		}
    85  		if width == 1 && r == utf8.RuneError {
    86  			buf = append(buf, `\x`...)
    87  			buf = append(buf, lowerhex[s[0]>>4])
    88  			buf = append(buf, lowerhex[s[0]&0xF])
    89  			continue
    90  		}
    91  		if r == rune('"') || r == '\\' { // always backslashed
    92  			buf = append(buf, '\\')
    93  			buf = append(buf, byte(r))
    94  			continue
    95  		}
    96  		if strconv.IsPrint(r) {
    97  			n := utf8.EncodeRune(runeTmp[:], r)
    98  			buf = append(buf, runeTmp[:n]...)
    99  			continue
   100  		}
   101  		switch r {
   102  		case '\a':
   103  			buf = append(buf, `\a`...)
   104  		case '\b':
   105  			buf = append(buf, `\b`...)
   106  		case '\f':
   107  			buf = append(buf, `\f`...)
   108  		case '\n':
   109  			buf = append(buf, `\n`...)
   110  		case '\r':
   111  			buf = append(buf, `\r`...)
   112  		case '\t':
   113  			buf = append(buf, `\t`...)
   114  		case '\v':
   115  			buf = append(buf, `\v`...)
   116  		default:
   117  			switch {
   118  			case r < ' ':
   119  				buf = append(buf, `\x`...)
   120  				buf = append(buf, lowerhex[s[0]>>4])
   121  				buf = append(buf, lowerhex[s[0]&0xF])
   122  			case r > utf8.MaxRune:
   123  				r = 0xFFFD
   124  				fallthrough
   125  			case r < 0x10000:
   126  				buf = append(buf, `\u`...)
   127  				for s := 12; s >= 0; s -= 4 {
   128  					buf = append(buf, lowerhex[r>>uint(s)&0xF])
   129  				}
   130  			default:
   131  				buf = append(buf, `\U`...)
   132  				for s := 28; s >= 0; s -= 4 {
   133  					buf = append(buf, lowerhex[r>>uint(s)&0xF])
   134  				}
   135  			}
   136  		}
   137  	}
   138  	return buf
   139  }
   140  
   141  // buildCommonLogLine builds a log entry for req in Apache Common Log Format.
   142  // ts is the timestamp with which the entry should be logged.
   143  // status and size are used to provide the response HTTP status and size.
   144  func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte {
   145  	username := "-"
   146  	if url.User != nil {
   147  		if name := url.User.Username(); name != "" {
   148  			username = name
   149  		}
   150  	}
   151  
   152  	host, _, err := net.SplitHostPort(req.RemoteAddr)
   153  	if err != nil {
   154  		host = req.RemoteAddr
   155  	}
   156  
   157  	uri := req.RequestURI
   158  
   159  	// Requests using the CONNECT method over HTTP/2.0 must use
   160  	// the authority field (aka r.Host) to identify the target.
   161  	// Refer: https://httpwg.github.io/specs/rfc7540.html#CONNECT
   162  	if req.ProtoMajor == 2 && req.Method == "CONNECT" {
   163  		uri = req.Host
   164  	}
   165  	if uri == "" {
   166  		uri = url.RequestURI()
   167  	}
   168  
   169  	buf := make([]byte, 0, 3*(len(host)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2)
   170  	buf = append(buf, host...)
   171  	buf = append(buf, " - "...)
   172  	buf = append(buf, username...)
   173  	buf = append(buf, " ["...)
   174  	buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...)
   175  	buf = append(buf, `] "`...)
   176  	buf = append(buf, req.Method...)
   177  	buf = append(buf, " "...)
   178  	buf = appendQuoted(buf, uri)
   179  	buf = append(buf, " "...)
   180  	buf = append(buf, req.Proto...)
   181  	buf = append(buf, `" `...)
   182  	buf = append(buf, strconv.Itoa(status)...)
   183  	buf = append(buf, " "...)
   184  	buf = append(buf, strconv.Itoa(size)...)
   185  	return buf
   186  }
   187  
   188  // writeLog writes a log entry for req to w in Apache Common Log Format.
   189  // ts is the timestamp with which the entry should be logged.
   190  // status and size are used to provide the response HTTP status and size.
   191  func writeLog(writer io.Writer, params LogFormatterParams) {
   192  	buf := buildCommonLogLine(params.Request, params.URL, params.TimeStamp, params.StatusCode, params.Size)
   193  	buf = append(buf, '\n')
   194  	writer.Write(buf)
   195  }
   196  
   197  // writeCombinedLog writes a log entry for req to w in Apache Combined Log Format.
   198  // ts is the timestamp with which the entry should be logged.
   199  // status and size are used to provide the response HTTP status and size.
   200  func writeCombinedLog(writer io.Writer, params LogFormatterParams) {
   201  	buf := buildCommonLogLine(params.Request, params.URL, params.TimeStamp, params.StatusCode, params.Size)
   202  	buf = append(buf, ` "`...)
   203  	buf = appendQuoted(buf, params.Request.Referer())
   204  	buf = append(buf, `" "`...)
   205  	buf = appendQuoted(buf, params.Request.UserAgent())
   206  	buf = append(buf, '"', '\n')
   207  	writer.Write(buf)
   208  }
   209  
   210  // CombinedLoggingHandler return a http.Handler that wraps h and logs requests to out in
   211  // Apache Combined Log Format.
   212  //
   213  // See http://httpd.apache.org/docs/2.2/logs.html#combined for a description of this format.
   214  //
   215  // LoggingHandler always sets the ident field of the log to -
   216  func CombinedLoggingHandler(out io.Writer, h http.Handler) http.Handler {
   217  	return loggingHandler{out, h, writeCombinedLog}
   218  }
   219  
   220  // LoggingHandler return a http.Handler that wraps h and logs requests to out in
   221  // Apache Common Log Format (CLF).
   222  //
   223  // See http://httpd.apache.org/docs/2.2/logs.html#common for a description of this format.
   224  //
   225  // LoggingHandler always sets the ident field of the log to -
   226  //
   227  // Example:
   228  //
   229  //  r := mux.NewRouter()
   230  //  r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
   231  //  	w.Write([]byte("This is a catch-all route"))
   232  //  })
   233  //  loggedRouter := handlers.LoggingHandler(os.Stdout, r)
   234  //  http.ListenAndServe(":1123", loggedRouter)
   235  //
   236  func LoggingHandler(out io.Writer, h http.Handler) http.Handler {
   237  	return loggingHandler{out, h, writeLog}
   238  }
   239  
   240  // CustomLoggingHandler provides a way to supply a custom log formatter
   241  // while taking advantage of the mechanisms in this package
   242  func CustomLoggingHandler(out io.Writer, h http.Handler, f LogFormatter) http.Handler {
   243  	return loggingHandler{out, h, f}
   244  }
   245  

View as plain text