diff options
author | Francis Lavoie <[email protected]> | 2024-01-24 23:00:22 -0500 |
---|---|---|
committer | GitHub <[email protected]> | 2024-01-25 04:00:22 +0000 |
commit | b9c40e711115d35c2809313aeec42ab10fcd6914 (patch) | |
tree | 4c32d099733d2ec273589d85e2d4b21c2cd4e89c | |
parent | f5344f8caddee53cebe249712ba7803e22abf9f1 (diff) | |
download | caddy-b9c40e711115d35c2809313aeec42ab10fcd6914.tar.gz caddy-b9c40e711115d35c2809313aeec42ab10fcd6914.zip |
logging: Automatic `wrap` default for `filter` encoder (#5980)
Co-authored-by: Kévin Dunglas <[email protected]>
-rw-r--r-- | caddytest/integration/caddyfile_adapt/log_filter_no_wrap.txt | 52 | ||||
-rw-r--r-- | logging.go | 48 | ||||
-rw-r--r-- | modules/logging/filterencoder.go | 66 |
3 files changed, 139 insertions, 27 deletions
diff --git a/caddytest/integration/caddyfile_adapt/log_filter_no_wrap.txt b/caddytest/integration/caddyfile_adapt/log_filter_no_wrap.txt new file mode 100644 index 000000000..f63a1d925 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/log_filter_no_wrap.txt @@ -0,0 +1,52 @@ +:80 + +log { + output stdout + format filter { + fields { + request>headers>Server delete + } + } +} +---------- +{ + "logging": { + "logs": { + "default": { + "exclude": [ + "http.log.access.log0" + ] + }, + "log0": { + "writer": { + "output": "stdout" + }, + "encoder": { + "fields": { + "request\u003eheaders\u003eServer": { + "filter": "delete" + } + }, + "format": "filter" + }, + "include": [ + "http.log.access.log0" + ] + } + } + }, + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "logs": { + "default_logger_name": "log0" + } + } + } + } + } +} diff --git a/logging.go b/logging.go index 58d5b2d3f..fe229505d 100644 --- a/logging.go +++ b/logging.go @@ -265,6 +265,17 @@ type WriterOpener interface { OpenWriter() (io.WriteCloser, error) } +// IsWriterStandardStream returns true if the input is a +// writer-opener to a standard stream (stdout, stderr). +func IsWriterStandardStream(wo WriterOpener) bool { + switch wo.(type) { + case StdoutWriter, StderrWriter, + *StdoutWriter, *StderrWriter: + return true + } + return false +} + type writerDestructor struct { io.WriteCloser } @@ -341,16 +352,18 @@ func (cl *BaseLog) provisionCommon(ctx Context, logging *Logging) error { return fmt.Errorf("loading log encoder module: %v", err) } cl.encoder = mod.(zapcore.Encoder) + + // if the encoder module needs the writer to determine + // the correct default to use for a nested encoder, we + // pass it down as a secondary provisioning step + if cfd, ok := mod.(ConfiguresFormatterDefault); ok { + if err := cfd.ConfigureDefaultFormat(cl.writerOpener); err != nil { + return fmt.Errorf("configuring default format for encoder module: %v", err) + } + } } if cl.encoder == nil { - // only allow colorized output if this log is going to stdout or stderr - var colorize bool - switch cl.writerOpener.(type) { - case StdoutWriter, StderrWriter, - *StdoutWriter, *StderrWriter: - colorize = true - } - cl.encoder = newDefaultProductionLogEncoder(colorize) + cl.encoder = newDefaultProductionLogEncoder(cl.writerOpener) } cl.buildCore() return nil @@ -680,7 +693,7 @@ func newDefaultProductionLog() (*defaultCustomLog, error) { if err != nil { return nil, err } - cl.encoder = newDefaultProductionLogEncoder(true) + cl.encoder = newDefaultProductionLogEncoder(cl.writerOpener) cl.levelEnabler = zapcore.InfoLevel cl.buildCore() @@ -697,16 +710,14 @@ func newDefaultProductionLog() (*defaultCustomLog, error) { }, nil } -func newDefaultProductionLogEncoder(colorize bool) zapcore.Encoder { +func newDefaultProductionLogEncoder(wo WriterOpener) zapcore.Encoder { encCfg := zap.NewProductionEncoderConfig() - if term.IsTerminal(int(os.Stdout.Fd())) { + if IsWriterStandardStream(wo) && term.IsTerminal(int(os.Stderr.Fd())) { // if interactive terminal, make output more human-readable by default encCfg.EncodeTime = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) { encoder.AppendString(ts.UTC().Format("2006/01/02 15:04:05.000")) } - if colorize { - encCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder - } + encCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder return zapcore.NewConsoleEncoder(encCfg) } return zapcore.NewJSONEncoder(encCfg) @@ -753,6 +764,15 @@ var ( var writers = NewUsagePool() +// ConfiguresFormatterDefault is an optional interface that +// encoder modules can implement to configure the default +// format of their encoder. This is useful for encoders +// which nest an encoder, that needs to know the writer +// in order to determine the correct default. +type ConfiguresFormatterDefault interface { + ConfigureDefaultFormat(WriterOpener) error +} + const DefaultLoggerName = "default" // Interface guards diff --git a/modules/logging/filterencoder.go b/modules/logging/filterencoder.go index 735d7d424..9b1895d79 100644 --- a/modules/logging/filterencoder.go +++ b/modules/logging/filterencoder.go @@ -17,11 +17,13 @@ package logging import ( "encoding/json" "fmt" + "os" "time" "go.uber.org/zap" "go.uber.org/zap/buffer" "go.uber.org/zap/zapcore" + "golang.org/x/term" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" @@ -36,8 +38,10 @@ func init() { // log entries before they are actually encoded by // an underlying encoder. type FilterEncoder struct { - // The underlying encoder that actually - // encodes the log entries. Required. + // The underlying encoder that actually encodes the + // log entries. If not specified, defaults to "json", + // unless the output is a terminal, in which case + // it defaults to "console". WrappedRaw json.RawMessage `json:"wrap,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"` // A map of field names to their filters. Note that this @@ -59,6 +63,9 @@ type FilterEncoder struct { // used to keep keys unique across nested objects keyPrefix string + + wrappedIsDefault bool + ctx caddy.Context } // CaddyModule returns the Caddy module information. @@ -71,16 +78,25 @@ func (FilterEncoder) CaddyModule() caddy.ModuleInfo { // 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)") - } + fe.ctx = ctx - // set up wrapped encoder (required) - val, err := ctx.LoadModule(fe, "WrappedRaw") - if err != nil { - return fmt.Errorf("loading fallback encoder module: %v", err) + if fe.WrappedRaw == nil { + // if wrap is not specified, default to JSON + fe.wrapped = &JSONEncoder{} + if p, ok := fe.wrapped.(caddy.Provisioner); ok { + if err := p.Provision(ctx); err != nil { + return fmt.Errorf("provisioning fallback encoder module: %v", err) + } + } + fe.wrappedIsDefault = true + } else { + // set up wrapped encoder + val, err := ctx.LoadModule(fe, "WrappedRaw") + if err != nil { + return fmt.Errorf("loading fallback encoder module: %v", err) + } + fe.wrapped = val.(zapcore.Encoder) } - fe.wrapped = val.(zapcore.Encoder) // set up each field filter if fe.Fields == nil { @@ -97,6 +113,29 @@ func (fe *FilterEncoder) Provision(ctx caddy.Context) error { return nil } +// ConfigureDefaultFormat will set the default format to "console" +// if the writer is a terminal. If already configured as a filter +// encoder, it passes through the writer so a deeply nested filter +// encoder can configure its own default format. +func (fe *FilterEncoder) ConfigureDefaultFormat(wo caddy.WriterOpener) error { + if !fe.wrappedIsDefault { + if cfd, ok := fe.wrapped.(caddy.ConfiguresFormatterDefault); ok { + return cfd.ConfigureDefaultFormat(wo) + } + return nil + } + + if caddy.IsWriterStandardStream(wo) && term.IsTerminal(int(os.Stderr.Fd())) { + fe.wrapped = &ConsoleEncoder{} + if p, ok := fe.wrapped.(caddy.Provisioner); ok { + if err := p.Provision(fe.ctx); err != nil { + return fmt.Errorf("provisioning fallback encoder module: %v", err) + } + } + } + return nil +} + // UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax: // // filter { @@ -390,7 +429,8 @@ func (mom logObjectMarshalerWrapper) MarshalLogObject(_ zapcore.ObjectEncoder) e // Interface guards var ( - _ zapcore.Encoder = (*FilterEncoder)(nil) - _ zapcore.ObjectMarshaler = (*logObjectMarshalerWrapper)(nil) - _ caddyfile.Unmarshaler = (*FilterEncoder)(nil) + _ zapcore.Encoder = (*FilterEncoder)(nil) + _ zapcore.ObjectMarshaler = (*logObjectMarshalerWrapper)(nil) + _ caddyfile.Unmarshaler = (*FilterEncoder)(nil) + _ caddy.ConfiguresFormatterDefault = (*FilterEncoder)(nil) ) |