@marcel91200 / caddy Mirror · read-only / commits / b9c40e71
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 12, 18:15 UTC

logging: Automatic `wrap` default for `filter` encoder (#5980)

Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>

Francis Lavoie committed Jan 24, 2024 at 23:00 UTC b9c40e711115d35c2809313aeec42ab10fcd6914
3 files changed +139 -27
caddytest/integration/caddyfile_adapt/log_filter_no_wrap.txt new
+52
@@ -0,0 +1,52 @@
1 +:80
2 +
3 +log {
4 + output stdout
5 + format filter {
6 + fields {
7 + request>headers>Server delete
8 + }
9 + }
10 +}
11 +----------
12 +{
13 + "logging": {
14 + "logs": {
15 + "default": {
16 + "exclude": [
17 + "http.log.access.log0"
18 + ]
19 + },
20 + "log0": {
21 + "writer": {
22 + "output": "stdout"
23 + },
24 + "encoder": {
25 + "fields": {
26 + "request\u003eheaders\u003eServer": {
27 + "filter": "delete"
28 + }
29 + },
30 + "format": "filter"
31 + },
32 + "include": [
33 + "http.log.access.log0"
34 + ]
35 + }
36 + }
37 + },
38 + "apps": {
39 + "http": {
40 + "servers": {
41 + "srv0": {
42 + "listen": [
43 + ":80"
44 + ],
45 + "logs": {
46 + "default_logger_name": "log0"
47 + }
48 + }
49 + }
50 + }
51 + }
52 +}
logging.go
+34 -14
@@ -265,6 +265,17 @@ type WriterOpener interface {
265 OpenWriter() (io.WriteCloser, error)
266 }
267
268 +// IsWriterStandardStream returns true if the input is a
269 +// writer-opener to a standard stream (stdout, stderr).
270 +func IsWriterStandardStream(wo WriterOpener) bool {
271 + switch wo.(type) {
272 + case StdoutWriter, StderrWriter,
273 + *StdoutWriter, *StderrWriter:
274 + return true
275 + }
276 + return false
277 +}
278 +
279 type writerDestructor struct {
280 io.WriteCloser
281 }
@@ -341,16 +352,18 @@ func (cl *BaseLog) provisionCommon(ctx Context, logging *Logging) error {
352 return fmt.Errorf("loading log encoder module: %v", err)
353 }
354 cl.encoder = mod.(zapcore.Encoder)
355 +
356 + // if the encoder module needs the writer to determine
357 + // the correct default to use for a nested encoder, we
358 + // pass it down as a secondary provisioning step
359 + if cfd, ok := mod.(ConfiguresFormatterDefault); ok {
360 + if err := cfd.ConfigureDefaultFormat(cl.writerOpener); err != nil {
361 + return fmt.Errorf("configuring default format for encoder module: %v", err)
362 + }
363 + }
364 }
365 if cl.encoder == nil {
346 - // only allow colorized output if this log is going to stdout or stderr
347 - var colorize bool
348 - switch cl.writerOpener.(type) {
349 - case StdoutWriter, StderrWriter,
350 - *StdoutWriter, *StderrWriter:
351 - colorize = true
352 - }
353 - cl.encoder = newDefaultProductionLogEncoder(colorize)
366 + cl.encoder = newDefaultProductionLogEncoder(cl.writerOpener)
367 }
368 cl.buildCore()
369 return nil
@@ -680,7 +693,7 @@ func newDefaultProductionLog() (*defaultCustomLog, error) {
693 if err != nil {
694 return nil, err
695 }
683 - cl.encoder = newDefaultProductionLogEncoder(true)
696 + cl.encoder = newDefaultProductionLogEncoder(cl.writerOpener)
697 cl.levelEnabler = zapcore.InfoLevel
698
699 cl.buildCore()
@@ -697,16 +710,14 @@ func newDefaultProductionLog() (*defaultCustomLog, error) {
710 }, nil
711 }
712
700 -func newDefaultProductionLogEncoder(colorize bool) zapcore.Encoder {
713 +func newDefaultProductionLogEncoder(wo WriterOpener) zapcore.Encoder {
714 encCfg := zap.NewProductionEncoderConfig()
702 - if term.IsTerminal(int(os.Stdout.Fd())) {
715 + if IsWriterStandardStream(wo) && term.IsTerminal(int(os.Stderr.Fd())) {
716 // if interactive terminal, make output more human-readable by default
717 encCfg.EncodeTime = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) {
718 encoder.AppendString(ts.UTC().Format("2006/01/02 15:04:05.000"))
719 }
707 - if colorize {
708 - encCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
709 - }
720 + encCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
721 return zapcore.NewConsoleEncoder(encCfg)
722 }
723 return zapcore.NewJSONEncoder(encCfg)
@@ -753,6 +764,15 @@ var (
764
765 var writers = NewUsagePool()
766
767 +// ConfiguresFormatterDefault is an optional interface that
768 +// encoder modules can implement to configure the default
769 +// format of their encoder. This is useful for encoders
770 +// which nest an encoder, that needs to know the writer
771 +// in order to determine the correct default.
772 +type ConfiguresFormatterDefault interface {
773 + ConfigureDefaultFormat(WriterOpener) error
774 +}
775 +
776 const DefaultLoggerName = "default"
777
778 // Interface guards
modules/logging/filterencoder.go
+53 -13
@@ -17,11 +17,13 @@ package logging
17 import (
18 "encoding/json"
19 "fmt"
20 + "os"
21 "time"
22
23 "go.uber.org/zap"
24 "go.uber.org/zap/buffer"
25 "go.uber.org/zap/zapcore"
26 + "golang.org/x/term"
27
28 "github.com/caddyserver/caddy/v2"
29 "github.com/caddyserver/caddy/v2/caddyconfig"
@@ -36,8 +38,10 @@ func init() {
38 // log entries before they are actually encoded by
39 // an underlying encoder.
40 type FilterEncoder struct {
39 - // The underlying encoder that actually
40 - // encodes the log entries. Required.
41 + // The underlying encoder that actually encodes the
42 + // log entries. If not specified, defaults to "json",
43 + // unless the output is a terminal, in which case
44 + // it defaults to "console".
45 WrappedRaw json.RawMessage `json:"wrap,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
46
47 // A map of field names to their filters. Note that this
@@ -59,6 +63,9 @@ type FilterEncoder struct {
63
64 // used to keep keys unique across nested objects
65 keyPrefix string
66 +
67 + wrappedIsDefault bool
68 + ctx caddy.Context
69 }
70
71 // CaddyModule returns the Caddy module information.
@@ -71,16 +78,25 @@ func (FilterEncoder) CaddyModule() caddy.ModuleInfo {
78
79 // Provision sets up the encoder.
80 func (fe *FilterEncoder) Provision(ctx caddy.Context) error {
74 - if fe.WrappedRaw == nil {
75 - return fmt.Errorf("missing \"wrap\" (must specify an underlying encoder)")
76 - }
81 + fe.ctx = ctx
82
78 - // set up wrapped encoder (required)
79 - val, err := ctx.LoadModule(fe, "WrappedRaw")
80 - if err != nil {
81 - return fmt.Errorf("loading fallback encoder module: %v", err)
83 + if fe.WrappedRaw == nil {
84 + // if wrap is not specified, default to JSON
85 + fe.wrapped = &JSONEncoder{}
86 + if p, ok := fe.wrapped.(caddy.Provisioner); ok {
87 + if err := p.Provision(ctx); err != nil {
88 + return fmt.Errorf("provisioning fallback encoder module: %v", err)
89 + }
90 + }
91 + fe.wrappedIsDefault = true
92 + } else {
93 + // set up wrapped encoder
94 + val, err := ctx.LoadModule(fe, "WrappedRaw")
95 + if err != nil {
96 + return fmt.Errorf("loading fallback encoder module: %v", err)
97 + }
98 + fe.wrapped = val.(zapcore.Encoder)
99 }
83 - fe.wrapped = val.(zapcore.Encoder)
100
101 // set up each field filter
102 if fe.Fields == nil {
@@ -97,6 +113,29 @@ func (fe *FilterEncoder) Provision(ctx caddy.Context) error {
113 return nil
114 }
115
116 +// ConfigureDefaultFormat will set the default format to "console"
117 +// if the writer is a terminal. If already configured as a filter
118 +// encoder, it passes through the writer so a deeply nested filter
119 +// encoder can configure its own default format.
120 +func (fe *FilterEncoder) ConfigureDefaultFormat(wo caddy.WriterOpener) error {
121 + if !fe.wrappedIsDefault {
122 + if cfd, ok := fe.wrapped.(caddy.ConfiguresFormatterDefault); ok {
123 + return cfd.ConfigureDefaultFormat(wo)
124 + }
125 + return nil
126 + }
127 +
128 + if caddy.IsWriterStandardStream(wo) && term.IsTerminal(int(os.Stderr.Fd())) {
129 + fe.wrapped = &ConsoleEncoder{}
130 + if p, ok := fe.wrapped.(caddy.Provisioner); ok {
131 + if err := p.Provision(fe.ctx); err != nil {
132 + return fmt.Errorf("provisioning fallback encoder module: %v", err)
133 + }
134 + }
135 + }
136 + return nil
137 +}
138 +
139 // UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
140 //
141 // filter {
@@ -390,7 +429,8 @@ func (mom logObjectMarshalerWrapper) MarshalLogObject(_ zapcore.ObjectEncoder) e
429
430 // Interface guards
431 var (
393 - _ zapcore.Encoder = (*FilterEncoder)(nil)
394 - _ zapcore.ObjectMarshaler = (*logObjectMarshalerWrapper)(nil)
395 - _ caddyfile.Unmarshaler = (*FilterEncoder)(nil)
432 + _ zapcore.Encoder = (*FilterEncoder)(nil)
433 + _ zapcore.ObjectMarshaler = (*logObjectMarshalerWrapper)(nil)
434 + _ caddyfile.Unmarshaler = (*FilterEncoder)(nil)
435 + _ caddy.ConfiguresFormatterDefault = (*FilterEncoder)(nil)
436 )