diff options
| author | Matt Holt <mholt@users.noreply.github.com> | 2019-10-28 14:39:37 -0600 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-10-28 14:39:37 -0600 | 
| commit | b00dfd3965f400956c5bb5b388e9d54ef98052e5 (patch) | |
| tree | 44517743815327f7ef63405b3a13e54f7f20c885 /modules/logging/encoders.go | |
| parent | 6c533558a3db4b30a6b7a81d19ac180fe2000ca2 (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/encoders.go')
| -rw-r--r-- | modules/logging/encoders.go | 268 | 
1 files changed, 268 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) +) | 
