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  }