Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
136b189
Add custom time format to JSON and TEXT logging
AlexFenlon Aug 19, 2025
4a62d07
clean up unix formats
AlexFenlon Aug 19, 2025
4078573
Merge branch 'main' into feat/log-time-format
AlexFenlon Aug 19, 2025
773a3a0
Merge branch 'main' into feat/log-time-format
AlexFenlon Aug 19, 2025
9b93713
chore(deps): bump the python group across 1 directory with 2 updates …
dependabot[bot] Aug 20, 2025
2349205
Fix broken hyperlink to F5 Container Registry Download doc in README.…
ogipierogi Aug 20, 2025
53b5043
Ensure all links resolve correctly in README.md (#8171)
pdabelf5 Aug 20, 2025
5fa633e
add helm gh action (#8175)
vepatel Aug 21, 2025
1b1ce1e
[pre-commit.ci] pre-commit autoupdate (#8156)
pre-commit-ci[bot] Aug 21, 2025
9567041
Fix Helm tests failing with latest version (#8179)
AlexFenlon Aug 21, 2025
37dcb95
Remove existence check from templates before range (#8181)
javorszky Aug 22, 2025
36f1e7f
chore(deps): bump the go group with 3 updates (#8183)
dependabot[bot] Aug 22, 2025
a27a0f1
chore(deps): bump the actions group across 1 directory with 2 updates…
dependabot[bot] Aug 25, 2025
42acf0d
Docker image update 24ce9eb1 (#8173)
github-actions[bot] Aug 25, 2025
4573fdb
chore(deps): bump the go group with 3 updates (#8186)
dependabot[bot] Aug 26, 2025
071aebb
add globalConfigurationCustomName parameter (#8142)
mohamadaldawamnah Aug 27, 2025
88828dd
Update CI workflow_call boolean inputs (#8197)
pdabelf5 Aug 28, 2025
6a6ecca
Add support for FIPS 140-3 compliance (#8195)
AlexFenlon Aug 28, 2025
2b4fe20
chore(deps): bump kindest/node from v1.33.2 to v1.33.4 in /tests in t…
dependabot[bot] Aug 28, 2025
c0ea80e
remove http cache for jwks (#8198)
vepatel Aug 28, 2025
731a2ad
Docker image update 5784bad7 (#8188)
github-actions[bot] Aug 28, 2025
09bd7a9
chore(deps): bump actions/dependency-review-action from 4.7.2 to 4.7.…
dependabot[bot] Aug 28, 2025
0ee5562
chore(deps): bump the python group with 2 updates (#8187)
dependabot[bot] Aug 28, 2025
0e43467
chore(deps): bump the go group with 2 updates (#8200)
dependabot[bot] Aug 28, 2025
966b70b
Update proxy endpoint for published builds (#8194)
pdabelf5 Sep 1, 2025
4a01a67
Update NGINX Agent to 3.3 (#8208)
AlexFenlon Sep 1, 2025
ada8720
Add Safe Proxy Buffer Configuration Adjustments (#8133)
AlexFenlon Sep 9, 2025
72db468
chore(deps): bump the python group with 2 updates (#8210)
dependabot[bot] Sep 2, 2025
a4211c7
chore(deps): bump the actions group across 1 directory with 2 updates…
dependabot[bot] Sep 2, 2025
c1ca0cf
Adds sha256 hash to quay.io/skopeo import (#8196)
javorszky Sep 2, 2025
085a874
remove shared cache config from helm (#8211)
vepatel Sep 2, 2025
5377907
Fix gofumpt empty string errors (#8221)
AlexFenlon Sep 4, 2025
6870b6e
update golang to 1.25 (#8220)
vepatel Sep 4, 2025
4383407
chore(deps): bump the actions group with 7 updates (#8227)
dependabot[bot] Sep 5, 2025
1f45271
update prometheus metrics with N+ license expiry (#8229)
vepatel Sep 5, 2025
5b5a41d
Docker image update 92ac582e (#8228)
github-actions[bot] Sep 5, 2025
43f96d5
chore(deps): bump the go group across 1 directory with 5 updates (#8…
pdabelf5 Sep 5, 2025
c030128
Update README.md to include a pointer to the NGINX Community Forum (#…
dwmcallister Sep 5, 2025
bbee9f5
Correct yaml indentation for daemon-set readiness probe (#8233)
pdabelf5 Sep 5, 2025
7c59fd4
Fix Proxy Buffer Config Adjustments (#8226)
AlexFenlon Sep 5, 2025
679d3d3
chore(deps): bump the actions group with 2 updates (#8238)
dependabot[bot] Sep 8, 2025
d3dcfe2
StatefulSet support (#8159)
haywoodsh Sep 8, 2025
a246acd
Docker image update d2837402 (#8239)
github-actions[bot] Sep 8, 2025
25a9bc9
chore(deps): bump nginx/dependencies/nginx-ubi from `12b2f67` to `786…
dependabot[bot] Sep 8, 2025
9ab29da
chore(deps): bump the docker-tests group in /tests with 2 updates (#8…
dependabot[bot] Sep 8, 2025
404e079
Version Bump for 5.3.0 (#8241)
github-actions[bot] Sep 8, 2025
8e617cd
Merge remote-tracking branch 'refs/remotes/origin/main' into feat/log…
AlexFenlon Sep 9, 2025
4aff4e5
address comments
AlexFenlon Sep 9, 2025
d4a7440
Merge branch 'main' into feat/log-time-format
AlexFenlon Sep 9, 2025
f4df927
fix snaps
AlexFenlon Sep 9, 2025
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
1 change: 1 addition & 0 deletions charts/nginx-ingress/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ Build the args for the service binary.
- -nginx-debug={{ .Values.controller.nginxDebug }}
- -log-level={{ .Values.controller.logLevel }}
- -log-format={{ .Values.controller.logFormat }}
- -log-time-format={{ .Values.controller.logTimeFormat }}
- -nginx-status={{ .Values.controller.nginxStatus.enable }}
{{- if .Values.controller.nginxStatus.enable }}
- -nginx-status-port={{ .Values.controller.nginxStatus.port }}
Expand Down
3 changes: 3 additions & 0 deletions charts/nginx-ingress/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ controller:
## Sets the log format of Ingress Controller. Options include: glog, json, text
logFormat: glog

## Sets time format for logs. Allowed values: default, unix, unix-ms, unix-ns. Applies to json and text.
logTimeFormat: default

## Cache configuration options
cache:
## Enables shared cache across multiple pods using an external persistent volume
Expand Down
21 changes: 21 additions & 0 deletions charts/tests/__snapshots__/helmunit_test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -881,6 +882,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -1416,6 +1418,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -1902,6 +1905,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -2482,6 +2486,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -2933,6 +2938,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -3370,6 +3376,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -3813,6 +3820,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -4256,6 +4264,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -4721,6 +4730,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -5166,6 +5176,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -5664,6 +5675,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -6122,6 +6134,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -6589,6 +6602,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -7066,6 +7080,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -7524,6 +7539,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -7982,6 +7998,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -8450,6 +8467,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -8961,6 +8979,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -9473,6 +9492,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down Expand Up @@ -9925,6 +9945,7 @@ spec:
- -nginx-debug=false
- -log-level=info
- -log-format=glog
- -log-time-format=default
- -nginx-status=true
- -nginx-status-port=8080
- -nginx-status-allow-cidrs=127.0.0.1
Expand Down
22 changes: 22 additions & 0 deletions cmd/nginx-ingress/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
appProtectEnforcerAddrDefault = "127.0.0.1:50000"
logLevelDefault = "info"
logFormatDefault = "glog"
logTimeFormatDefault = "default"
)

var (
Expand Down Expand Up @@ -223,6 +224,9 @@ var (
logLevel = flag.String("log-level", logLevelDefault,
`Sets log level for Ingress Controller. Allowed values: fatal, error, warning, info, debug, trace.`)

logTimeFormat = flag.String("log-time-format", logTimeFormatDefault,
`Sets time format for logs. Allowed values: default, unix, unix-ms, unix-ns.`)

enableDynamicWeightChangesReload = flag.Bool(dynamicWeightChangesParam, false, "Enable changing weights of split clients without reloading NGINX. Requires -nginx-plus")

startupCheckFn func() error
Expand All @@ -249,6 +253,11 @@ func initValidate(ctx context.Context) {
nl.Warnf(l, "Invalid log level: %s. Valid options are: trace, debug, info, warning, error, fatal. Falling back to default: %s", *logLevel, logLevelDefault)
}

logTimeFormatValidationError := validateLogTimeFormat(*logTimeFormat)
if logTimeFormatValidationError != nil {
nl.Warnf(l, "Invalid log time format: %s. Valid options are: default, unix, unix-ms, unix-ns. Falling back to default: %s", *logTimeFormat, logTimeFormatDefault)
}

if *enableLatencyMetrics && !*enablePrometheusMetrics {
nl.Warn(l, "enable-latency-metrics flag requires enable-prometheus-metrics, latency metrics will not be collected")
*enableLatencyMetrics = false
Expand All @@ -269,6 +278,10 @@ func initValidate(ctx context.Context) {
*mgmtConfigMap = ""
}

if strings.ToLower(*logFormat) == "glog" && strings.ToLower(*logTimeFormat) != "default" {
nl.Warnf(l, "log-time-format '%s' is ignored when using log-format 'glog'. Use log-format 'json' or 'text' to apply custom time formatting.", *logTimeFormat)
}

mustValidateInitialChecks(ctx)
mustValidateWatchedNamespaces(ctx)
mustValidateFlags(ctx)
Expand Down Expand Up @@ -485,6 +498,15 @@ func validateLogFormat(logFormat string) error {
return fmt.Errorf("invalid log format: %v", logFormat)
}

// validateLogTimeFormat makes sure a given logTimeFormat is one of the allowed values
func validateLogTimeFormat(timeFormat string) error {
switch strings.ToLower(timeFormat) {
case "default", "unix", "unix-ms", "unix-ns":
return nil
}
return fmt.Errorf("invalid log time format: %v", timeFormat)
}

// parseNginxStatusAllowCIDRs converts a comma separated CIDR/IP address string into an array of CIDR/IP addresses.
// It returns an array of the valid CIDR/IP addresses or an error if given an invalid address.
func parseNginxStatusAllowCIDRs(input string) (cidrs []string, err error) {
Expand Down
34 changes: 32 additions & 2 deletions cmd/nginx-ingress/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func main() {
commitHash, commitTime, dirtyBuild := getBuildInfo()
fmt.Printf("NGINX Ingress Controller Version=%v Commit=%v Date=%v DirtyState=%v Arch=%v/%v Go=%v\n", version, commitHash, commitTime, dirtyBuild, runtime.GOOS, runtime.GOARCH, runtime.Version())
parseFlags()
ctx := initLogger(*logFormat, logLevels[*logLevel], os.Stdout)
ctx := initLogger(*logFormat, logLevels[*logLevel], *logTimeFormat, os.Stdout)
l := nl.LoggerFromContext(ctx)

initValidate(ctx)
Expand Down Expand Up @@ -1182,7 +1182,7 @@ func logEventAndExit(ctx context.Context, eventLog record.EventRecorder, obj pkg
nl.Fatal(l, err.Error())
}

func initLogger(logFormat string, level slog.Level, out io.Writer) context.Context {
func initLogger(logFormat string, level slog.Level, timeFormat string, out io.Writer) context.Context {
programLevel := new(slog.LevelVar) // Info by default
var h slog.Handler

Expand All @@ -1197,6 +1197,36 @@ func initLogger(logFormat string, level slog.Level, out io.Writer) context.Conte
a.Value = slog.AnyValue(src)
}
}
// Handle custom timestamp formatting
if a.Key == slog.TimeKey {
if t, ok := a.Value.Any().(time.Time); ok {
switch timeFormat {
case "unix":
// Unix timestamp in seconds
return slog.Attr{
Key: slog.TimeKey,
Value: slog.Int64Value(t.Unix()),
}
case "unix-ms":
// Unix timestamp with milliseconds
return slog.Attr{
Key: slog.TimeKey,
Value: slog.Int64Value(t.UnixMilli()),
}
case "unix-ns":
// Unix timestamp with nanoseconds
return slog.Attr{
Key: slog.TimeKey,
Value: slog.Int64Value(t.UnixNano()),
}
case "default":
Copy link
Contributor

Choose a reason for hiding this comment

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

this isn't needed here, because

  1. either we've already made sure that the time log format is one of the four allowed values, in which case if it's not the other three, then the switch's default arm is enough, or
  2. we haven't, and if we pass in some weird value like iso8601, which should not be allowed, that will also be ignored and rfc3339 will be printed, as per the original value coming from slog

fallthrough
default:
// Default timestamp format (keep original time key and format eg. RFC3339)
return a
}
}
}
return a
},
}
Expand Down
76 changes: 75 additions & 1 deletion cmd/nginx-ingress/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestLogFormats(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var buf bytes.Buffer
ctx := initLogger(tc.format, levels.LevelInfo, &buf)
ctx := initLogger(tc.format, levels.LevelInfo, "default", &buf)
l := nl.LoggerFromContext(ctx)
l.Log(ctx, levels.LevelInfo, "test")
got := buf.String()
Expand All @@ -61,6 +61,80 @@ func TestLogFormats(t *testing.T) {
}
}

func TestLogTimeFormats(t *testing.T) {
testCases := []struct {
name string
logFormat string
timeFormat string
wantre string
}{
// JSON format tests
{
name: "json default time format",
logFormat: "json",
timeFormat: "default",
wantre: `^{"time":"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+.*","level":"INFO","source":\{"file":"[^"]+\.go","line":\d+\},"msg":".*}`,
},
{
name: "json unix time format",
logFormat: "json",
timeFormat: "unix",
wantre: `^{"time":\d{10},"level":"INFO","source":\{"file":"[^"]+\.go","line":\d+\},"msg":".*}`,
},
{
name: "json unix-ms time format",
logFormat: "json",
timeFormat: "unix-ms",
wantre: `^{"time":\d{13},"level":"INFO","source":\{"file":"[^"]+\.go","line":\d+\},"msg":".*}`,
},
{
name: "json unix-ns time format",
logFormat: "json",
timeFormat: "unix-ns",
wantre: `^{"time":\d{19},"level":"INFO","source":\{"file":"[^"]+\.go","line":\d+\},"msg":".*}`,
},
// TEXT format tests
{
name: "text default time format",
logFormat: "text",
timeFormat: "default",
wantre: `^time=\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+.*level=\w+\ssource=[^:]+\.go:\d+\smsg=\w+`,
},
{
name: "text unix time format",
logFormat: "text",
timeFormat: "unix",
wantre: `^time=\d{10}\slevel=\w+\ssource=[^:]+\.go:\d+\smsg=\w+`,
},
{
name: "text unix-ms time format",
logFormat: "text",
timeFormat: "unix-ms",
wantre: `^time=\d{13}\slevel=\w+\ssource=[^:]+\.go:\d+\smsg=\w+`,
},
{
name: "text unix-ns time format",
logFormat: "text",
timeFormat: "unix-ns",
wantre: `^time=\d{19}\slevel=\w+\ssource=[^:]+\.go:\d+\smsg=\w+`,
},
}
t.Parallel()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var buf bytes.Buffer
ctx := initLogger(tc.logFormat, levels.LevelInfo, tc.timeFormat, &buf)
l := nl.LoggerFromContext(ctx)
l.Log(ctx, levels.LevelInfo, "test")
got := buf.String()
re := regexp.MustCompile(tc.wantre)
if !re.MatchString(got) {
t.Errorf("\ngot:\n%q\nwant regex:\n%q", got, tc.wantre)
}
})
}
}

func TestK8sVersionValidation(t *testing.T) {
testCases := []struct {
name string
Expand Down
Loading