@@ -9,9 +9,14 @@ import (
99 "net"
1010 "net/http"
1111 "net/url"
12+ "text/template"
1213 "time"
1314)
1415
16+const (
17+ defaultRequestLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
18+)
19+
1520// responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status
1621// code and body size
1722type responseLogger struct {
@@ -64,15 +69,38 @@ func (l *responseLogger) Size() int {
6469 return l.size
6570}
6671
72+// logMessageData is the container for all values that are available as variables in the request logging format.
73+// All values are pre-formatted strings so it is easy to use them in the format string.
74+type logMessageData struct {
75+ Client,
76+ Host,
77+ Protocol,
78+ RequestDuration,
79+ RequestMethod,
80+ RequestURI,
81+ ResponseSize,
82+ StatusCode,
83+ Timestamp,
84+ Upstream,
85+ UserAgent,
86+ Username string
87+}
88+
6789// loggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends
6890type loggingHandler struct {
69− writer io.Writer
70− handler http.Handler
71− enabled bool
91+ writer io.Writer
92+ handler http.Handler
93+ enabled bool
94+ logTemplate *template.Template
7295}
7396
74−func LoggingHandler(out io.Writer, h http.Handler, v bool) http.Handler {
75− return loggingHandler{out, h, v}
97+func LoggingHandler(out io.Writer, h http.Handler, v bool, requestLoggingTpl string) http.Handler {
98+ return loggingHandler{
99+ writer: out,
100+ handler: h,
101+ enabled: v,
102+ logTemplate: template.Must(template.New("request-log").Parse(requestLoggingTpl)),
103+ }
76104}
77105
78106func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
@@ -83,14 +111,13 @@ func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
83111 if !h.enabled {
84112 return
85113 }
86− logLine := buildLogLine(logger.authInfo, logger.upstream, req, url, t, logger.Status(), logger.Size())
87− h.writer.Write(logLine)
114+ h.writeLogLine(logger.authInfo, logger.upstream, req, url, t, logger.Status(), logger.Size())
88115}
89116
90117// Log entry for req similar to Apache Common Log Format.
91118// ts is the timestamp with which the entry should be logged.
92119// status, size are used to provide the response HTTP status and size.
93−func buildLogLine(username, upstream string, req *http.Request, url url.URL, ts time.Time, status int, size int) []byte {
120+func (h loggingHandler) writeLogLine(username, upstream string, req *http.Request, url url.URL, ts time.Time, status int, size int) {
94121 if username == "" {
95122 username = "-"
96123 }
@@ -114,19 +141,20 @@ func buildLogLine(username, upstream string, req *http.Request, url url.URL, ts
114141
115142 duration := float64(time.Now().Sub(ts)) / float64(time.Second)
116143
117− logLine := fmt.Sprintf("%s - %s [%s] %s %s %s %q %s %q %d %d %0.3f\n",
118− client,
119− username,
120− ts.Format("02/Jan/2006:15:04:05 -0700"),
121− req.Host,
122− req.Method,
123− upstream,
124− url.RequestURI(),
125− req.Proto,
126− req.UserAgent(),
127− status,
128− size,
129− duration,
130− )
131− return []byte(logLine)
144+ h.logTemplate.Execute(h.writer, logMessageData{
145+ Client: client,
146+ Host: req.Host,
147+ Protocol: req.Proto,
148+ RequestDuration: fmt.Sprintf("%0.3f", duration),
149+ RequestMethod: req.Method,
150+ RequestURI: fmt.Sprintf("%q", url.RequestURI()),
151+ ResponseSize: fmt.Sprintf("%d", size),
152+ StatusCode: fmt.Sprintf("%d", status),
153+ Timestamp: ts.Format("02/Jan/2006:15:04:05 -0700"),
154+ Upstream: upstream,
155+ UserAgent: fmt.Sprintf("%q", req.UserAgent()),
156+ Username: username,
157+ })
158+
159+ h.writer.Write([]byte("\n"))
132160}