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