mirror of
https://github.com/caddyserver/caddy.git
synced 2025-10-19 07:43:17 +00:00
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
This commit is contained in:
parent
6c533558a3
commit
b00dfd3965
34 changed files with 2234 additions and 201 deletions
268
modules/logging/encoders.go
Normal file
268
modules/logging/encoders.go
Normal file
|
@ -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)
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue