@@ -9,9 +9,14 @@ import (
10 "net/http"
10 "net/http"
17+ defaultRequestLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
15// responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status
20// responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status
16// code and body size
21// code and body size
17type responseLogger struct {
22type responseLogger struct {
@@ -64,15 +69,38 @@ func (l *responseLogger) Size() int {
64 return l.size
69 return l.size
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 {
67// loggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends
89// loggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends
68type loggingHandler struct {
90type loggingHandler struct {
69− writer io.Writer
91+ writer io.Writer
70− handler http.Handler
92+ handler http.Handler
71− enabled bool
93+ enabled bool
94+ logTemplate *template.Template
74−func LoggingHandler(out io.Writer, h http.Handler, v bool) http.Handler {
97+func LoggingHandler(out io.Writer, h http.Handler, v bool, requestLoggingTpl string) http.Handler {
75− return loggingHandler{out, h, v}
98+ return loggingHandler{
102+ logTemplate: template.Must(template.New("request-log").Parse(requestLoggingTpl)),
78func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
106func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
@@ -83,14 +111,13 @@ func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
83 if !h.enabled {
111 if !h.enabled {
86− logLine := buildLogLine(logger.authInfo, logger.upstream, req, url, t, logger.Status(), logger.Size())
114+ h.writeLogLine(logger.authInfo, logger.upstream, req, url, t, logger.Status(), logger.Size())
87− h.writer.Write(logLine)
90// Log entry for req similar to Apache Common Log Format.
117// Log entry for req similar to Apache Common Log Format.
91// ts is the timestamp with which the entry should be logged.
118// ts is the timestamp with which the entry should be logged.
92// status, size are used to provide the response HTTP status and size.
119// 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) {
94 if username == "" {
121 if username == "" {
95 username = "-"
122 username = "-"
@@ -114,19 +141,20 @@ func buildLogLine(username, upstream string, req *http.Request, url url.URL, ts
115 duration := float64(time.Now().Sub(ts)) / float64(time.Second)
142 duration := float64(time.Now().Sub(ts)) / float64(time.Second)
117− logLine := fmt.Sprintf("%s - %s [%s] %s %s %s %q %s %q %d %d %0.3f\n",
144+ h.logTemplate.Execute(h.writer, logMessageData{
118− client,
145+ Client: client,
119− username,
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),
120− ts.Format("02/Jan/2006:15:04:05 -0700"),
153+ Timestamp: ts.Format("02/Jan/2006:15:04:05 -0700"),
123− upstream,
154+ Upstream: upstream,
126− req.UserAgent(),
155+ UserAgent: fmt.Sprintf("%q", req.UserAgent()),
127− status,
156+ Username: username,
131− return []byte(logLine)
158+
159+ h.writer.Write([]byte("\n"))