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
)