mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-26 21:53:48 +03:00
logging: Automatic wrap
default for filter
encoder (#5980)
Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
This commit is contained in:
parent
f5344f8cad
commit
b9c40e7111
3 changed files with 139 additions and 27 deletions
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)
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue