Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions apis/v1alpha2/nginxproxy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,12 @@ type NginxLogging struct {
// +optional
// +kubebuilder:default=info
AgentLevel *AgentLogLevel `json:"agentLevel,omitempty"`

// AccessLog defines the access log settings, including format itself and disabling option.
// For now only path /dev/stdout can be used.
//
// +optional
AccessLog *NginxAccessLog `json:"accessLog,omitempty"`
}

// NginxErrorLogLevel type defines the log level of error logs for NGINX.
Expand Down Expand Up @@ -352,6 +358,22 @@ const (
AgentLogLevelFatal AgentLogLevel = "fatal"
)

// NginxAccessLog defines the configuration for an NGINX access log.
type NginxAccessLog struct {
// Disabled turns off access logging when set to true.
//
// +optional
Disabled *bool `json:"disabled,omitempty"`

// Format specifies the custom log format string.
// If not specified, NGINX default 'combined' format is used.
// For now only path /dev/stdout can be used.
// See https://nginx.org/en/docs/http/ngx_http_log_module.html#log_format
//
// +optional
Format *string `json:"format,omitempty"`
}

// NginxPlus specifies NGINX Plus additional settings. These will only be applied if NGINX Plus is being used.
type NginxPlus struct {
// AllowedAddresses specifies IPAddresses or CIDR blocks to the allow list for accessing the NGINX Plus API.
Expand Down
30 changes: 30 additions & 0 deletions apis/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions config/crd/bases/gateway.nginx.org_nginxproxies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8016,6 +8016,23 @@ spec:
logging:
description: Logging defines logging related settings for NGINX.
properties:
accessLog:
description: |-
AccessLog defines the access log settings, including format itself and disabling option.
For now only path /dev/stdout can be used.
properties:
disabled:
description: Disabled turns off access logging when set to
true.
type: boolean
format:
description: |-
Format specifies the custom log format string.
If not specified, NGINX default 'combined' format is used.
For now only path /dev/stdout can be used.
See https://nginx.org/en/docs/http/ngx_http_log_module.html#log_format
type: string
type: object
agentLevel:
default: info
description: |-
Expand Down
17 changes: 17 additions & 0 deletions deploy/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8603,6 +8603,23 @@ spec:
logging:
description: Logging defines logging related settings for NGINX.
properties:
accessLog:
description: |-
AccessLog defines the access log settings, including format itself and disabling option.
For now only path /dev/stdout can be used.
properties:
disabled:
description: Disabled turns off access logging when set to
true.
type: boolean
format:
description: |-
Format specifies the custom log format string.
If not specified, NGINX default 'combined' format is used.
For now only path /dev/stdout can be used.
See https://nginx.org/en/docs/http/ngx_http_log_module.html#log_format
type: string
type: object
agentLevel:
default: info
description: |-
Expand Down
6 changes: 6 additions & 0 deletions internal/controller/nginx/config/base_http_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ var baseHTTPTemplate = gotemplate.Must(gotemplate.New("baseHttp").Parse(baseHTTP

type httpConfig struct {
DNSResolver *dataplane.DNSResolverConfig
AccessLog *dataplane.AccessLog
DefaultAccessLogPath string
DefaultLogFormatName string
Comment on lines +16 to +17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these fields belong in this type.

I'd recommend moving these to the AccessLog:

type AccessLog struct {
    Format     string  // User's format string
    Disabled   bool    // User's disabled flag
    Path       string  // Where to write logs (/dev/stdout)
    FormatName string  // Internal format name (ngf_user_defined_log_format)
}

And populate the constants in buildAccessLog().

Includes []shared.Include
NginxReadinessProbePort int32
IPFamily shared.IPFamily
Expand All @@ -27,6 +30,9 @@ func executeBaseHTTPConfig(conf dataplane.Configuration) []executeResult {
NginxReadinessProbePort: conf.BaseHTTPConfig.NginxReadinessProbePort,
IPFamily: getIPFamily(conf.BaseHTTPConfig),
DNSResolver: conf.BaseHTTPConfig.DNSResolver,
AccessLog: conf.Logging.AccessLog,
DefaultAccessLogPath: dataplane.DefaultAccessLogPath,
DefaultLogFormatName: dataplane.DefaultLogFormatName,
}

results := make([]executeResult, 0, len(includes)+1)
Expand Down
14 changes: 14 additions & 0 deletions internal/controller/nginx/config/base_http_config_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ server {
}
}

{{- /* Define custom log format */ -}}
{{- /* Access log directives for AccessLog. If path is "off" we disable logging. */ -}}
{{- /* We use a fixed name for user-defined log format to avoid complexity of passing the name around. */ -}}
{{- if .AccessLog }}
{{- if .AccessLog.Disabled }}
access_log off;
{{- else }}
{{- if .AccessLog.Format }}
log_format {{ .DefaultLogFormatName }} '{{ .AccessLog.Format }}';
access_log {{ .DefaultAccessLogPath }} {{ .DefaultLogFormatName }};
Comment on lines +59 to +60
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per the comment above, this would become

Suggested change
log_format {{ .DefaultLogFormatName }} '{{ .AccessLog.Format }}';
access_log {{ .DefaultAccessLogPath }} {{ .DefaultLogFormatName }};
log_format {{ .AccessLog.FormatName }} '{{ .AccessLog.Format }}';
access_log {{ .AccessLog.Path }} {{ .AccessLog.FormatName }};

{{- end }}
{{- end }}
{{- end }}

{{ range $i := .Includes -}}
include {{ $i.Name }};
{{ end -}}
Expand Down
72 changes: 72 additions & 0 deletions internal/controller/nginx/config/base_http_config_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"fmt"
"sort"
"strings"
"testing"
Expand All @@ -10,6 +11,77 @@ import (
"github.com/nginx/nginx-gateway-fabric/v2/internal/controller/state/dataplane"
)

func TestLoggingSettingsTemplate(t *testing.T) {
t.Parallel()

logFormat := "$remote_addr - [$time_local] \"$request\" $status $body_bytes_sent"

tests := []struct {
name string
accessLog *dataplane.AccessLog
expectedOutputs []string
unexpectedOutputs []string
}{
{
name: "Log format and access log with custom path and custom format name",
Copy link
Contributor

@ciarams87 ciarams87 Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
name: "Log format and access log with custom path and custom format name",
name: "Log format and access log with custom format",

accessLog: &dataplane.AccessLog{Format: logFormat},
expectedOutputs: []string{
fmt.Sprintf("log_format %s '%s'", dataplane.DefaultLogFormatName, logFormat),
fmt.Sprintf("access_log %s %s", dataplane.DefaultAccessLogPath, dataplane.DefaultLogFormatName),
},
},
{
name: "Empty format",
accessLog: &dataplane.AccessLog{Format: ""},
unexpectedOutputs: []string{
fmt.Sprintf("log_format %s '%s'", dataplane.DefaultLogFormatName, logFormat),
fmt.Sprintf("access_log %s %s", dataplane.DefaultAccessLogPath, dataplane.DefaultLogFormatName),
},
},
{
name: "Access log off while format presented",
accessLog: &dataplane.AccessLog{Disabled: true, Format: logFormat},
expectedOutputs: []string{
`access_log off;`,
},
unexpectedOutputs: []string{
fmt.Sprintf("access_log off %s", dataplane.DefaultLogFormatName),
},
},
{
name: "Access log off",
accessLog: &dataplane.AccessLog{Disabled: true},
expectedOutputs: []string{
`access_log off;`,
},
unexpectedOutputs: []string{
`log_format`,
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
g := NewWithT(t)

conf := dataplane.Configuration{
Logging: dataplane.Logging{AccessLog: tt.accessLog},
}

res := executeBaseHTTPConfig(conf)
g.Expect(res).To(HaveLen(1))
httpConfig := string(res[0].data)
for _, expectedOutput := range tt.expectedOutputs {
g.Expect(httpConfig).To(ContainSubstring(expectedOutput))
}
for _, unexpectedOutput := range tt.unexpectedOutputs {
g.Expect(httpConfig).ToNot(ContainSubstring(unexpectedOutput))
}
})
}
}

func TestExecuteBaseHttp_HTTP2(t *testing.T) {
t.Parallel()
confOn := dataplane.Configuration{
Expand Down
23 changes: 23 additions & 0 deletions internal/controller/provisioner/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,11 +406,18 @@ func (p *NginxProvisioner) buildNginxConfigMaps(
workerConnections = *nProxyCfg.WorkerConnections
}

// Add LogFormats and AccessLogs to mainFields
accessLog := addAccessLogsToNginxConfig(logging)

mainFields := map[string]interface{}{
"ErrorLevel": logLevel,
"WorkerConnections": workerConnections,
}

if accessLog != nil {
mainFields["AccessLog"] = accessLog
}

// Create events ConfigMap data using template
eventsFields := map[string]interface{}{
"WorkerConnections": workerConnections,
Expand Down Expand Up @@ -1436,3 +1443,19 @@ func DetermineNginxImageName(

return fmt.Sprintf("%s:%s", image, tag), pullPolicy
}

func addAccessLogsToNginxConfig(logging *ngfAPIv1alpha2.NginxLogging) *ngfAPIv1alpha2.NginxAccessLog {
accessLog := &ngfAPIv1alpha2.NginxAccessLog{}
if logging == nil {
return accessLog
}

if logging.AccessLog != nil {
accessLog = &ngfAPIv1alpha2.NginxAccessLog{
Disabled: logging.AccessLog.Disabled,
Format: logging.AccessLog.Format,
}
}

return accessLog
}
28 changes: 28 additions & 0 deletions internal/controller/state/dataplane/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const (
defaultErrorLogLevel = "info"
DefaultWorkerConnections = int32(1024)
DefaultNginxReadinessProbePort = int32(8081)
// DefaultLogFormatName is used when user provides custom access_log format.
DefaultLogFormatName = "ngf_user_defined_log_format"
// DefaultAccessLogPath is the default path for the access log.
DefaultAccessLogPath = "/dev/stdout"
)

// BuildConfiguration builds the Configuration from the Graph.
Expand Down Expand Up @@ -1206,6 +1210,8 @@ func convertAddresses(addresses []ngfAPIv1alpha2.RewriteClientIPAddress) []strin
return trustedAddresses
}

// buildLogging converts the API logging spec (currently singular LogFormat / AccessLog fields
// in v1alpha2) into internal slice-based representation used by templates.
func buildLogging(gateway *graph.Gateway) Logging {
logSettings := Logging{ErrorLevel: defaultErrorLogLevel}

Expand All @@ -1218,11 +1224,33 @@ func buildLogging(gateway *graph.Gateway) Logging {
if ngfProxy.Logging.ErrorLevel != nil {
logSettings.ErrorLevel = string(*ngfProxy.Logging.ErrorLevel)
}

srcLogSettings := ngfProxy.Logging

if accessLog := buildAccessLog(srcLogSettings); accessLog != nil {
logSettings.AccessLog = accessLog
}
}

return logSettings
}

func buildAccessLog(srcLogSettings *ngfAPIv1alpha2.NginxLogging) *AccessLog {
if srcLogSettings.AccessLog != nil {
if srcLogSettings.AccessLog.Disabled != nil && *srcLogSettings.AccessLog.Disabled {
return &AccessLog{Disabled: true}
}

if srcLogSettings.AccessLog.Format != nil && *srcLogSettings.AccessLog.Format != "" {
return &AccessLog{
Format: *srcLogSettings.AccessLog.Format,
}
}
}

return nil
}

func buildWorkerConnections(gateway *graph.Gateway) int32 {
if gateway == nil || gateway.EffectiveNginxProxy == nil {
return DefaultWorkerConnections
Expand Down
Loading
Loading