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)
|
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 {
|
type writerDestructor struct {
|
||||||
io.WriteCloser
|
io.WriteCloser
|
||||||
}
|
}
|
||||||
|
@ -341,16 +352,18 @@ func (cl *BaseLog) provisionCommon(ctx Context, logging *Logging) error {
|
||||||
return fmt.Errorf("loading log encoder module: %v", err)
|
return fmt.Errorf("loading log encoder module: %v", err)
|
||||||
}
|
}
|
||||||
cl.encoder = mod.(zapcore.Encoder)
|
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 {
|
if cl.encoder == nil {
|
||||||
// only allow colorized output if this log is going to stdout or stderr
|
cl.encoder = newDefaultProductionLogEncoder(cl.writerOpener)
|
||||||
var colorize bool
|
|
||||||
switch cl.writerOpener.(type) {
|
|
||||||
case StdoutWriter, StderrWriter,
|
|
||||||
*StdoutWriter, *StderrWriter:
|
|
||||||
colorize = true
|
|
||||||
}
|
|
||||||
cl.encoder = newDefaultProductionLogEncoder(colorize)
|
|
||||||
}
|
}
|
||||||
cl.buildCore()
|
cl.buildCore()
|
||||||
return nil
|
return nil
|
||||||
|
@ -680,7 +693,7 @@ func newDefaultProductionLog() (*defaultCustomLog, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cl.encoder = newDefaultProductionLogEncoder(true)
|
cl.encoder = newDefaultProductionLogEncoder(cl.writerOpener)
|
||||||
cl.levelEnabler = zapcore.InfoLevel
|
cl.levelEnabler = zapcore.InfoLevel
|
||||||
|
|
||||||
cl.buildCore()
|
cl.buildCore()
|
||||||
|
@ -697,16 +710,14 @@ func newDefaultProductionLog() (*defaultCustomLog, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDefaultProductionLogEncoder(colorize bool) zapcore.Encoder {
|
func newDefaultProductionLogEncoder(wo WriterOpener) zapcore.Encoder {
|
||||||
encCfg := zap.NewProductionEncoderConfig()
|
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
|
// if interactive terminal, make output more human-readable by default
|
||||||
encCfg.EncodeTime = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) {
|
encCfg.EncodeTime = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) {
|
||||||
encoder.AppendString(ts.UTC().Format("2006/01/02 15:04:05.000"))
|
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.NewConsoleEncoder(encCfg)
|
||||||
}
|
}
|
||||||
return zapcore.NewJSONEncoder(encCfg)
|
return zapcore.NewJSONEncoder(encCfg)
|
||||||
|
@ -753,6 +764,15 @@ var (
|
||||||
|
|
||||||
var writers = NewUsagePool()
|
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"
|
const DefaultLoggerName = "default"
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
|
|
|
@ -17,11 +17,13 @@ package logging
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/buffer"
|
"go.uber.org/zap/buffer"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
|
"golang.org/x/term"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
|
@ -36,8 +38,10 @@ func init() {
|
||||||
// log entries before they are actually encoded by
|
// log entries before they are actually encoded by
|
||||||
// an underlying encoder.
|
// an underlying encoder.
|
||||||
type FilterEncoder struct {
|
type FilterEncoder struct {
|
||||||
// The underlying encoder that actually
|
// The underlying encoder that actually encodes the
|
||||||
// encodes the log entries. Required.
|
// 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"`
|
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
|
// 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
|
// used to keep keys unique across nested objects
|
||||||
keyPrefix string
|
keyPrefix string
|
||||||
|
|
||||||
|
wrappedIsDefault bool
|
||||||
|
ctx caddy.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaddyModule returns the Caddy module information.
|
// CaddyModule returns the Caddy module information.
|
||||||
|
@ -71,16 +78,25 @@ func (FilterEncoder) CaddyModule() caddy.ModuleInfo {
|
||||||
|
|
||||||
// Provision sets up the encoder.
|
// Provision sets up the encoder.
|
||||||
func (fe *FilterEncoder) Provision(ctx caddy.Context) error {
|
func (fe *FilterEncoder) Provision(ctx caddy.Context) error {
|
||||||
if fe.WrappedRaw == nil {
|
fe.ctx = ctx
|
||||||
return fmt.Errorf("missing \"wrap\" (must specify an underlying encoder)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up wrapped encoder (required)
|
if fe.WrappedRaw == nil {
|
||||||
val, err := ctx.LoadModule(fe, "WrappedRaw")
|
// if wrap is not specified, default to JSON
|
||||||
if err != nil {
|
fe.wrapped = &JSONEncoder{}
|
||||||
return fmt.Errorf("loading fallback encoder module: %v", err)
|
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
|
// set up each field filter
|
||||||
if fe.Fields == nil {
|
if fe.Fields == nil {
|
||||||
|
@ -97,6 +113,29 @@ func (fe *FilterEncoder) Provision(ctx caddy.Context) error {
|
||||||
return nil
|
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:
|
// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
|
||||||
//
|
//
|
||||||
// filter {
|
// filter {
|
||||||
|
@ -390,7 +429,8 @@ func (mom logObjectMarshalerWrapper) MarshalLogObject(_ zapcore.ObjectEncoder) e
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ zapcore.Encoder = (*FilterEncoder)(nil)
|
_ zapcore.Encoder = (*FilterEncoder)(nil)
|
||||||
_ zapcore.ObjectMarshaler = (*logObjectMarshalerWrapper)(nil)
|
_ zapcore.ObjectMarshaler = (*logObjectMarshalerWrapper)(nil)
|
||||||
_ caddyfile.Unmarshaler = (*FilterEncoder)(nil)
|
_ caddyfile.Unmarshaler = (*FilterEncoder)(nil)
|
||||||
|
_ caddy.ConfiguresFormatterDefault = (*FilterEncoder)(nil)
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue