github.com/webonyx/up@v0.7.4-0.20180808230834-91b94e551323/http/logs/logs.go (about)

     1  // Package logs provides HTTP request and response logging.
     2  package logs
     3  
     4  import (
     5  	"net/http"
     6  	"strconv"
     7  	"time"
     8  
     9  	"github.com/apex/log"
    10  
    11  	"github.com/apex/up"
    12  	"github.com/apex/up/internal/logs"
    13  	"github.com/apex/up/internal/util"
    14  )
    15  
    16  // TODO: optional verbose mode with req/res header etc?
    17  
    18  // log context.
    19  var ctx = logs.Plugin("logs")
    20  
    21  // response wrapper.
    22  type response struct {
    23  	http.ResponseWriter
    24  	written  int
    25  	code     int
    26  	duration time.Duration
    27  }
    28  
    29  // Write implementation.
    30  func (r *response) Write(b []byte) (int, error) {
    31  	n, err := r.ResponseWriter.Write(b)
    32  	r.written += n
    33  	return n, err
    34  }
    35  
    36  // WriteHeader implementation.
    37  func (r *response) WriteHeader(code int) {
    38  	r.code = code
    39  	r.ResponseWriter.WriteHeader(code)
    40  }
    41  
    42  // New logs handler.
    43  func New(c *up.Config, next http.Handler) (http.Handler, error) {
    44  	if c.Logs.Disable {
    45  		return next, nil
    46  	}
    47  
    48  	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    49  		ctx := logContext(r)
    50  		logRequest(ctx, r)
    51  
    52  		start := time.Now()
    53  		res := &response{ResponseWriter: w, code: 200}
    54  		next.ServeHTTP(res, r)
    55  		res.duration = time.Since(start)
    56  
    57  		logResponse(ctx, res, r)
    58  	})
    59  
    60  	return h, nil
    61  }
    62  
    63  // logContext returns the common log context for a request.
    64  func logContext(r *http.Request) log.Interface {
    65  	return ctx.WithFields(log.Fields{
    66  		"id":     r.Header.Get("X-Request-Id"),
    67  		"method": r.Method,
    68  		"path":   r.URL.Path,
    69  		"query":  r.URL.Query().Encode(),
    70  		"ip":     r.RemoteAddr,
    71  	})
    72  }
    73  
    74  // logRequest logs the request.
    75  func logRequest(ctx log.Interface, r *http.Request) {
    76  	if s := r.Header.Get("Content-Length"); s != "" {
    77  		n, err := strconv.Atoi(s)
    78  		if err == nil {
    79  			ctx = ctx.WithField("size", n)
    80  		}
    81  	}
    82  
    83  	ctx.Info("request")
    84  }
    85  
    86  // logResponse logs the response.
    87  func logResponse(ctx log.Interface, res *response, r *http.Request) {
    88  	ctx = ctx.WithFields(log.Fields{
    89  		"duration": util.Milliseconds(res.duration),
    90  		"size":     res.written,
    91  		"status":   res.code,
    92  	})
    93  
    94  	switch {
    95  	case res.code >= 500:
    96  		ctx.Error("response")
    97  	case res.code >= 400:
    98  		ctx.Warn("response")
    99  	default:
   100  		ctx.Info("response")
   101  	}
   102  }