github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/middleware/log_request.go (about)

     1  package middleware
     2  
     3  import (
     4  	"crypto/rand"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"io"
     8  	"net"
     9  	"net/http"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/goadesign/goa"
    14  
    15  	"golang.org/x/net/context"
    16  )
    17  
    18  // LogRequest creates a request logger middleware.
    19  // This middleware is aware of the RequestID middleware and if registered after it leverages the
    20  // request ID for logging.
    21  // If verbose is true then the middlware logs the request and response bodies.
    22  func LogRequest(verbose bool) goa.Middleware {
    23  	return func(h goa.Handler) goa.Handler {
    24  		return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
    25  			reqID := ctx.Value(reqIDKey)
    26  			if reqID == nil {
    27  				reqID = shortID()
    28  			}
    29  			ctx = goa.WithLogContext(ctx, "req_id", reqID)
    30  			startedAt := time.Now()
    31  			r := goa.ContextRequest(ctx)
    32  			goa.LogInfo(ctx, "started", r.Method, r.URL.String(), "from", from(req),
    33  				"ctrl", goa.ContextController(ctx), "action", goa.ContextAction(ctx))
    34  			if verbose {
    35  				if len(r.Header) > 0 {
    36  					logCtx := make([]interface{}, 2*len(r.Header))
    37  					i := 0
    38  					for k, v := range r.Header {
    39  						logCtx[i] = k
    40  						logCtx[i+1] = interface{}(strings.Join(v, ", "))
    41  						i = i + 2
    42  					}
    43  					goa.LogInfo(ctx, "headers", logCtx...)
    44  				}
    45  				if len(r.Params) > 0 {
    46  					logCtx := make([]interface{}, 2*len(r.Params))
    47  					i := 0
    48  					for k, v := range r.Params {
    49  						logCtx[i] = k
    50  						logCtx[i+1] = interface{}(strings.Join(v, ", "))
    51  						i = i + 2
    52  					}
    53  					goa.LogInfo(ctx, "params", logCtx...)
    54  				}
    55  				if r.ContentLength > 0 {
    56  					if mp, ok := r.Payload.(map[string]interface{}); ok {
    57  						logCtx := make([]interface{}, 2*len(mp))
    58  						i := 0
    59  						for k, v := range mp {
    60  							logCtx[i] = k
    61  							logCtx[i+1] = interface{}(v)
    62  							i = i + 2
    63  						}
    64  						goa.LogInfo(ctx, "payload", logCtx...)
    65  					} else {
    66  						// Not the most efficient but this is used for debugging
    67  						js, err := json.Marshal(r.Payload)
    68  						if err != nil {
    69  							js = []byte("<invalid JSON>")
    70  						}
    71  						goa.LogInfo(ctx, "payload", "raw", string(js))
    72  					}
    73  				}
    74  			}
    75  			err := h(ctx, rw, req)
    76  			resp := goa.ContextResponse(ctx)
    77  			if code := resp.ErrorCode; code != "" {
    78  				goa.LogInfo(ctx, "completed", "status", resp.Status, "error", code,
    79  					"bytes", resp.Length, "time", time.Since(startedAt).String(),
    80  					"ctrl", goa.ContextController(ctx), "action", goa.ContextAction(ctx))
    81  			} else {
    82  				goa.LogInfo(ctx, "completed", "status", resp.Status,
    83  					"bytes", resp.Length, "time", time.Since(startedAt).String(),
    84  					"ctrl", goa.ContextController(ctx), "action", goa.ContextAction(ctx))
    85  			}
    86  			return err
    87  		}
    88  	}
    89  }
    90  
    91  // shortID produces a "unique" 6 bytes long string.
    92  // Do not use as a reliable way to get unique IDs, instead use for things like logging.
    93  func shortID() string {
    94  	b := make([]byte, 6)
    95  	io.ReadFull(rand.Reader, b)
    96  	return base64.StdEncoding.EncodeToString(b)
    97  }
    98  
    99  // from makes a best effort to compute the request client IP.
   100  func from(req *http.Request) string {
   101  	if f := req.Header.Get("X-Forwarded-For"); f != "" {
   102  		return f
   103  	}
   104  	f := req.RemoteAddr
   105  	ip, _, err := net.SplitHostPort(f)
   106  	if err != nil {
   107  		return f
   108  	}
   109  	return ip
   110  }