mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 06:03:48 +03:00
logging: Implement Caddyfile support for filter encoder (#3578)
* logging: Implement Caddyfile support for filter encoder * logging: Add support for parsing IP masks from strings wip * logging: Implement Caddyfile support for ip_mask * logging: Get rid of unnecessary logic to allow strings, not that useful * logging: Add adapt test
This commit is contained in:
parent
b88e2b6a49
commit
309c1fec62
3 changed files with 225 additions and 9 deletions
69
caddytest/integration/caddyfile_adapt/log_filters.txt
Normal file
69
caddytest/integration/caddyfile_adapt/log_filters.txt
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
:80
|
||||||
|
|
||||||
|
log {
|
||||||
|
output stdout
|
||||||
|
format filter {
|
||||||
|
wrap console
|
||||||
|
fields {
|
||||||
|
request>headers>Authorization delete
|
||||||
|
request>headers>Server delete
|
||||||
|
request>remote_addr ip_mask {
|
||||||
|
ipv4 24
|
||||||
|
ipv6 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"logging": {
|
||||||
|
"logs": {
|
||||||
|
"default": {
|
||||||
|
"exclude": [
|
||||||
|
"http.log.access.log0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"log0": {
|
||||||
|
"writer": {
|
||||||
|
"output": "stdout"
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"fields": {
|
||||||
|
"request\u003eheaders\u003eAuthorization": {
|
||||||
|
"filter": "delete"
|
||||||
|
},
|
||||||
|
"request\u003eheaders\u003eServer": {
|
||||||
|
"filter": "delete"
|
||||||
|
},
|
||||||
|
"request\u003eremote_addr": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/buffer"
|
"go.uber.org/zap/buffer"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
|
@ -94,6 +96,80 @@ func (fe *FilterEncoder) Provision(ctx caddy.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
|
||||||
|
//
|
||||||
|
// filter {
|
||||||
|
// wrap <another encoder>
|
||||||
|
// fields {
|
||||||
|
// <field> <filter> {
|
||||||
|
// <filter options>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
func (fe *FilterEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
for d.Next() {
|
||||||
|
for d.NextBlock(0) {
|
||||||
|
switch d.Val() {
|
||||||
|
case "wrap":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
moduleName := d.Val()
|
||||||
|
mod, err := caddy.GetModule("caddy.logging.encoders." + moduleName)
|
||||||
|
if err != nil {
|
||||||
|
return d.Errf("getting log encoder module named '%s': %v", moduleName, err)
|
||||||
|
}
|
||||||
|
unm, ok := mod.New().(caddyfile.Unmarshaler)
|
||||||
|
if !ok {
|
||||||
|
return d.Errf("log encoder module '%s' is not a Caddyfile unmarshaler", mod)
|
||||||
|
}
|
||||||
|
err = unm.UnmarshalCaddyfile(d.NewFromNextSegment())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
enc, ok := unm.(zapcore.Encoder)
|
||||||
|
if !ok {
|
||||||
|
return d.Errf("module %s is not a zapcore.Encoder", mod)
|
||||||
|
}
|
||||||
|
fe.WrappedRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, nil)
|
||||||
|
|
||||||
|
case "fields":
|
||||||
|
for d.NextBlock(1) {
|
||||||
|
field := d.Val()
|
||||||
|
if !d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
filterName := d.Val()
|
||||||
|
mod, err := caddy.GetModule("caddy.logging.encoders.filter." + filterName)
|
||||||
|
if err != nil {
|
||||||
|
return d.Errf("getting log filter module named '%s': %v", filterName, err)
|
||||||
|
}
|
||||||
|
unm, ok := mod.New().(caddyfile.Unmarshaler)
|
||||||
|
if !ok {
|
||||||
|
return d.Errf("log encoder module '%s' is not a Caddyfile unmarshaler", mod)
|
||||||
|
}
|
||||||
|
err = unm.UnmarshalCaddyfile(d.NewFromNextSegment())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, ok := unm.(LogFieldFilter)
|
||||||
|
if !ok {
|
||||||
|
return d.Errf("module %s is not a LogFieldFilter", mod)
|
||||||
|
}
|
||||||
|
if fe.FieldsRaw == nil {
|
||||||
|
fe.FieldsRaw = make(map[string]json.RawMessage)
|
||||||
|
}
|
||||||
|
fe.FieldsRaw[field] = caddyconfig.JSONModuleObject(f, "filter", filterName, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return d.Errf("unrecognized subdirective %s", d.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// AddArray is part of the zapcore.ObjectEncoder interface.
|
// AddArray is part of the zapcore.ObjectEncoder interface.
|
||||||
// Array elements do not get filtered.
|
// Array elements do not get filtered.
|
||||||
func (fe FilterEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error {
|
func (fe FilterEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error {
|
||||||
|
@ -330,4 +406,5 @@ func (mom logObjectMarshalerWrapper) MarshalLogObject(_ zapcore.ObjectEncoder) e
|
||||||
var (
|
var (
|
||||||
_ zapcore.Encoder = (*FilterEncoder)(nil)
|
_ zapcore.Encoder = (*FilterEncoder)(nil)
|
||||||
_ zapcore.ObjectMarshaler = (*logObjectMarshalerWrapper)(nil)
|
_ zapcore.ObjectMarshaler = (*logObjectMarshalerWrapper)(nil)
|
||||||
|
_ caddyfile.Unmarshaler = (*FilterEncoder)(nil)
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,8 +16,10 @@ package logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,6 +46,11 @@ func (DeleteFilter) CaddyModule() caddy.ModuleInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
|
||||||
|
func (DeleteFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Filter filters the input field.
|
// Filter filters the input field.
|
||||||
func (DeleteFilter) Filter(in zapcore.Field) zapcore.Field {
|
func (DeleteFilter) Filter(in zapcore.Field) zapcore.Field {
|
||||||
in.Type = zapcore.SkipType
|
in.Type = zapcore.SkipType
|
||||||
|
@ -53,11 +60,14 @@ func (DeleteFilter) Filter(in zapcore.Field) zapcore.Field {
|
||||||
// IPMaskFilter is a Caddy log field filter that
|
// IPMaskFilter is a Caddy log field filter that
|
||||||
// masks IP addresses.
|
// masks IP addresses.
|
||||||
type IPMaskFilter struct {
|
type IPMaskFilter struct {
|
||||||
// The IPv4 range in CIDR notation.
|
// The IPv4 mask, as an subnet size CIDR.
|
||||||
IPv4CIDR int `json:"ipv4_cidr,omitempty"`
|
IPv4MaskRaw int `json:"ipv4_cidr,omitempty"`
|
||||||
|
|
||||||
// The IPv6 range in CIDR notation.
|
// The IPv6 mask, as an subnet size CIDR.
|
||||||
IPv6CIDR int `json:"ipv6_cidr,omitempty"`
|
IPv6MaskRaw int `json:"ipv6_cidr,omitempty"`
|
||||||
|
|
||||||
|
v4Mask net.IPMask
|
||||||
|
v6Mask net.IPMask
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaddyModule returns the Caddy module information.
|
// CaddyModule returns the Caddy module information.
|
||||||
|
@ -68,6 +78,58 @@ func (IPMaskFilter) CaddyModule() caddy.ModuleInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
|
||||||
|
func (m *IPMaskFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
for d.Next() {
|
||||||
|
for d.NextBlock(0) {
|
||||||
|
switch d.Val() {
|
||||||
|
case "ipv4":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
val, err := strconv.Atoi(d.Val())
|
||||||
|
if err != nil {
|
||||||
|
return d.Errf("error parsing %s: %v", d.Val(), err)
|
||||||
|
}
|
||||||
|
m.IPv4MaskRaw = val
|
||||||
|
|
||||||
|
case "ipv6":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
val, err := strconv.Atoi(d.Val())
|
||||||
|
if err != nil {
|
||||||
|
return d.Errf("error parsing %s: %v", d.Val(), err)
|
||||||
|
}
|
||||||
|
m.IPv6MaskRaw = val
|
||||||
|
|
||||||
|
default:
|
||||||
|
return d.Errf("unrecognized subdirective %s", d.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision parses m's IP masks, from integers.
|
||||||
|
func (m *IPMaskFilter) Provision(ctx caddy.Context) error {
|
||||||
|
parseRawToMask := func(rawField int, bitLen int) net.IPMask {
|
||||||
|
if rawField == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// we assume the int is a subnet size CIDR
|
||||||
|
// e.g. "16" being equivalent to masking the last
|
||||||
|
// two bytes of an ipv4 address, like "255.255.0.0"
|
||||||
|
return net.CIDRMask(rawField, bitLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.v4Mask = parseRawToMask(m.IPv4MaskRaw, 32)
|
||||||
|
m.v6Mask = parseRawToMask(m.IPv6MaskRaw, 128)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Filter filters the input field.
|
// Filter filters the input field.
|
||||||
func (m IPMaskFilter) Filter(in zapcore.Field) zapcore.Field {
|
func (m IPMaskFilter) Filter(in zapcore.Field) zapcore.Field {
|
||||||
host, port, err := net.SplitHostPort(in.String)
|
host, port, err := net.SplitHostPort(in.String)
|
||||||
|
@ -78,13 +140,10 @@ func (m IPMaskFilter) Filter(in zapcore.Field) zapcore.Field {
|
||||||
if ipAddr == nil {
|
if ipAddr == nil {
|
||||||
return in
|
return in
|
||||||
}
|
}
|
||||||
bitLen := 32
|
mask := m.v4Mask
|
||||||
cidrPrefix := m.IPv4CIDR
|
|
||||||
if ipAddr.To16() != nil {
|
if ipAddr.To16() != nil {
|
||||||
bitLen = 128
|
mask = m.v6Mask
|
||||||
cidrPrefix = m.IPv6CIDR
|
|
||||||
}
|
}
|
||||||
mask := net.CIDRMask(cidrPrefix, bitLen)
|
|
||||||
masked := ipAddr.Mask(mask)
|
masked := ipAddr.Mask(mask)
|
||||||
if port == "" {
|
if port == "" {
|
||||||
in.String = masked.String()
|
in.String = masked.String()
|
||||||
|
@ -93,3 +152,14 @@ func (m IPMaskFilter) Filter(in zapcore.Field) zapcore.Field {
|
||||||
}
|
}
|
||||||
return in
|
return in
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Interface guards
|
||||||
|
var (
|
||||||
|
_ LogFieldFilter = (*DeleteFilter)(nil)
|
||||||
|
_ LogFieldFilter = (*IPMaskFilter)(nil)
|
||||||
|
|
||||||
|
_ caddyfile.Unmarshaler = (*DeleteFilter)(nil)
|
||||||
|
_ caddyfile.Unmarshaler = (*IPMaskFilter)(nil)
|
||||||
|
|
||||||
|
_ caddy.Provisioner = (*IPMaskFilter)(nil)
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in a new issue