From b00dfd3965f400956c5bb5b388e9d54ef98052e5 Mon Sep 17 00:00:00 2001 From: Matt Holt Date: Mon, 28 Oct 2019 14:39:37 -0600 Subject: v2: Logging! (#2831) * logging: Initial implementation * logging: More encoder formats, better defaults * logging: Fix repetition bug with FilterEncoder; add more presets * logging: DiscardWriter; delete or no-op logs that discard their output * logging: Add http.handlers.log module; enhance Replacer methods The Replacer interface has new methods to customize how to handle empty or unrecognized placeholders. Closes #2815. * logging: Overhaul HTTP logging, fix bugs, improve filtering, etc. * logging: General cleanup, begin transitioning to using new loggers * Fixes after merge conflict --- modules/logging/encoders.go | 268 ++++++++++++++++++++++++++++++++ modules/logging/filewriter.go | 91 +++++++++++ modules/logging/filterencoder.go | 321 +++++++++++++++++++++++++++++++++++++++ modules/logging/filters.go | 94 ++++++++++++ modules/logging/nopencoder.go | 114 ++++++++++++++ 5 files changed, 888 insertions(+) create mode 100644 modules/logging/encoders.go create mode 100644 modules/logging/filewriter.go create mode 100644 modules/logging/filterencoder.go create mode 100644 modules/logging/filters.go create mode 100644 modules/logging/nopencoder.go (limited to 'modules/logging') diff --git a/modules/logging/encoders.go b/modules/logging/encoders.go new file mode 100644 index 0000000..c3c8aba --- /dev/null +++ b/modules/logging/encoders.go @@ -0,0 +1,268 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logging + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/caddyserver/caddy/v2" + zaplogfmt "github.com/jsternberg/zap-logfmt" + "go.uber.org/zap" + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" +) + +func init() { + caddy.RegisterModule(ConsoleEncoder{}) + caddy.RegisterModule(JSONEncoder{}) + caddy.RegisterModule(LogfmtEncoder{}) + caddy.RegisterModule(StringEncoder{}) +} + +// ConsoleEncoder encodes log entries that are mostly human-readable. +type ConsoleEncoder struct { + zapcore.Encoder + LogEncoderConfig +} + +// CaddyModule returns the Caddy module information. +func (ConsoleEncoder) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + Name: "caddy.logging.encoders.console", + New: func() caddy.Module { return new(ConsoleEncoder) }, + } +} + +// Provision sets up the encoder. +func (ce *ConsoleEncoder) Provision(_ caddy.Context) error { + ce.Encoder = zapcore.NewConsoleEncoder(ce.ZapcoreEncoderConfig()) + return nil +} + +// JSONEncoder encodes entries as JSON. +type JSONEncoder struct { + zapcore.Encoder + *LogEncoderConfig +} + +// CaddyModule returns the Caddy module information. +func (JSONEncoder) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + Name: "caddy.logging.encoders.json", + New: func() caddy.Module { return new(JSONEncoder) }, + } +} + +// Provision sets up the encoder. +func (je *JSONEncoder) Provision(_ caddy.Context) error { + je.Encoder = zapcore.NewJSONEncoder(je.ZapcoreEncoderConfig()) + return nil +} + +// LogfmtEncoder encodes log entries as logfmt: +// https://www.brandur.org/logfmt +type LogfmtEncoder struct { + zapcore.Encoder + LogEncoderConfig +} + +// CaddyModule returns the Caddy module information. +func (LogfmtEncoder) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + Name: "caddy.logging.encoders.logfmt", + New: func() caddy.Module { return new(LogfmtEncoder) }, + } +} + +// Provision sets up the encoder. +func (lfe *LogfmtEncoder) Provision(_ caddy.Context) error { + lfe.Encoder = zaplogfmt.NewEncoder(lfe.ZapcoreEncoderConfig()) + return nil +} + +// StringEncoder writes a log entry that consists entirely +// of a single string field in the log entry. This is useful +// for custom, self-encoded log entries that consist of a +// single field in the structured log entry. +type StringEncoder struct { + zapcore.Encoder + FieldName string `json:"field,omitempty"` + FallbackRaw json.RawMessage `json:"fallback,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (StringEncoder) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + Name: "caddy.logging.encoders.string", + New: func() caddy.Module { return new(StringEncoder) }, + } +} + +// Provision sets up the encoder. +func (se *StringEncoder) Provision(ctx caddy.Context) error { + if se.FallbackRaw != nil { + val, err := ctx.LoadModuleInline("format", "caddy.logging.encoders", se.FallbackRaw) + if err != nil { + return fmt.Errorf("loading fallback encoder module: %v", err) + } + se.FallbackRaw = nil // allow GC to deallocate + se.Encoder = val.(zapcore.Encoder) + } + if se.Encoder == nil { + se.Encoder = nopEncoder{} + } + return nil +} + +// Clone wraps the underlying encoder's Clone. This is +// necessary because we implement our own EncodeEntry, +// and if we simply let the embedded encoder's Clone +// be promoted, it would return a clone of that, and +// we'd lose our StringEncoder's EncodeEntry. +func (se StringEncoder) Clone() zapcore.Encoder { + return StringEncoder{ + Encoder: se.Encoder.Clone(), + FieldName: se.FieldName, + } +} + +// EncodeEntry partially implements the zapcore.Encoder interface. +func (se StringEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { + for _, f := range fields { + if f.Key == se.FieldName { + buf := bufferpool.Get() + buf.AppendString(f.String) + if !strings.HasSuffix(f.String, "\n") { + buf.AppendByte('\n') + } + return buf, nil + } + } + if se.Encoder == nil { + return nil, fmt.Errorf("no fallback encoder defined") + } + return se.Encoder.EncodeEntry(ent, fields) +} + +// LogEncoderConfig holds configuration common to most encoders. +type LogEncoderConfig struct { + MessageKey *string `json:"message_key,omitempty"` + LevelKey *string `json:"level_key,omitempty"` + TimeKey *string `json:"time_key,omitempty"` + NameKey *string `json:"name_key,omitempty"` + CallerKey *string `json:"caller_key,omitempty"` + StacktraceKey *string `json:"stacktrace_key,omitempty"` + LineEnding *string `json:"line_ending,omitempty"` + TimeFormat string `json:"time_format,omitempty"` + DurationFormat string `json:"duration_format,omitempty"` + LevelFormat string `json:"level_format,omitempty"` +} + +// ZapcoreEncoderConfig returns the equivalent zapcore.EncoderConfig. +// If lec is nil, zap.NewProductionEncoderConfig() is returned. +func (lec *LogEncoderConfig) ZapcoreEncoderConfig() zapcore.EncoderConfig { + cfg := zap.NewProductionEncoderConfig() + if lec == nil { + lec = new(LogEncoderConfig) + } + if lec.MessageKey != nil { + cfg.MessageKey = *lec.MessageKey + } + if lec.TimeKey != nil { + cfg.TimeKey = *lec.TimeKey + } + if lec.NameKey != nil { + cfg.NameKey = *lec.NameKey + } + if lec.CallerKey != nil { + cfg.CallerKey = *lec.CallerKey + } + if lec.StacktraceKey != nil { + cfg.StacktraceKey = *lec.StacktraceKey + } + if lec.LineEnding != nil { + cfg.LineEnding = *lec.LineEnding + } + + // time format + var timeFormatter zapcore.TimeEncoder + switch lec.TimeFormat { + case "", "unix_seconds_float": + timeFormatter = zapcore.EpochTimeEncoder + case "unix_milli_float": + timeFormatter = zapcore.EpochMillisTimeEncoder + case "unix_nano": + timeFormatter = zapcore.EpochNanosTimeEncoder + case "iso8601": + timeFormatter = zapcore.ISO8601TimeEncoder + default: + timeFormat := lec.TimeFormat + switch lec.TimeFormat { + case "rfc3339": + timeFormat = time.RFC3339 + case "rfc3339_nano": + timeFormat = time.RFC3339Nano + case "wall": + timeFormat = "2006/01/02 15:04:05" + case "wall_milli": + timeFormat = "2006/01/02 15:04:05.000" + case "wall_nano": + timeFormat = "2006/01/02 15:04:05.000000000" + } + timeFormatter = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) { + encoder.AppendString(ts.UTC().Format(timeFormat)) + } + } + cfg.EncodeTime = timeFormatter + + // duration format + var durFormatter zapcore.DurationEncoder + switch lec.DurationFormat { + case "", "seconds": + durFormatter = zapcore.SecondsDurationEncoder + case "nano": + durFormatter = zapcore.NanosDurationEncoder + case "string": + durFormatter = zapcore.StringDurationEncoder + } + cfg.EncodeDuration = durFormatter + + // level format + var levelFormatter zapcore.LevelEncoder + switch lec.LevelFormat { + case "", "lower": + levelFormatter = zapcore.LowercaseLevelEncoder + case "upper": + levelFormatter = zapcore.CapitalLevelEncoder + case "color": + levelFormatter = zapcore.CapitalColorLevelEncoder + } + cfg.EncodeLevel = levelFormatter + + return cfg +} + +var bufferpool = buffer.NewPool() + +// Interface guards +var ( + _ zapcore.Encoder = (*ConsoleEncoder)(nil) + _ zapcore.Encoder = (*JSONEncoder)(nil) + _ zapcore.Encoder = (*LogfmtEncoder)(nil) + _ zapcore.Encoder = (*StringEncoder)(nil) +) diff --git a/modules/logging/filewriter.go b/modules/logging/filewriter.go new file mode 100644 index 0000000..29e805e --- /dev/null +++ b/modules/logging/filewriter.go @@ -0,0 +1,91 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logging + +import ( + "io" + "os" + "path/filepath" + + "github.com/caddyserver/caddy/v2" + "gopkg.in/natefinch/lumberjack.v2" +) + +func init() { + caddy.RegisterModule(FileWriter{}) +} + +// FileWriter can write logs to files. +type FileWriter struct { + Filename string `json:"filename,omitempty"` + Roll *bool `json:"roll,omitempty"` + RollSizeMB int `json:"roll_size_mb,omitempty"` + RollCompress *bool `json:"roll_gzip,omitempty"` + RollLocalTime bool `json:"roll_local_time,omitempty"` + RollKeep int `json:"roll_keep,omitempty"` + RollKeepDays int `json:"roll_keep_days,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (FileWriter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + Name: "caddy.logging.writers.file", + New: func() caddy.Module { return new(FileWriter) }, + } +} + +func (fw FileWriter) String() string { + fpath, err := filepath.Abs(fw.Filename) + if err == nil { + return fpath + } + return fw.Filename +} + +// WriterKey returns a unique key representing this fw. +func (fw FileWriter) WriterKey() string { + return "file:" + fw.Filename +} + +// OpenWriter opens a new file writer. +func (fw FileWriter) OpenWriter() (io.WriteCloser, error) { + // roll log files by default + if fw.Roll == nil || *fw.Roll == true { + if fw.RollSizeMB == 0 { + fw.RollSizeMB = 100 + } + if fw.RollCompress == nil { + compress := true + fw.RollCompress = &compress + } + if fw.RollKeep == 0 { + fw.RollKeep = 10 + } + if fw.RollKeepDays == 0 { + fw.RollKeepDays = 90 + } + return &lumberjack.Logger{ + Filename: fw.Filename, + MaxSize: fw.RollSizeMB, + MaxAge: fw.RollKeepDays, + MaxBackups: fw.RollKeep, + LocalTime: fw.RollLocalTime, + Compress: *fw.RollCompress, + }, nil + } + + // otherwise just open a regular file + return os.OpenFile(fw.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666) +} diff --git a/modules/logging/filterencoder.go b/modules/logging/filterencoder.go new file mode 100644 index 0000000..eff0279 --- /dev/null +++ b/modules/logging/filterencoder.go @@ -0,0 +1,321 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logging + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/caddyserver/caddy/v2" + "go.uber.org/zap" + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" +) + +func init() { + caddy.RegisterModule(FilterEncoder{}) +} + +// FilterEncoder wraps an underlying encoder. It does +// not do any encoding itself, but it can manipulate +// (filter) fields before they are actually encoded. +// A wrapped encoder is required. +type FilterEncoder struct { + WrappedRaw json.RawMessage `json:"wrap,omitempty"` + FieldsRaw map[string]json.RawMessage `json:"fields,omitempty"` + + wrapped zapcore.Encoder + Fields map[string]LogFieldFilter `json:"-"` + + // used to keep keys unique across nested objects + keyPrefix string +} + +// CaddyModule returns the Caddy module information. +func (FilterEncoder) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + Name: "caddy.logging.encoders.filter", + New: func() caddy.Module { return new(FilterEncoder) }, + } +} + +// Provision sets up the encoder. +func (fe *FilterEncoder) Provision(ctx caddy.Context) error { + if fe.WrappedRaw == nil { + return fmt.Errorf("missing \"wrap\" (must specify an underlying encoder)") + } + + // set up wrapped encoder (required) + val, err := ctx.LoadModuleInline("format", "caddy.logging.encoders", fe.WrappedRaw) + if err != nil { + return fmt.Errorf("loading fallback encoder module: %v", err) + } + fe.WrappedRaw = nil // allow GC to deallocate + fe.wrapped = val.(zapcore.Encoder) + + // set up each field filter + if fe.Fields == nil { + fe.Fields = make(map[string]LogFieldFilter) + } + for field, filterRaw := range fe.FieldsRaw { + if filterRaw == nil { + continue + } + val, err := ctx.LoadModuleInline("filter", "caddy.logging.encoders.filter", filterRaw) + if err != nil { + return fmt.Errorf("loading log filter module: %v", err) + } + fe.Fields[field] = val.(LogFieldFilter) + } + fe.FieldsRaw = nil // allow GC to deallocate + + return nil +} + +// AddArray is part of the zapcore.ObjectEncoder interface. +// Array elements do not get filtered. +func (fe FilterEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error { + if filter, ok := fe.Fields[fe.keyPrefix+key]; ok { + filter.Filter(zap.Array(key, marshaler)).AddTo(fe.wrapped) + return nil + } + return fe.wrapped.AddArray(key, marshaler) +} + +// AddObject is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error { + fe.keyPrefix += key + ">" + return fe.wrapped.AddObject(key, logObjectMarshalerWrapper{ + enc: fe, + marsh: marshaler, + }) +} + +// AddBinary is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddBinary(key string, value []byte) { + if !fe.filtered(key, value) { + fe.wrapped.AddBinary(key, value) + } +} + +// AddByteString is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddByteString(key string, value []byte) { + if !fe.filtered(key, value) { + fe.wrapped.AddByteString(key, value) + } +} + +// AddBool is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddBool(key string, value bool) { + if !fe.filtered(key, value) { + fe.wrapped.AddBool(key, value) + } +} + +// AddComplex128 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddComplex128(key string, value complex128) { + if !fe.filtered(key, value) { + fe.wrapped.AddComplex128(key, value) + } +} + +// AddComplex64 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddComplex64(key string, value complex64) { + if !fe.filtered(key, value) { + fe.wrapped.AddComplex64(key, value) + } +} + +// AddDuration is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddDuration(key string, value time.Duration) { + if !fe.filtered(key, value) { + fe.wrapped.AddDuration(key, value) + } +} + +// AddFloat64 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddFloat64(key string, value float64) { + if !fe.filtered(key, value) { + fe.wrapped.AddFloat64(key, value) + } +} + +// AddFloat32 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddFloat32(key string, value float32) { + if !fe.filtered(key, value) { + fe.wrapped.AddFloat32(key, value) + } +} + +// AddInt is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddInt(key string, value int) { + if !fe.filtered(key, value) { + fe.wrapped.AddInt(key, value) + } +} + +// AddInt64 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddInt64(key string, value int64) { + if !fe.filtered(key, value) { + fe.wrapped.AddInt64(key, value) + } +} + +// AddInt32 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddInt32(key string, value int32) { + if !fe.filtered(key, value) { + fe.wrapped.AddInt32(key, value) + } +} + +// AddInt16 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddInt16(key string, value int16) { + if !fe.filtered(key, value) { + fe.wrapped.AddInt16(key, value) + } +} + +// AddInt8 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddInt8(key string, value int8) { + if !fe.filtered(key, value) { + fe.wrapped.AddInt8(key, value) + } +} + +// AddString is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddString(key, value string) { + if !fe.filtered(key, value) { + fe.wrapped.AddString(key, value) + } +} + +// AddTime is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddTime(key string, value time.Time) { + if !fe.filtered(key, value) { + fe.wrapped.AddTime(key, value) + } +} + +// AddUint is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddUint(key string, value uint) { + if !fe.filtered(key, value) { + fe.wrapped.AddUint(key, value) + } +} + +// AddUint64 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddUint64(key string, value uint64) { + if !fe.filtered(key, value) { + fe.wrapped.AddUint64(key, value) + } +} + +// AddUint32 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddUint32(key string, value uint32) { + if !fe.filtered(key, value) { + fe.wrapped.AddUint32(key, value) + } +} + +// AddUint16 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddUint16(key string, value uint16) { + if !fe.filtered(key, value) { + fe.wrapped.AddUint16(key, value) + } +} + +// AddUint8 is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddUint8(key string, value uint8) { + if !fe.filtered(key, value) { + fe.wrapped.AddUint8(key, value) + } +} + +// AddUintptr is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddUintptr(key string, value uintptr) { + if !fe.filtered(key, value) { + fe.wrapped.AddUintptr(key, value) + } +} + +// AddReflected is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) AddReflected(key string, value interface{}) error { + if !fe.filtered(key, value) { + return fe.wrapped.AddReflected(key, value) + } + return nil +} + +// OpenNamespace is part of the zapcore.ObjectEncoder interface. +func (fe FilterEncoder) OpenNamespace(key string) { + fe.wrapped.OpenNamespace(key) +} + +// Clone is part of the zapcore.ObjectEncoder interface. +// We don't use it as of Oct 2019 (v2 beta 7), I'm not +// really sure what it'd be useful for in our case. +func (fe FilterEncoder) Clone() zapcore.Encoder { + return FilterEncoder{ + Fields: fe.Fields, + wrapped: fe.wrapped.Clone(), + keyPrefix: fe.keyPrefix, + } +} + +// EncodeEntry partially implements the zapcore.Encoder interface. +func (fe FilterEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { + // without this clone and storing it to fe.wrapped, fields + // from subsequent log entries get appended to previous + // ones, and I'm not 100% sure why; see end of + // https://github.com/uber-go/zap/issues/750 + fe.wrapped = fe.wrapped.Clone() + for _, field := range fields { + field.AddTo(fe) + } + return fe.wrapped.EncodeEntry(ent, nil) +} + +// filtered returns true if the field was filtered. +// If true is returned, the field was filtered and +// added to the underlying encoder (so do not do +// that again). If false was returned, the field has +// not yet been added to the underlying encoder. +func (fe FilterEncoder) filtered(key string, value interface{}) bool { + filter, ok := fe.Fields[fe.keyPrefix+key] + if !ok { + return false + } + filter.Filter(zap.Any(key, value)).AddTo(fe.wrapped) + return true +} + +// logObjectMarshalerWrapper allows us to recursively +// filter fields of objects as they get encoded. +type logObjectMarshalerWrapper struct { + enc FilterEncoder + marsh zapcore.ObjectMarshaler +} + +// MarshalLogObject implements the zapcore.ObjectMarshaler interface. +func (mom logObjectMarshalerWrapper) MarshalLogObject(_ zapcore.ObjectEncoder) error { + return mom.marsh.MarshalLogObject(mom.enc) +} + +// Interface guards +var ( + _ zapcore.Encoder = (*FilterEncoder)(nil) + _ zapcore.ObjectMarshaler = (*logObjectMarshalerWrapper)(nil) +) diff --git a/modules/logging/filters.go b/modules/logging/filters.go new file mode 100644 index 0000000..b44e084 --- /dev/null +++ b/modules/logging/filters.go @@ -0,0 +1,94 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logging + +import ( + "net" + + "github.com/caddyserver/caddy/v2" + "go.uber.org/zap/zapcore" +) + +func init() { + caddy.RegisterModule(DeleteFilter{}) + caddy.RegisterModule(IPMaskFilter{}) +} + +// LogFieldFilter can filter (or manipulate) +// a field in a log entry. If delete is true, +// out will be ignored and the field will be +// removed from the output. +type LogFieldFilter interface { + Filter(zapcore.Field) zapcore.Field +} + +// DeleteFilter is a Caddy log field filter that +// deletes the field. +type DeleteFilter struct{} + +// CaddyModule returns the Caddy module information. +func (DeleteFilter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + Name: "caddy.logging.encoders.filter.delete", + New: func() caddy.Module { return new(DeleteFilter) }, + } +} + +// Filter filters the input field. +func (DeleteFilter) Filter(in zapcore.Field) zapcore.Field { + in.Type = zapcore.SkipType + return in +} + +// IPMaskFilter is a Caddy log field filter that +// masks IP addresses. +type IPMaskFilter struct { + IPv4CIDR int `json:"ipv4_cidr,omitempty"` + IPv6CIDR int `json:"ipv6_cidr,omitempty"` +} + +// CaddyModule returns the Caddy module information. +func (IPMaskFilter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + Name: "caddy.logging.encoders.filter.ip_mask", + New: func() caddy.Module { return new(IPMaskFilter) }, + } +} + +// Filter filters the input field. +func (m IPMaskFilter) Filter(in zapcore.Field) zapcore.Field { + host, port, err := net.SplitHostPort(in.String) + if err != nil { + host = in.String // assume whole thing was IP address + } + ipAddr := net.ParseIP(host) + if ipAddr == nil { + return in + } + bitLen := 32 + cidrPrefix := m.IPv4CIDR + if ipAddr.To16() != nil { + bitLen = 128 + cidrPrefix = m.IPv6CIDR + } + mask := net.CIDRMask(cidrPrefix, bitLen) + masked := ipAddr.Mask(mask) + if port == "" { + in.String = masked.String() + } else { + in.String = net.JoinHostPort(masked.String(), port) + } + return in +} diff --git a/modules/logging/nopencoder.go b/modules/logging/nopencoder.go new file mode 100644 index 0000000..fc3d70d --- /dev/null +++ b/modules/logging/nopencoder.go @@ -0,0 +1,114 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logging + +import ( + "time" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/zapcore" +) + +// nopEncoder is a zapcore.Encoder that does nothing. +type nopEncoder struct{} + +// AddArray is part of the zapcore.ObjectEncoder interface. +// Array elements do not get filtered. +func (nopEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error { return nil } + +// AddObject is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error { return nil } + +// AddBinary is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddBinary(key string, value []byte) {} + +// AddByteString is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddByteString(key string, value []byte) {} + +// AddBool is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddBool(key string, value bool) {} + +// AddComplex128 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddComplex128(key string, value complex128) {} + +// AddComplex64 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddComplex64(key string, value complex64) {} + +// AddDuration is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddDuration(key string, value time.Duration) {} + +// AddFloat64 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddFloat64(key string, value float64) {} + +// AddFloat32 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddFloat32(key string, value float32) {} + +// AddInt is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddInt(key string, value int) {} + +// AddInt64 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddInt64(key string, value int64) {} + +// AddInt32 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddInt32(key string, value int32) {} + +// AddInt16 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddInt16(key string, value int16) {} + +// AddInt8 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddInt8(key string, value int8) {} + +// AddString is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddString(key, value string) {} + +// AddTime is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddTime(key string, value time.Time) {} + +// AddUint is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddUint(key string, value uint) {} + +// AddUint64 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddUint64(key string, value uint64) {} + +// AddUint32 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddUint32(key string, value uint32) {} + +// AddUint16 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddUint16(key string, value uint16) {} + +// AddUint8 is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddUint8(key string, value uint8) {} + +// AddUintptr is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddUintptr(key string, value uintptr) {} + +// AddReflected is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) AddReflected(key string, value interface{}) error { return nil } + +// OpenNamespace is part of the zapcore.ObjectEncoder interface. +func (nopEncoder) OpenNamespace(key string) {} + +// Clone is part of the zapcore.ObjectEncoder interface. +// We don't use it as of Oct 2019 (v2 beta 7), I'm not +// really sure what it'd be useful for in our case. +func (ne nopEncoder) Clone() zapcore.Encoder { return ne } + +// EncodeEntry partially implements the zapcore.Encoder interface. +func (nopEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { + return bufferpool.Get(), nil +} + +// Interface guard +var _ zapcore.Encoder = (*nopEncoder)(nil) -- cgit v1.2.3