@marcel91200 / caddy Mirror · read-only / commits / 5d97522d
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 14, 05:15 UTC

v2: 'log' directive for Caddyfile, and debug mode (#3052)

* httpcaddyfile: Begin implementing log directive, and debug mode For now, debug mode just sets the log level for all logs to DEBUG (unless a level is specified explicitly). * httpcaddyfile: Finish 'log' directive Also rename StringEncoder -> SingleFieldEncoder * Fix minor bug in replacer (when vals are empty)

Matt Holt committed Feb 25, 2020 at 22:00 UTC 5d97522d18da39cd3f9dd309774a5ad2c51f4c51
9 files changed +428 -21
caddyconfig/httpcaddyfile/builtins.go
+110
@@ -24,8 +24,10 @@ import (
24
25 "github.com/caddyserver/caddy/v2"
26 "github.com/caddyserver/caddy/v2/caddyconfig"
27 + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
28 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
29 "github.com/caddyserver/caddy/v2/modules/caddytls"
30 + "go.uber.org/zap/zapcore"
31 )
32
33 func init() {
@@ -37,6 +39,7 @@ func init() {
39 RegisterHandlerDirective("route", parseRoute)
40 RegisterHandlerDirective("handle", parseSegmentAsSubroute)
41 RegisterDirective("handle_errors", parseHandleErrors)
42 + RegisterDirective("log", parseLog)
43 }
44
45 // parseBind parses the bind directive. Syntax:
@@ -426,9 +429,116 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
429 }, nil
430 }
431
432 +// parseLog parses the log directive. Syntax:
433 +//
434 +// log {
435 +// output <writer_module> ...
436 +// format <encoder_module> ...
437 +// level <level>
438 +// }
439 +//
440 +func parseLog(h Helper) ([]ConfigValue, error) {
441 + var configValues []ConfigValue
442 + for h.Next() {
443 + cl := new(caddy.CustomLog)
444 +
445 + for h.NextBlock(0) {
446 + switch h.Val() {
447 + case "output":
448 + if !h.NextArg() {
449 + return nil, h.ArgErr()
450 + }
451 + moduleName := h.Val()
452 +
453 + // can't use the usual caddyfile.Unmarshaler flow with the
454 + // standard writers because they are in the caddy package
455 + // (because they are the default) and implementing that
456 + // interface there would unfortunately create circular import
457 + var wo caddy.WriterOpener
458 + switch moduleName {
459 + case "stdout":
460 + wo = caddy.StdoutWriter{}
461 + case "stderr":
462 + wo = caddy.StderrWriter{}
463 + case "discard":
464 + wo = caddy.DiscardWriter{}
465 + default:
466 + mod, err := caddy.GetModule("caddy.logging.writers." + moduleName)
467 + if err != nil {
468 + return nil, h.Errf("getting log writer module named '%s': %v", moduleName, err)
469 + }
470 + unm, ok := mod.New().(caddyfile.Unmarshaler)
471 + if !ok {
472 + return nil, h.Errf("log writer module '%s' is not a Caddyfile unmarshaler", mod)
473 + }
474 + err = unm.UnmarshalCaddyfile(h.NewFromNextSegment())
475 + if err != nil {
476 + return nil, err
477 + }
478 + wo, ok = unm.(caddy.WriterOpener)
479 + if !ok {
480 + return nil, h.Errf("module %s is not a WriterOpener", mod)
481 + }
482 + }
483 + cl.WriterRaw = caddyconfig.JSONModuleObject(wo, "output", moduleName, h.warnings)
484 +
485 + case "format":
486 + if !h.NextArg() {
487 + return nil, h.ArgErr()
488 + }
489 + moduleName := h.Val()
490 + mod, err := caddy.GetModule("caddy.logging.encoders." + moduleName)
491 + if err != nil {
492 + return nil, h.Errf("getting log encoder module named '%s': %v", moduleName, err)
493 + }
494 + unm, ok := mod.New().(caddyfile.Unmarshaler)
495 + if !ok {
496 + return nil, h.Errf("log encoder module '%s' is not a Caddyfile unmarshaler", mod)
497 + }
498 + err = unm.UnmarshalCaddyfile(h.NewFromNextSegment())
499 + if err != nil {
500 + return nil, err
501 + }
502 + enc, ok := unm.(zapcore.Encoder)
503 + if !ok {
504 + return nil, h.Errf("module %s is not a zapcore.Encoder", mod)
505 + }
506 + cl.EncoderRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, h.warnings)
507 +
508 + case "level":
509 + if !h.NextArg() {
510 + return nil, h.ArgErr()
511 + }
512 + cl.Level = h.Val()
513 + if h.NextArg() {
514 + return nil, h.ArgErr()
515 + }
516 +
517 + default:
518 + return nil, h.Errf("unrecognized subdirective: %s", h.Val())
519 + }
520 + }
521 +
522 + var val namedCustomLog
523 + if !reflect.DeepEqual(cl, new(caddy.CustomLog)) {
524 + cl.Include = []string{"http.log.access"}
525 + val.name = fmt.Sprintf("log%d", logCounter)
526 + val.log = cl
527 + logCounter++
528 + }
529 + configValues = append(configValues, ConfigValue{
530 + Class: "custom_log",
531 + Value: val,
532 + })
533 + }
534 + return configValues, nil
535 +}
536 +
537 // tlsCertTags maps certificate filenames to their tag.
538 // This is used to remember which tag is used for each
539 // certificate files, since we need to avoid loading
540 // the same certificate files more than once, overwriting
541 // previous tags
542 var tlsCertTags = make(map[string]string)
543 +
544 +var logCounter int
caddyconfig/httpcaddyfile/httptype.go
+67
@@ -282,6 +282,35 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
282 }
283 }
284
285 + // extract any custom logs, and enforce configured levels
286 + var customLogs []namedCustomLog
287 + var hasDefaultLog bool
288 + for _, sb := range serverBlocks {
289 + for _, clVal := range sb.pile["custom_log"] {
290 + ncl := clVal.Value.(namedCustomLog)
291 + if ncl.name == "" {
292 + continue
293 + }
294 + if ncl.name == "default" {
295 + hasDefaultLog = true
296 + }
297 + if _, ok := options["debug"]; ok && ncl.log.Level == "" {
298 + ncl.log.Level = "DEBUG"
299 + }
300 + customLogs = append(customLogs, ncl)
301 + }
302 + }
303 + if !hasDefaultLog {
304 + // if the default log was not customized, ensure we
305 + // configure it with any applicable options
306 + if _, ok := options["debug"]; ok {
307 + customLogs = append(customLogs, namedCustomLog{
308 + name: "default",
309 + log: &caddy.CustomLog{Level: "DEBUG"},
310 + })
311 + }
312 + }
313 +
314 // annnd the top-level config, then we're done!
315 cfg := &caddy.Config{AppsRaw: make(caddy.ModuleMap)}
316 if !reflect.DeepEqual(httpApp, caddyhttp.App{}) {
@@ -299,6 +328,18 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
328 if adminConfig, ok := options["admin"].(string); ok && adminConfig != "" {
329 cfg.Admin = &caddy.AdminConfig{Listen: adminConfig}
330 }
331 + if len(customLogs) > 0 {
332 + if cfg.Logging == nil {
333 + cfg.Logging = &caddy.Logging{
334 + Logs: make(map[string]*caddy.CustomLog),
335 + }
336 + }
337 + for _, ncl := range customLogs {
338 + if ncl.name != "" {
339 + cfg.Logging.Logs[ncl.name] = ncl.log
340 + }
341 + }
342 + }
343
344 return cfg, warnings, nil
345 }
@@ -335,6 +376,8 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
376 val, err = parseOptEmail(disp)
377 case "admin":
378 val, err = parseOptAdmin(disp)
379 + case "debug":
380 + options["debug"] = true
381 default:
382 return nil, fmt.Errorf("unrecognized parameter name: %s", dir)
383 }
@@ -506,6 +549,25 @@ func (st *ServerType) serversFromPairings(
549 srv.Errors.Routes = appendSubrouteToRouteList(srv.Errors.Routes, sr, matcherSetsEnc, p, warnings)
550 }
551 }
552 +
553 + // add log associations
554 + for _, cval := range sblock.pile["custom_log"] {
555 + ncl := cval.Value.(namedCustomLog)
556 + if srv.Logs == nil {
557 + srv.Logs = &caddyhttp.ServerLogConfig{
558 + LoggerNames: make(map[string]string),
559 + }
560 + }
561 + hosts, err := st.hostsFromServerBlockKeys(sblock.block)
562 + if err != nil {
563 + return nil, err
564 + }
565 + for _, h := range hosts {
566 + if ncl.name != "" {
567 + srv.Logs.LoggerNames[h] = ncl.name
568 + }
569 + }
570 + }
571 }
572
573 // a catch-all TLS conn policy is necessary to ensure TLS can
@@ -940,6 +1002,11 @@ type matcherSetAndTokens struct {
1002 tokens []caddyfile.Token
1003 }
1004
1005 +type namedCustomLog struct {
1006 + name string
1007 + log *caddy.CustomLog
1008 +}
1009 +
1010 // sbAddrAssocation is a mapping from a list of
1011 // addresses to a list of server blocks that are
1012 // served on those addresses.
go.mod
+1 -1
@@ -7,7 +7,7 @@ require (
7 github.com/alecthomas/chroma v0.7.0
8 github.com/andybalholm/brotli v0.0.0-20190821151343-b60f0d972eeb
9 github.com/cenkalti/backoff/v3 v3.1.1 // indirect
10 - github.com/dustin/go-humanize v1.0.0
10 + github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac
11 github.com/go-acme/lego/v3 v3.3.0
12 github.com/golang/groupcache v0.0.0-20191002201903-404acd9df4cc
13 github.com/ilibs/json5 v1.0.1
go.sum
+2
@@ -94,6 +94,8 @@ github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s9
94 github.com/dnsimple/dnsimple-go v0.30.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
95 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
96 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
97 +github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac h1:opbrjaN/L8gg6Xh5D04Tem+8xVcz6ajZlGCs49mQgyg=
98 +github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
99 github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
100 github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
101 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
modules/logging/encoders.go
+143 -12
@@ -21,6 +21,7 @@ import (
21 "time"
22
23 "github.com/caddyserver/caddy/v2"
24 + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
25 zaplogfmt "github.com/jsternberg/zap-logfmt"
26 "go.uber.org/zap"
27 "go.uber.org/zap/buffer"
@@ -31,7 +32,7 @@ func init() {
32 caddy.RegisterModule(ConsoleEncoder{})
33 caddy.RegisterModule(JSONEncoder{})
34 caddy.RegisterModule(LogfmtEncoder{})
34 - caddy.RegisterModule(StringEncoder{})
35 + caddy.RegisterModule(SingleFieldEncoder{})
36 }
37
38 // ConsoleEncoder encodes log entries that are mostly human-readable.
@@ -54,6 +55,27 @@ func (ce *ConsoleEncoder) Provision(_ caddy.Context) error {
55 return nil
56 }
57
58 +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
59 +//
60 +// console {
61 +// <common encoder config subdirectives...>
62 +// }
63 +//
64 +// See the godoc on the LogEncoderConfig type for the syntax of
65 +// subdirectives that are common to most/all encoders.
66 +func (ce *ConsoleEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
67 + for d.Next() {
68 + if d.NextArg() {
69 + return d.ArgErr()
70 + }
71 + err := ce.LogEncoderConfig.UnmarshalCaddyfile(d)
72 + if err != nil {
73 + return err
74 + }
75 + }
76 + return nil
77 +}
78 +
79 // JSONEncoder encodes entries as JSON.
80 type JSONEncoder struct {
81 zapcore.Encoder `json:"-"`
@@ -74,6 +96,27 @@ func (je *JSONEncoder) Provision(_ caddy.Context) error {
96 return nil
97 }
98
99 +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
100 +//
101 +// json {
102 +// <common encoder config subdirectives...>
103 +// }
104 +//
105 +// See the godoc on the LogEncoderConfig type for the syntax of
106 +// subdirectives that are common to most/all encoders.
107 +func (je *JSONEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
108 + for d.Next() {
109 + if d.NextArg() {
110 + return d.ArgErr()
111 + }
112 + err := je.LogEncoderConfig.UnmarshalCaddyfile(d)
113 + if err != nil {
114 + return err
115 + }
116 + }
117 + return nil
118 +}
119 +
120 // LogfmtEncoder encodes log entries as logfmt:
121 // https://www.brandur.org/logfmt
122 type LogfmtEncoder struct {
@@ -95,26 +138,47 @@ func (lfe *LogfmtEncoder) Provision(_ caddy.Context) error {
138 return nil
139 }
140
98 -// StringEncoder writes a log entry that consists entirely
141 +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
142 +//
143 +// logfmt {
144 +// <common encoder config subdirectives...>
145 +// }
146 +//
147 +// See the godoc on the LogEncoderConfig type for the syntax of
148 +// subdirectives that are common to most/all encoders.
149 +func (lfe *LogfmtEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
150 + for d.Next() {
151 + if d.NextArg() {
152 + return d.ArgErr()
153 + }
154 + err := lfe.LogEncoderConfig.UnmarshalCaddyfile(d)
155 + if err != nil {
156 + return err
157 + }
158 + }
159 + return nil
160 +}
161 +
162 +// SingleFieldEncoder writes a log entry that consists entirely
163 // of a single string field in the log entry. This is useful
164 // for custom, self-encoded log entries that consist of a
165 // single field in the structured log entry.
102 -type StringEncoder struct {
166 +type SingleFieldEncoder struct {
167 zapcore.Encoder `json:"-"`
168 FieldName string `json:"field,omitempty"`
169 FallbackRaw json.RawMessage `json:"fallback,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
170 }
171
172 // CaddyModule returns the Caddy module information.
109 -func (StringEncoder) CaddyModule() caddy.ModuleInfo {
173 +func (SingleFieldEncoder) CaddyModule() caddy.ModuleInfo {
174 return caddy.ModuleInfo{
111 - ID: "caddy.logging.encoders.string",
112 - New: func() caddy.Module { return new(StringEncoder) },
175 + ID: "caddy.logging.encoders.single_field",
176 + New: func() caddy.Module { return new(SingleFieldEncoder) },
177 }
178 }
179
180 // Provision sets up the encoder.
117 -func (se *StringEncoder) Provision(ctx caddy.Context) error {
181 +func (se *SingleFieldEncoder) Provision(ctx caddy.Context) error {
182 if se.FallbackRaw != nil {
183 val, err := ctx.LoadModule(se, "FallbackRaw")
184 if err != nil {
@@ -132,16 +196,16 @@ func (se *StringEncoder) Provision(ctx caddy.Context) error {
196 // necessary because we implement our own EncodeEntry,
197 // and if we simply let the embedded encoder's Clone
198 // be promoted, it would return a clone of that, and
135 -// we'd lose our StringEncoder's EncodeEntry.
136 -func (se StringEncoder) Clone() zapcore.Encoder {
137 - return StringEncoder{
199 +// we'd lose our SingleFieldEncoder's EncodeEntry.
200 +func (se SingleFieldEncoder) Clone() zapcore.Encoder {
201 + return SingleFieldEncoder{
202 Encoder: se.Encoder.Clone(),
203 FieldName: se.FieldName,
204 }
205 }
206
207 // EncodeEntry partially implements the zapcore.Encoder interface.
144 -func (se StringEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
208 +func (se SingleFieldEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
209 for _, f := range fields {
210 if f.Key == se.FieldName {
211 buf := bufferpool.Get()
@@ -158,6 +222,21 @@ func (se StringEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (
222 return se.Encoder.EncodeEntry(ent, fields)
223 }
224
225 +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
226 +//
227 +// single_field <field_name>
228 +//
229 +func (se *SingleFieldEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
230 + for d.Next() {
231 + var fieldName string
232 + if !d.AllArgs(&fieldName) {
233 + return d.ArgErr()
234 + }
235 + se.FieldName = d.Val()
236 + }
237 + return nil
238 +}
239 +
240 // LogEncoderConfig holds configuration common to most encoders.
241 type LogEncoderConfig struct {
242 MessageKey *string `json:"message_key,omitempty"`
@@ -172,6 +251,53 @@ type LogEncoderConfig struct {
251 LevelFormat string `json:"level_format,omitempty"`
252 }
253
254 +// UnmarshalCaddyfile populates the struct from Caddyfile tokens. Syntax:
255 +//
256 +// {
257 +// message_key <key>
258 +// level_key <key>
259 +// time_key <key>
260 +// name_key <key>
261 +// caller_key <key>
262 +// stacktrace_key <key>
263 +// line_ending <char>
264 +// time_format <format>
265 +// level_format <format>
266 +// }
267 +//
268 +func (lec *LogEncoderConfig) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
269 + for nesting := d.Nesting(); d.NextBlock(nesting); {
270 + subdir := d.Val()
271 + var arg string
272 + if !d.AllArgs(&arg) {
273 + return d.ArgErr()
274 + }
275 + switch subdir {
276 + case "message_key":
277 + lec.MessageKey = &arg
278 + case "level_key":
279 + lec.LevelKey = &arg
280 + case "time_key":
281 + lec.TimeKey = &arg
282 + case "name_key":
283 + lec.NameKey = &arg
284 + case "caller_key":
285 + lec.CallerKey = &arg
286 + case "stacktrace_key":
287 + lec.StacktraceKey = &arg
288 + case "line_ending":
289 + lec.LineEnding = &arg
290 + case "time_format":
291 + lec.TimeFormat = arg
292 + case "level_format":
293 + lec.LevelFormat = arg
294 + default:
295 + return d.Errf("unrecognized subdirective %s", subdir)
296 + }
297 + }
298 + return nil
299 +}
300 +
301 // ZapcoreEncoderConfig returns the equivalent zapcore.EncoderConfig.
302 // If lec is nil, zap.NewProductionEncoderConfig() is returned.
303 func (lec *LogEncoderConfig) ZapcoreEncoderConfig() zapcore.EncoderConfig {
@@ -263,5 +389,10 @@ var (
389 _ zapcore.Encoder = (*ConsoleEncoder)(nil)
390 _ zapcore.Encoder = (*JSONEncoder)(nil)
391 _ zapcore.Encoder = (*LogfmtEncoder)(nil)
266 - _ zapcore.Encoder = (*StringEncoder)(nil)
392 + _ zapcore.Encoder = (*SingleFieldEncoder)(nil)
393 +
394 + _ caddyfile.Unmarshaler = (*ConsoleEncoder)(nil)
395 + _ caddyfile.Unmarshaler = (*JSONEncoder)(nil)
396 + _ caddyfile.Unmarshaler = (*LogfmtEncoder)(nil)
397 + _ caddyfile.Unmarshaler = (*SingleFieldEncoder)(nil)
398 )
modules/logging/filewriter.go
+75 -1
@@ -19,8 +19,12 @@ import (
19 "io"
20 "os"
21 "path/filepath"
22 + "strconv"
23 + "time"
24
25 "github.com/caddyserver/caddy/v2"
26 + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
27 + "github.com/dustin/go-humanize"
28 "gopkg.in/natefinch/lumberjack.v2"
29 )
30
@@ -125,7 +129,77 @@ func (fw FileWriter) OpenWriter() (io.WriteCloser, error) {
129 return os.OpenFile(fw.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
130 }
131
132 +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
133 +//
134 +// file <filename> {
135 +// roll_disabled
136 +// roll_size <size>
137 +// roll_keep <num>
138 +// roll_keep_for <days>
139 +// }
140 +//
141 +// The roll_size value will be rounded down to number of megabytes (MiB).
142 +// The roll_keep_for duration will be rounded down to number of days.
143 +func (fw *FileWriter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
144 + for d.Next() {
145 + if !d.NextArg() {
146 + return d.ArgErr()
147 + }
148 + fw.Filename = d.Val()
149 + if d.NextArg() {
150 + return d.ArgErr()
151 + }
152 +
153 + for d.NextBlock(0) {
154 + switch d.Val() {
155 + case "roll_disabled":
156 + var f bool
157 + fw.Roll = &f
158 + if d.NextArg() {
159 + return d.ArgErr()
160 + }
161 +
162 + case "roll_size":
163 + var sizeStr string
164 + if !d.AllArgs(&sizeStr) {
165 + return d.ArgErr()
166 + }
167 + size, err := humanize.ParseBytes(sizeStr)
168 + if err != nil {
169 + return d.Errf("parsing size: %v", err)
170 + }
171 + fw.RollSizeMB = int(size) / 1024 / 1024
172 +
173 + case "roll_keep":
174 + var keepStr string
175 + if !d.AllArgs(&keepStr) {
176 + return d.ArgErr()
177 + }
178 + keep, err := strconv.Atoi(keepStr)
179 + if err != nil {
180 + return d.Errf("parsing roll_keep number: %v", err)
181 + }
182 + fw.RollKeep = keep
183 +
184 + case "roll_keep_for":
185 + var keepForStr string
186 + if !d.AllArgs(&keepForStr) {
187 + return d.ArgErr()
188 + }
189 + keepFor, err := time.ParseDuration(keepForStr)
190 + if err != nil {
191 + return d.Errf("parsing roll_keep_for duration: %v", err)
192 + }
193 + fw.RollKeepDays = int(keepFor.Hours()) / 24
194 + }
195 + }
196 + }
197 + return nil
198 +}
199 +
200 // Interface guards
201 var (
130 - _ caddy.Provisioner = (*FileWriter)(nil)
202 + _ caddy.Provisioner = (*FileWriter)(nil)
203 + _ caddy.WriterOpener = (*FileWriter)(nil)
204 + _ caddyfile.Unmarshaler = (*FileWriter)(nil)
205 )
modules/logging/netwriter.go
+21 -2
@@ -20,6 +20,7 @@ import (
20 "net"
21
22 "github.com/caddyserver/caddy/v2"
23 + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
24 )
25
26 func init() {
@@ -75,8 +76,26 @@ func (nw NetWriter) OpenWriter() (io.WriteCloser, error) {
76 return net.Dial(nw.addr.Network, nw.addr.JoinHostPort(0))
77 }
78
79 +// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
80 +//
81 +// net <address>
82 +//
83 +func (nw *NetWriter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
84 + for d.Next() {
85 + if !d.NextArg() {
86 + return d.ArgErr()
87 + }
88 + nw.Address = d.Val()
89 + if d.NextArg() {
90 + return d.ArgErr()
91 + }
92 + }
93 + return nil
94 +}
95 +
96 // Interface guards
97 var (
80 - _ caddy.Provisioner = (*NetWriter)(nil)
81 - _ caddy.WriterOpener = (*NetWriter)(nil)
98 + _ caddy.Provisioner = (*NetWriter)(nil)
99 + _ caddy.WriterOpener = (*NetWriter)(nil)
100 + _ caddyfile.Unmarshaler = (*NetWriter)(nil)
101 )
replacer.go
+4 -5
@@ -148,11 +148,10 @@ func (r *Replacer) replace(input, empty string,
148 if errOnUnknown {
149 return "", fmt.Errorf("unrecognized placeholder %s%s%s",
150 string(phOpen), key, string(phClose))
151 - } else if treatUnknownAsEmpty {
152 - if empty != "" {
153 - sb.WriteString(empty)
154 - }
155 - } else {
151 + } else if !treatUnknownAsEmpty {
152 + // if treatUnknownAsEmpty is true, we'll
153 + // handle an empty val later; so only
154 + // continue otherwise
155 lastWriteCursor = i
156 continue
157 }
replacer_test.go
+5
@@ -67,6 +67,11 @@ func TestReplacer(t *testing.T) {
67 input: `{{}`,
68 expect: "",
69 },
70 + {
71 + input: `{unknown}`,
72 + empty: "-",
73 + expect: "-",
74 + },
75 } {
76 actual := rep.ReplaceAll(tc.input, tc.empty)
77 if actual != tc.expect {