log: don't allow overwriting singly-assigned vals (#7927)

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
pull/7918/head^2
Mohammed Al Sahaf 2026-08-08 00:59:12 +03:00 committed by GitHub
parent e096ca9503
commit 64b64c61eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 76 additions and 0 deletions

View File

@ -995,6 +995,17 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
// with a wildcard domain
customHostnames := []string{}
noHostname := false
// track single-valued subdirectives so that repeating one is an error
// rather than silently overriding the previous value; see
// https://caddy.community/t/logs-not-getting-written-to-journal/33852
seen := make(map[string]bool)
assertUnique := func(subdir string) error {
if seen[subdir] {
return h.Errf("'%s' subdirective specified more than once in the same log block", subdir)
}
seen[subdir] = true
return nil
}
for h.NextBlock(0) {
switch h.Val() {
case "hostnames":
@ -1008,6 +1019,9 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
customHostnames = append(customHostnames, args...)
case "output":
if err := assertUnique(h.Val()); err != nil {
return nil, err
}
if !h.NextArg() {
return nil, h.ArgErr()
}
@ -1040,14 +1054,22 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
cl.WriterRaw = caddyconfig.JSONModuleObject(wo, "output", moduleName, h.warnings)
case "sampling":
if err := assertUnique(h.Val()); err != nil {
return nil, err
}
d := h.Dispenser.NewFromNextSegment()
for d.NextArg() {
// consume any tokens on the same line, if any.
}
sampling := &caddy.LogSampling{}
samplingSeen := make(map[string]bool)
for nesting := d.Nesting(); d.NextBlock(nesting); {
subdir := d.Val()
if samplingSeen[subdir] {
return nil, d.Errf("'%s' specified more than once in the same sampling block", subdir)
}
samplingSeen[subdir] = true
switch subdir {
case "interval":
if !d.NextArg() {
@ -1084,6 +1106,9 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
cl.Sampling = sampling
case "core":
if err := assertUnique(h.Val()); err != nil {
return nil, err
}
if !h.NextArg() {
return nil, h.ArgErr()
}
@ -1100,6 +1125,9 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
cl.CoreRaw = caddyconfig.JSONModuleObject(core, "module", moduleName, h.warnings)
case "format":
if err := assertUnique(h.Val()); err != nil {
return nil, err
}
if !h.NextArg() {
return nil, h.ArgErr()
}
@ -1116,6 +1144,9 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
cl.EncoderRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, h.warnings)
case "level":
if err := assertUnique(h.Val()); err != nil {
return nil, err
}
if !h.NextArg() {
return nil, h.ArgErr()
}
@ -1141,6 +1172,9 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
}
case "no_hostname":
if err := assertUnique(h.Val()); err != nil {
return nil, err
}
if h.NextArg() {
return nil, h.ArgErr()
}

View File

@ -76,6 +76,48 @@ func TestLogDirectiveSyntax(t *testing.T) {
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.log0"]},"log0":{"sampling":{"interval":2000000000,"first":3,"thereafter":4},"include":["http.log.access.log0"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"log0"}}}}}}`,
expectError: false,
},
{
input: `:8080 {
log {
output stdout
output file foo.log
}
}
`,
expectError: true,
},
{
input: `:8080 {
log {
format json
format console
}
}
`,
expectError: true,
},
{
input: `:8080 {
log {
level INFO
level DEBUG
}
}
`,
expectError: true,
},
{
input: `:8080 {
log {
sampling {
interval 1s
interval 2s
}
}
}
`,
expectError: true,
},
} {
adapter := caddyfile.Adapter{