diff --git a/log/access_logger.go b/log/access_logger.go index 0bbe876d1712e32586dc4a8812797c4f173fd991..f7386979f61f8bdd057b41b2ac4c0b4733b50f8c 100644 --- a/log/access_logger.go +++ b/log/access_logger.go @@ -165,6 +165,12 @@ func (l *loggingResponseWriter) accessLogFields(r *http.Request) logrus.Fields { fields[httpResponseContentTypeField] = l.contentType } + if fieldsBitMask&ContextMetadata != 0 { + for k, v := range l.extractContextMetadataFields() { + fields[contextMetadataField+"."+k] = v + } + } + return fields } diff --git a/log/access_logger_context_metadata.go b/log/access_logger_context_metadata.go new file mode 100644 index 0000000000000000000000000000000000000000..d4071ac742a96f27dbf5a55fe98ddab5633f78f4 --- /dev/null +++ b/log/access_logger_context_metadata.go @@ -0,0 +1,26 @@ +package log + +var contextMetadataFields = map[string]string{ + "user": "X-Gitlab-Meta-User", + "project": "X-Gitlab-Meta-Project", + "root_namespace": "X-Gitlab-Meta-Root-Namespace", + "subscription_plan": "X-Gitlab-Meta-Subscription-Plan", + "caller_id": "X-Gitlab-Meta-Caller-Id", + "remote_ip": "X-Gitlab-Meta-Remote-Ip", + "related_class": "X-Gitlab-Meta-Related-Class", + "feature_category": "X-Gitlab-Meta-Feature-Category", +} + +func (l *loggingResponseWriter) extractContextMetadataFields() map[string]string { + out := make(map[string]string) + + for k, v := range contextMetadataFields { + val := l.rw.Header().Get(v) + + if val != "" { + out[k] = val + } + } + + return out +} diff --git a/log/access_logger_fields.go b/log/access_logger_fields.go index f542353d26e2b38f4788d9c94335a7516d979052..c0bf384b33fce40d03871a7d39cdaab57025dec0 100644 --- a/log/access_logger_fields.go +++ b/log/access_logger_fields.go @@ -1,6 +1,6 @@ package log -// AccessLogField is used to select which fields are recorded in the access log. See WithoutFields. +// AccessLogField is used to select which fields are recorded in the access log. See WithFieldsExcluded. type AccessLogField uint16 const ( @@ -50,6 +50,8 @@ const ( // in the access log. Time is recorded before an actual Write happens to ensure that this metric // is not affected by a slow client receiving data. RequestTTFB + + ContextMetadata ) const defaultEnabledFields = ^AccessLogField(0) // By default, all fields are enabled @@ -71,4 +73,5 @@ const ( requestTTFBField = "ttfb_ms" // ESC: no mapping systemField = "system" // ESC: no mapping httpResponseContentTypeField = "content_type" // ESC: no mapping + contextMetadataField = "meta" // ESC: no mapping ) diff --git a/log/access_logger_test.go b/log/access_logger_test.go index 316240aaf0f69702472d14b590974f2023fff668..b38e7e52b0974003b3385da4af54f391266bd4e6 100644 --- a/log/access_logger_test.go +++ b/log/access_logger_test.go @@ -14,14 +14,15 @@ import ( func TestAccessLogger(t *testing.T) { tests := []struct { - name string - urlSuffix string - body string - logMatchers []string - options []AccessLoggerOption - requestHeaders map[string]string - responseHeaders map[string]string - handler http.Handler + name string + urlSuffix string + body string + logMatchers []string + negatedLogMatchers []string + options []AccessLoggerOption + requestHeaders map[string]string + responseHeaders map[string]string + handler http.Handler }{ { name: "trivial", @@ -162,6 +163,23 @@ func TestAccessLogger(t *testing.T) { fmt.Fprint(w, "yo") }), }, + { + name: "context metadata", + logMatchers: []string{ + `\bmeta.feature_category=source_code_management\b`, + }, + negatedLogMatchers: []string{ + // Ignore empty fields + `\bmeta.caller_id\b`, + // Ignore unknown fields + `\bmeta.foo\b`, + }, + handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-Gitlab-Meta-Feature-Category", "source_code_management") + w.Header().Set("X-Gitlab-Meta-Caller-Id", "") + w.Header().Set("X-Gitlab-Meta-Foo", "") + }), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -214,6 +232,10 @@ func TestAccessLogger(t *testing.T) { for _, v := range tt.logMatchers { require.Regexp(t, v, logString) } + + for _, v := range tt.negatedLogMatchers { + require.NotRegexp(t, v, logString) + } }) } }