aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorFrancis Lavoie <[email protected]>2024-01-24 23:00:22 -0500
committerGitHub <[email protected]>2024-01-25 04:00:22 +0000
commitb9c40e711115d35c2809313aeec42ab10fcd6914 (patch)
tree4c32d099733d2ec273589d85e2d4b21c2cd4e89c
parentf5344f8caddee53cebe249712ba7803e22abf9f1 (diff)
downloadcaddy-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.txt52
-rw-r--r--logging.go48
-rw-r--r--modules/logging/filterencoder.go66
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)
)