From ec13a9002a7b070d515ed5aa89726e91c57bbecb Mon Sep 17 00:00:00 2001 From: Claudio Netto Date: Tue, 19 Jul 2022 15:46:15 -0300 Subject: [PATCH] feat(api): support custom Nginx log format + log additional headers and fields (#120) --- api/v1alpha1/rpaasplan_types.go | 6 ++ api/v1alpha1/zz_generated.deepcopy.go | 12 ++++ .../extensions.tsuru.io_rpaasflavors.yaml | 14 +++++ .../extensions.tsuru.io_rpaasinstances.yaml | 14 +++++ .../bases/extensions.tsuru.io_rpaasplans.yaml | 14 +++++ .../pkg/rpaas/nginx/configuration_render.go | 31 +++++++++- .../rpaas/nginx/configuration_render_test.go | 58 ++++++++++++++++++- 7 files changed, 143 insertions(+), 6 deletions(-) diff --git a/api/v1alpha1/rpaasplan_types.go b/api/v1alpha1/rpaasplan_types.go index 125f42110..8b4e2f8b2 100644 --- a/api/v1alpha1/rpaasplan_types.go +++ b/api/v1alpha1/rpaasplan_types.go @@ -70,6 +70,12 @@ type NginxConfig struct { CacheSnapshotStorage CacheSnapshotStorage `json:"cacheSnapshotStorage,omitempty"` CacheSnapshotSync CacheSnapshotSyncSpec `json:"cacheSnapshotSync,omitempty"` + LogFormat string `json:"logFormat,omitempty"` + LogFormatEscape string `json:"logFormatEscape,omitempty"` + LogFormatName string `json:"logFormatName,omitempty"` + LogAdditionalHeaders []string `json:"logAdditionalHeaders,omitempty"` + LogAdditionalFields map[string]string `json:"logAdditionalFields,omitempty"` + HTTPListenOptions string `json:"httpListenOptions,omitempty"` HTTPSListenOptions string `json:"httpsListenOptions,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index e189b03df..e582c9a40 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -245,6 +245,18 @@ func (in *NginxConfig) DeepCopyInto(out *NginxConfig) { } in.CacheSnapshotStorage.DeepCopyInto(&out.CacheSnapshotStorage) in.CacheSnapshotSync.DeepCopyInto(&out.CacheSnapshotSync) + if in.LogAdditionalHeaders != nil { + in, out := &in.LogAdditionalHeaders, &out.LogAdditionalHeaders + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.LogAdditionalFields != nil { + in, out := &in.LogAdditionalFields, &out.LogAdditionalFields + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } if in.VTSEnabled != nil { in, out := &in.VTSEnabled, &out.VTSEnabled *out = new(bool) diff --git a/config/crd/bases/extensions.tsuru.io_rpaasflavors.yaml b/config/crd/bases/extensions.tsuru.io_rpaasflavors.yaml index 140c7ffc8..b1427a182 100644 --- a/config/crd/bases/extensions.tsuru.io_rpaasflavors.yaml +++ b/config/crd/bases/extensions.tsuru.io_rpaasflavors.yaml @@ -446,6 +446,20 @@ spec: type: string httpsListenOptions: type: string + logAdditionalFields: + additionalProperties: + type: string + type: object + logAdditionalHeaders: + items: + type: string + type: array + logFormat: + type: string + logFormatEscape: + type: string + logFormatName: + type: string syslogEnabled: type: boolean syslogFacility: diff --git a/config/crd/bases/extensions.tsuru.io_rpaasinstances.yaml b/config/crd/bases/extensions.tsuru.io_rpaasinstances.yaml index aebaed244..799f9af0a 100644 --- a/config/crd/bases/extensions.tsuru.io_rpaasinstances.yaml +++ b/config/crd/bases/extensions.tsuru.io_rpaasinstances.yaml @@ -423,6 +423,20 @@ spec: type: string httpsListenOptions: type: string + logAdditionalFields: + additionalProperties: + type: string + type: object + logAdditionalHeaders: + items: + type: string + type: array + logFormat: + type: string + logFormatEscape: + type: string + logFormatName: + type: string syslogEnabled: type: boolean syslogFacility: diff --git a/config/crd/bases/extensions.tsuru.io_rpaasplans.yaml b/config/crd/bases/extensions.tsuru.io_rpaasplans.yaml index 5b29601e0..4bf71a36e 100644 --- a/config/crd/bases/extensions.tsuru.io_rpaasplans.yaml +++ b/config/crd/bases/extensions.tsuru.io_rpaasplans.yaml @@ -104,6 +104,20 @@ spec: type: string httpsListenOptions: type: string + logAdditionalFields: + additionalProperties: + type: string + type: object + logAdditionalHeaders: + items: + type: string + type: array + logFormat: + type: string + logFormatEscape: + type: string + logFormatName: + type: string syslogEnabled: type: boolean syslogFacility: diff --git a/internal/pkg/rpaas/nginx/configuration_render.go b/internal/pkg/rpaas/nginx/configuration_render.go index 4ac517b44..db39c78a6 100644 --- a/internal/pkg/rpaas/nginx/configuration_render.go +++ b/internal/pkg/rpaas/nginx/configuration_render.go @@ -289,14 +289,40 @@ http { include mime.types; default_type application/octet-stream; + {{- $logFormatName := default "rpaasv2" $config.LogFormatName }} + + {{- if $config.LogFormat }} + log_format {{ $config.LogFormatName }} {{ with $config.LogFormatEscape}}escape={{ . }}{{ end }} {{ $config.LogFormat }}; + {{- else }} + log_format {{ $logFormatName }} escape=json + '{' + {{- range $key, $value := $config.LogAdditionalFields }} + '"{{ $key }}":"{{ $value }}",' + {{- end }} + '"remote_addr":"${remote_addr}",' + '"remote_user":"${remote_user}",' + '"time_local":"${time_local}",' + '"request":"${request}",' + '"status":"${status}",' + '"body_bytes_sent":"${body_bytes_sent}",' + '"referer":"${http_referer}",' + '"user_agent":"${http_user_agent}"' + {{- range $index, $header := $config.LogAdditionalHeaders }} + {{- if not $index }}{{ "\n" }}','{{ end }} + {{- $h := lower (replace "-" "_" $header) }} + '"header_{{ $h }}":"${http_{{ $h }}}" {{- if lt (add1 $index) (len $config.LogAdditionalHeaders) }},{{ end }}' + {{- end }} + '}'; + {{- end }} + {{- if not (boolValue $config.SyslogEnabled) }} - access_log /dev/stdout combined; + access_log /dev/stdout {{ $logFormatName }}; error_log /dev/stderr; {{- else }} access_log syslog:server={{ $config.SyslogServerAddress }} {{- with $config.SyslogFacility }},facility={{ . }}{{ end }} {{- with $config.SyslogTag }},tag={{ . }}{{ end}} - combined; + {{ $logFormatName }}; error_log syslog:server={{ $config.SyslogServerAddress }} {{- with $config.SyslogFacility }},facility={{ . }}{{ end }} @@ -338,7 +364,6 @@ http { {{- end }} {{- range $index, $bind := $instance.Spec.Binds }} - {{- if eq $index 0 }} upstream rpaas_default_upstream { server {{ $bind.Host }}; diff --git a/internal/pkg/rpaas/nginx/configuration_render_test.go b/internal/pkg/rpaas/nginx/configuration_render_test.go index bcece8d81..98b12e152 100644 --- a/internal/pkg/rpaas/nginx/configuration_render_test.go +++ b/internal/pkg/rpaas/nginx/configuration_render_test.go @@ -37,7 +37,7 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { assert.NotRegexp(t, `worker_processes(.+);`, result) assert.NotRegexp(t, `worker_connections(.+);`, result) assert.Regexp(t, `include modules/\*\.conf;`, result) - assert.Regexp(t, `access_log /dev/stdout combined;`, result) + assert.Regexp(t, `access_log /dev/stdout rpaasv2;`, result) assert.Regexp(t, `error_log /dev/stderr;`, result) assert.Regexp(t, `server {\n\s+listen 8800;\n\s+}\n+`, result) assert.Regexp(t, `server { @@ -127,7 +127,7 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { Instance: &v1alpha1.RpaasInstance{}, }, assertion: func(t *testing.T, result string) { - assert.Regexp(t, `access_log syslog:server=syslog.server.example.com\n\s+combined;`, result) + assert.Regexp(t, `access_log syslog:server=syslog.server.example.com\n\s+rpaasv2;`, result) assert.Regexp(t, `error_log syslog:server=syslog.server.example.com;`, result) }, }, @@ -143,7 +143,7 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { Instance: &v1alpha1.RpaasInstance{}, }, assertion: func(t *testing.T, result string) { - assert.Regexp(t, `access_log syslog:server=syslog.server.example.com,facility=local1,tag=my-tag\n\s+combined;`, result) + assert.Regexp(t, `access_log syslog:server=syslog.server.example.com,facility=local1,tag=my-tag\n\s+rpaasv2;`, result) assert.Regexp(t, `error_log syslog:server=syslog.server.example.com,facility=local1,tag=my-tag;`, result) }, }, @@ -531,6 +531,58 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { assert.Regexp(t, `load_module "modules/mod2.so";`, result) }, }, + { + name: "with custom log format", + data: ConfigurationData{ + Config: &v1alpha1.NginxConfig{ + LogFormatName: "custom", + LogFormatEscape: "default", + LogFormat: `'status=${status} foo_bar=${http_x_foo_bar}'`, + }, + Instance: &v1alpha1.RpaasInstance{}, + }, + assertion: func(t *testing.T, result string) { + assert.Regexp(t, `log_format custom escape=default 'status=\$\{status\} foo_bar=\$\{http_x_foo_bar\}';`, result) + assert.Regexp(t, `access_log /dev/stdout custom;`, result) + }, + }, + { + name: "with default log format and additional headers", + data: ConfigurationData{ + Config: &v1alpha1.NginxConfig{ + LogAdditionalHeaders: []string{"X-Foo-Bar", "X-App-Version", "X-App-Vendor", "X-App-User"}, + }, + Instance: &v1alpha1.RpaasInstance{}, + }, + assertion: func(t *testing.T, result string) { + assert.Regexp(t, `\s+',' +\s+'"header_x_foo_bar":"\$\{http_x_foo_bar\}",' +\s+'"header_x_app_version":"\$\{http_x_app_version\}",' +\s+'"header_x_app_vendor":"\$\{http_x_app_vendor\}",' +\s+'"header_x_app_user":"\$\{http_x_app_user\}"' +\s+'}';`, result) + }, + }, + { + name: "with log additional fields", + data: ConfigurationData{ + Config: &v1alpha1.NginxConfig{ + LogAdditionalFields: map[string]string{ + "key1": "Some custom var: ${http_x_foo_bar}", + "key2": "Another custom var: ${host}", + "custom_key": "${custom_var}", + }, + }, + Instance: &v1alpha1.RpaasInstance{}, + }, + assertion: func(t *testing.T, result string) { + assert.Regexp(t, `\s+'\{' +\s+'"custom_key":"\$\{custom_var\}",' +\s+'"key1":"Some custom var: \$\{http_x_foo_bar\}",' +\s+'"key2":"Another custom var: \$\{host\}",' +`, result) + }, + }, } for _, tt := range tests {