mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 10:12:29 +08:00 
			
		
		
		
	logging: Automatic wrap default for filter encoder (#5980)
				
					
				
			Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
This commit is contained in:
		
							
								
								
									
										52
									
								
								caddytest/integration/caddyfile_adapt/log_filter_no_wrap.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								caddytest/integration/caddyfile_adapt/log_filter_no_wrap.txt
									
									
									
									
									
										Normal file
									
								
							@ -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"
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								logging.go
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								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
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user