Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
:80

log {
output stdout
format filter {
wrap console

# Multiple regexp filters for the same field - this should work now!
request>headers>Authorization regexp "Bearer\s+([A-Za-z0-9_-]+)" "Bearer [REDACTED]"
request>headers>Authorization regexp "Basic\s+([A-Za-z0-9+/=]+)" "Basic [REDACTED]"
request>headers>Authorization regexp "token=([^&\s]+)" "token=[REDACTED]"

# Single regexp filter - this should continue to work as before
request>headers>Cookie regexp "sessionid=[^;]+" "sessionid=[REDACTED]"

# Mixed filters (non-regexp) - these should work normally
request>headers>Server delete
request>remote_ip ip_mask {
ipv4 24
ipv6 32
}
}
}
----------
{
"logging": {
"logs": {
"default": {
"exclude": [
"http.log.access.log0"
]
},
"log0": {
"writer": {
"output": "stdout"
},
"encoder": {
"fields": {
"request\u003eheaders\u003eAuthorization": {
"filter": "multi_regexp",
"operations": [
{
"regexp": "Bearer\\s+([A-Za-z0-9_-]+)",
"value": "Bearer [REDACTED]"
},
{
"regexp": "Basic\\s+([A-Za-z0-9+/=]+)",
"value": "Basic [REDACTED]"
},
{
"regexp": "token=([^\u0026\\s]+)",
"value": "token=[REDACTED]"
}
]
},
"request\u003eheaders\u003eCookie": {
"filter": "regexp",
"regexp": "sessionid=[^;]+",
"value": "sessionid=[REDACTED]"
},
"request\u003eheaders\u003eServer": {
"filter": "delete"
},
"request\u003eremote_ip": {
"filter": "ip_mask",
"ipv4_cidr": 24,
"ipv6_cidr": 32
}
},
"format": "filter",
"wrap": {
"format": "console"
}
},
"include": [
"http.log.access.log0"
]
}
}
},
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":80"
],
"logs": {
"default_logger_name": "log0"
}
}
}
}
}
}
39 changes: 39 additions & 0 deletions modules/logging/filterencoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ func (fe *FilterEncoder) ConfigureDefaultFormat(wo caddy.WriterOpener) error {
func (fe *FilterEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
d.Next() // consume encoder name

// Track regexp filters for automatic merging
regexpFilters := make(map[string][]*RegexpFilter)

// parse a field
parseField := func() error {
if fe.FieldsRaw == nil {
Expand All @@ -171,6 +174,23 @@ func (fe *FilterEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if !ok {
return d.Errf("module %s (%T) is not a logging.LogFieldFilter", moduleID, unm)
}

// Special handling for regexp filters to support multiple instances
if regexpFilter, isRegexp := filter.(*RegexpFilter); isRegexp {
regexpFilters[field] = append(regexpFilters[field], regexpFilter)
return nil // Don't set FieldsRaw yet, we'll merge them later
}

// Check if we're trying to add a non-regexp filter to a field that already has regexp filters
if _, hasRegexpFilters := regexpFilters[field]; hasRegexpFilters {
return d.Errf("cannot mix regexp filters with other filter types for field %s", field)
}

// Check if field already has a filter and it's not regexp-related
if _, exists := fe.FieldsRaw[field]; exists {
return d.Errf("field %s already has a filter; multiple non-regexp filters per field are not supported", field)
}

fe.FieldsRaw[field] = caddyconfig.JSONModuleObject(filter, "filter", filterName, nil)
return nil
}
Expand Down Expand Up @@ -210,6 +230,25 @@ func (fe *FilterEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
}
}

// After parsing all fields, merge multiple regexp filters into MultiRegexpFilter
for field, filters := range regexpFilters {
if len(filters) == 1 {
// Single regexp filter, use the original RegexpFilter
fe.FieldsRaw[field] = caddyconfig.JSONModuleObject(filters[0], "filter", "regexp", nil)
} else {
// Multiple regexp filters, merge into MultiRegexpFilter
multiFilter := &MultiRegexpFilter{}
for _, regexpFilter := range filters {
err := multiFilter.AddOperation(regexpFilter.RawRegexp, regexpFilter.Value)
if err != nil {
return fmt.Errorf("adding regexp operation for field %s: %v", field, err)
}
}
fe.FieldsRaw[field] = caddyconfig.JSONModuleObject(multiFilter, "filter", "multi_regexp", nil)
}
}

return nil
}

Expand Down
Loading
Loading