summaryrefslogtreecommitdiff
path: root/modules/logging
diff options
context:
space:
mode:
authorMatt Holt <mholt@users.noreply.github.com>2019-10-28 14:39:37 -0600
committerGitHub <noreply@github.com>2019-10-28 14:39:37 -0600
commitb00dfd3965f400956c5bb5b388e9d54ef98052e5 (patch)
tree44517743815327f7ef63405b3a13e54f7f20c885 /modules/logging
parent6c533558a3db4b30a6b7a81d19ac180fe2000ca2 (diff)
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
Diffstat (limited to 'modules/logging')
-rw-r--r--modules/logging/encoders.go268
-rw-r--r--modules/logging/filewriter.go91
-rw-r--r--modules/logging/filterencoder.go321
-rw-r--r--modules/logging/filters.go94
-rw-r--r--modules/logging/nopencoder.go114
5 files changed, 888 insertions, 0 deletions
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)