github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/handlers/logging.go (about)

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