/
access.go
170 lines (140 loc) · 3.85 KB
/
access.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package logging
import (
"fmt"
"net"
"net/http"
"strings"
"time"
"github.com/sirupsen/logrus"
flowidFilter "github.com/zalando/skipper/filters/flowid"
logFilter "github.com/zalando/skipper/filters/log"
)
const (
dateFormat = "02/Jan/2006:15:04:05 -0700"
commonLogFormat = `%s - - [%s] "%s %s %s" %d %d`
// format:
// remote_host - - [date] "method uri protocol" status response_size "referer" "user_agent"
combinedLogFormat = commonLogFormat + ` "%s" "%s"`
// We add the duration in ms, a requested host and a flow id and audit log
accessLogFormat = combinedLogFormat + " %d %s %s %s\n"
)
type accessLogFormatter struct {
format string
}
// Access log entry.
type AccessEntry struct {
// The client request.
Request *http.Request
// The status code of the response.
StatusCode int
// The size of the response in bytes.
ResponseSize int64
// The time spent processing request.
Duration time.Duration
// The time that the request was received.
RequestTime time.Time
}
// TODO: create individual instances from the access log and
// delegate the ownership from the package level to the user
// code.
var (
accessLog *logrus.Logger
stripQuery bool
)
// strip port from addresses with hostname, ipv4 or ipv6
func stripPort(address string) string {
if h, _, err := net.SplitHostPort(address); err == nil {
return h
}
return address
}
// The remote host of the client. When the 'X-Forwarded-For'
// header is set, then its value is used as is.
func remoteHost(r *http.Request) string {
ff := r.Header.Get("X-Forwarded-For")
if ff != "" {
return ff
}
return stripPort(r.RemoteAddr)
}
func omitWhitespace(h string) string {
if h != "" {
return h
}
return "-"
}
func (f *accessLogFormatter) Format(e *logrus.Entry) ([]byte, error) {
keys := []string{
"host", "timestamp", "method", "uri", "proto",
"status", "response-size", "referer", "user-agent",
"duration", "requested-host", "flow-id", "audit"}
values := make([]interface{}, len(keys))
for i, key := range keys {
if s, ok := e.Data[key].(string); ok {
values[i] = omitWhitespace(s)
} else {
values[i] = e.Data[key]
}
}
return []byte(fmt.Sprintf(f.format, values...)), nil
}
func stripQueryString(u string) string {
if i := strings.IndexRune(u, '?'); i < 0 {
return u
} else {
return u[:i]
}
}
// Logs an access event in Apache combined log format (with a minor customization with the duration).
// Additional allows to provide extra data that may be also logged, depending on the specific log format.
func LogAccess(entry *AccessEntry, additional map[string]interface{}) {
if accessLog == nil || entry == nil {
return
}
ts := entry.RequestTime.Format(dateFormat)
host := "-"
method := ""
uri := ""
proto := ""
referer := ""
userAgent := ""
requestedHost := ""
flowId := ""
var auditHeader string
status := entry.StatusCode
responseSize := entry.ResponseSize
duration := int64(entry.Duration / time.Millisecond)
if entry.Request != nil {
host = remoteHost(entry.Request)
method = entry.Request.Method
proto = entry.Request.Proto
referer = entry.Request.Referer()
userAgent = entry.Request.UserAgent()
requestedHost = entry.Request.Host
flowId = entry.Request.Header.Get(flowidFilter.HeaderName)
uri = entry.Request.RequestURI
if stripQuery {
uri = stripQueryString(uri)
}
auditHeader = entry.Request.Header.Get(logFilter.UnverifiedAuditHeader)
}
logData := logrus.Fields{
"timestamp": ts,
"host": host,
"method": method,
"uri": uri,
"proto": proto,
"referer": referer,
"user-agent": userAgent,
"status": status,
"response-size": responseSize,
"requested-host": requestedHost,
"duration": duration,
"flow-id": flowId,
"audit": auditHeader,
}
for k, v := range additional {
logData[k] = v
}
accessLog.WithFields(logData).Infoln()
}