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 }