git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge pull request #423 from Jimdo/configure_accesslog_format

Make Request Logging Format Configurable

commitfaff555c553374859d01534e74f72c22a0e4417e authorTanvir Alam <talam@users.noreply.github.com> dateMon 4 Dec 2017, 12:56 (9 years ago) parent085c6cf1cefc96
5 files changed+126−43
README.md+11−1 logging_handler.go+51−23 logging_handler_test.go+42 main.go+2−1 options.go+20−18
README.md+11−1
@@ -210,6 +210,7 @@ Usage of oauth2_proxy:
210210 -redeem-url string: Token redemption endpoint
211211 -redirect-url string: the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
212212 -request-logging: Log requests to stdout (default true)
213+ -request-logging-format: Template for request log lines (see "Logging Format" paragraph below)
213214 -resource string: The resource that is protected (Azure AD only)
214215 -scope string: OAuth scope specification
215216 -set-xauthrequest: set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)
@@ -347,12 +348,21 @@ following:
347348
348349## Logging Format
349350
350−OAuth2 Proxy logs requests to stdout in a format similar to Apache Combined Log.
351+By default, OAuth2 Proxy logs requests to stdout in a format similar to Apache Combined Log.
351352
352353```
353354<REMOTE_ADDRESS> - <user@domain.com> [19/Mar/2015:17:20:19 -0400] <HOST_HEADER> GET <UPSTREAM_HOST> "/path/" HTTP/1.1 "<USER_AGENT>" <RESPONSE_CODE> <RESPONSE_BYTES> <REQUEST_DURATION>
354355```
355356
357+If you require a different format than that, you can configure it with the `-request-logging-format` flag.
358+The default format is configured as follows:
359+
360+```
361+{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}
362+```
363+
364+[See `logMessageData` in `logging_handler.go`](./logging_handler.go) for all available variables.
365+
356366## Adding a new Provider
357367
358368Follow the examples in the [`providers` package](providers/) to define a new
logging_handler.go+51−23
@@ -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}
logging_handler_test.gonew file+42
@@ -0,0 +1,42 @@
1+package main
2+
3+import (
4+ "bytes"
5+ "fmt"
6+ "net/http"
7+ "net/http/httptest"
8+ "testing"
9+ "time"
10+)
11+
12+func TestLoggingHandler_ServeHTTP(t *testing.T) {
13+ ts := time.Now()
14+
15+ tests := []struct {
16+ Format,
17+ ExpectedLogMessage string
18+ }{
19+ {defaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n", ts.Format("02/Jan/2006:15:04:05 -0700"))},
20+ {"{{.RequestMethod}}", "GET\n"},
21+ }
22+
23+ for _, test := range tests {
24+ buf := bytes.NewBuffer(nil)
25+ handler := func(w http.ResponseWriter, req *http.Request) {
26+ w.Write([]byte("test"))
27+ }
28+
29+ h := LoggingHandler(buf, http.HandlerFunc(handler), true, test.Format)
30+
31+ r, _ := http.NewRequest("GET", "/foo/bar", nil)
32+ r.RemoteAddr = "127.0.0.1"
33+ r.Host = "test-server"
34+
35+ h.ServeHTTP(httptest.NewRecorder(), r)
36+
37+ actual := buf.String()
38+ if actual != test.ExpectedLogMessage {
39+ t.Errorf("Log message was\n%s\ninstead of expected \n%s", actual, test.ExpectedLogMessage)
40+ }
41+ }
42+}
main.go+2−1
@@ -67,6 +67,7 @@ func main() {
6767 flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag")
6868
6969 flagSet.Bool("request-logging", true, "Log requests to stdout")
70+ flagSet.String("request-logging-format", defaultRequestLoggingFormat, "Template for log lines")
7071
7172 flagSet.String("provider", "google", "OAuth provider")
7273 flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)")
@@ -125,7 +126,7 @@ func main() {
125126 }
126127
127128 s := &Server{
128− Handler: LoggingHandler(os.Stdout, oauthproxy, opts.RequestLogging),
129+ Handler: LoggingHandler(os.Stdout, oauthproxy, opts.RequestLogging, opts.RequestLoggingFormat),
129130 Opts: opts,
130131 }
131132 s.ListenAndServe()
options.go+20−18
@@ -74,7 +74,8 @@ type Options struct {
7474 Scope string `flag:"scope" cfg:"scope"`
7575 ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"`
7676
77− RequestLogging bool `flag:"request-logging" cfg:"request_logging"`
77+ RequestLogging bool `flag:"request-logging" cfg:"request_logging"`
78+ RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format"`
7879
7980 SignatureKey string `flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY"`
8081
@@ -94,23 +95,24 @@ type SignatureData struct {
9495
9596func NewOptions() *Options {
9697 return &Options{
97− ProxyPrefix: "/oauth2",
98− HttpAddress: "127.0.0.1:4180",
99− HttpsAddress: ":443",
100− DisplayHtpasswdForm: true,
101− CookieName: "_oauth2_proxy",
102− CookieSecure: true,
103− CookieHttpOnly: true,
104− CookieExpire: time.Duration(168) * time.Hour,
105− CookieRefresh: time.Duration(0),
106− SetXAuthRequest: false,
107− SkipAuthPreflight: false,
108− PassBasicAuth: true,
109− PassUserHeaders: true,
110− PassAccessToken: false,
111− PassHostHeader: true,
112− ApprovalPrompt: "force",
113− RequestLogging: true,
98+ ProxyPrefix: "/oauth2",
99+ HttpAddress: "127.0.0.1:4180",
100+ HttpsAddress: ":443",
101+ DisplayHtpasswdForm: true,
102+ CookieName: "_oauth2_proxy",
103+ CookieSecure: true,
104+ CookieHttpOnly: true,
105+ CookieExpire: time.Duration(168) * time.Hour,
106+ CookieRefresh: time.Duration(0),
107+ SetXAuthRequest: false,
108+ SkipAuthPreflight: false,
109+ PassBasicAuth: true,
110+ PassUserHeaders: true,
111+ PassAccessToken: false,
112+ PassHostHeader: true,
113+ ApprovalPrompt: "force",
114+ RequestLogging: true,
115+ RequestLoggingFormat: defaultRequestLoggingFormat,
114116 }
115117}
116118