mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 03:27:23 -05:00 
			
		
		
		
	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)
This commit is contained in:
		
							parent
							
								
									f6b9cb7122
								
							
						
					
					
						commit
						5d97522d18
					
				@ -24,8 +24,10 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/caddyserver/caddy/v2"
 | 
						"github.com/caddyserver/caddy/v2"
 | 
				
			||||||
	"github.com/caddyserver/caddy/v2/caddyconfig"
 | 
						"github.com/caddyserver/caddy/v2/caddyconfig"
 | 
				
			||||||
 | 
						"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
 | 
				
			||||||
	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
 | 
						"github.com/caddyserver/caddy/v2/modules/caddyhttp"
 | 
				
			||||||
	"github.com/caddyserver/caddy/v2/modules/caddytls"
 | 
						"github.com/caddyserver/caddy/v2/modules/caddytls"
 | 
				
			||||||
 | 
						"go.uber.org/zap/zapcore"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
@ -37,6 +39,7 @@ func init() {
 | 
				
			|||||||
	RegisterHandlerDirective("route", parseRoute)
 | 
						RegisterHandlerDirective("route", parseRoute)
 | 
				
			||||||
	RegisterHandlerDirective("handle", parseSegmentAsSubroute)
 | 
						RegisterHandlerDirective("handle", parseSegmentAsSubroute)
 | 
				
			||||||
	RegisterDirective("handle_errors", parseHandleErrors)
 | 
						RegisterDirective("handle_errors", parseHandleErrors)
 | 
				
			||||||
 | 
						RegisterDirective("log", parseLog)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// parseBind parses the bind directive. Syntax:
 | 
					// parseBind parses the bind directive. Syntax:
 | 
				
			||||||
@ -426,9 +429,116 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
 | 
				
			|||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// parseLog parses the log directive. Syntax:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     log {
 | 
				
			||||||
 | 
					//         output <writer_module> ...
 | 
				
			||||||
 | 
					//         format <encoder_module> ...
 | 
				
			||||||
 | 
					//         level  <level>
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					func parseLog(h Helper) ([]ConfigValue, error) {
 | 
				
			||||||
 | 
						var configValues []ConfigValue
 | 
				
			||||||
 | 
						for h.Next() {
 | 
				
			||||||
 | 
							cl := new(caddy.CustomLog)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for h.NextBlock(0) {
 | 
				
			||||||
 | 
								switch h.Val() {
 | 
				
			||||||
 | 
								case "output":
 | 
				
			||||||
 | 
									if !h.NextArg() {
 | 
				
			||||||
 | 
										return nil, h.ArgErr()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									moduleName := h.Val()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// can't use the usual caddyfile.Unmarshaler flow with the
 | 
				
			||||||
 | 
									// standard writers because they are in the caddy package
 | 
				
			||||||
 | 
									// (because they are the default) and implementing that
 | 
				
			||||||
 | 
									// interface there would unfortunately create circular import
 | 
				
			||||||
 | 
									var wo caddy.WriterOpener
 | 
				
			||||||
 | 
									switch moduleName {
 | 
				
			||||||
 | 
									case "stdout":
 | 
				
			||||||
 | 
										wo = caddy.StdoutWriter{}
 | 
				
			||||||
 | 
									case "stderr":
 | 
				
			||||||
 | 
										wo = caddy.StderrWriter{}
 | 
				
			||||||
 | 
									case "discard":
 | 
				
			||||||
 | 
										wo = caddy.DiscardWriter{}
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
										mod, err := caddy.GetModule("caddy.logging.writers." + moduleName)
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											return nil, h.Errf("getting log writer module named '%s': %v", moduleName, err)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										unm, ok := mod.New().(caddyfile.Unmarshaler)
 | 
				
			||||||
 | 
										if !ok {
 | 
				
			||||||
 | 
											return nil, h.Errf("log writer module '%s' is not a Caddyfile unmarshaler", mod)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										err = unm.UnmarshalCaddyfile(h.NewFromNextSegment())
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											return nil, err
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										wo, ok = unm.(caddy.WriterOpener)
 | 
				
			||||||
 | 
										if !ok {
 | 
				
			||||||
 | 
											return nil, h.Errf("module %s is not a WriterOpener", mod)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									cl.WriterRaw = caddyconfig.JSONModuleObject(wo, "output", moduleName, h.warnings)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case "format":
 | 
				
			||||||
 | 
									if !h.NextArg() {
 | 
				
			||||||
 | 
										return nil, h.ArgErr()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									moduleName := h.Val()
 | 
				
			||||||
 | 
									mod, err := caddy.GetModule("caddy.logging.encoders." + moduleName)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil, h.Errf("getting log encoder module named '%s': %v", moduleName, err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									unm, ok := mod.New().(caddyfile.Unmarshaler)
 | 
				
			||||||
 | 
									if !ok {
 | 
				
			||||||
 | 
										return nil, h.Errf("log encoder module '%s' is not a Caddyfile unmarshaler", mod)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									err = unm.UnmarshalCaddyfile(h.NewFromNextSegment())
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									enc, ok := unm.(zapcore.Encoder)
 | 
				
			||||||
 | 
									if !ok {
 | 
				
			||||||
 | 
										return nil, h.Errf("module %s is not a zapcore.Encoder", mod)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									cl.EncoderRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, h.warnings)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case "level":
 | 
				
			||||||
 | 
									if !h.NextArg() {
 | 
				
			||||||
 | 
										return nil, h.ArgErr()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									cl.Level = h.Val()
 | 
				
			||||||
 | 
									if h.NextArg() {
 | 
				
			||||||
 | 
										return nil, h.ArgErr()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									return nil, h.Errf("unrecognized subdirective: %s", h.Val())
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var val namedCustomLog
 | 
				
			||||||
 | 
							if !reflect.DeepEqual(cl, new(caddy.CustomLog)) {
 | 
				
			||||||
 | 
								cl.Include = []string{"http.log.access"}
 | 
				
			||||||
 | 
								val.name = fmt.Sprintf("log%d", logCounter)
 | 
				
			||||||
 | 
								val.log = cl
 | 
				
			||||||
 | 
								logCounter++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							configValues = append(configValues, ConfigValue{
 | 
				
			||||||
 | 
								Class: "custom_log",
 | 
				
			||||||
 | 
								Value: val,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return configValues, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// tlsCertTags maps certificate filenames to their tag.
 | 
					// tlsCertTags maps certificate filenames to their tag.
 | 
				
			||||||
// This is used to remember which tag is used for each
 | 
					// This is used to remember which tag is used for each
 | 
				
			||||||
// certificate files, since we need to avoid loading
 | 
					// certificate files, since we need to avoid loading
 | 
				
			||||||
// the same certificate files more than once, overwriting
 | 
					// the same certificate files more than once, overwriting
 | 
				
			||||||
// previous tags
 | 
					// previous tags
 | 
				
			||||||
var tlsCertTags = make(map[string]string)
 | 
					var tlsCertTags = make(map[string]string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var logCounter int
 | 
				
			||||||
 | 
				
			|||||||
@ -282,6 +282,35 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// extract any custom logs, and enforce configured levels
 | 
				
			||||||
 | 
						var customLogs []namedCustomLog
 | 
				
			||||||
 | 
						var hasDefaultLog bool
 | 
				
			||||||
 | 
						for _, sb := range serverBlocks {
 | 
				
			||||||
 | 
							for _, clVal := range sb.pile["custom_log"] {
 | 
				
			||||||
 | 
								ncl := clVal.Value.(namedCustomLog)
 | 
				
			||||||
 | 
								if ncl.name == "" {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if ncl.name == "default" {
 | 
				
			||||||
 | 
									hasDefaultLog = true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if _, ok := options["debug"]; ok && ncl.log.Level == "" {
 | 
				
			||||||
 | 
									ncl.log.Level = "DEBUG"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								customLogs = append(customLogs, ncl)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !hasDefaultLog {
 | 
				
			||||||
 | 
							// if the default log was not customized, ensure we
 | 
				
			||||||
 | 
							// configure it with any applicable options
 | 
				
			||||||
 | 
							if _, ok := options["debug"]; ok {
 | 
				
			||||||
 | 
								customLogs = append(customLogs, namedCustomLog{
 | 
				
			||||||
 | 
									name: "default",
 | 
				
			||||||
 | 
									log:  &caddy.CustomLog{Level: "DEBUG"},
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// annnd the top-level config, then we're done!
 | 
						// annnd the top-level config, then we're done!
 | 
				
			||||||
	cfg := &caddy.Config{AppsRaw: make(caddy.ModuleMap)}
 | 
						cfg := &caddy.Config{AppsRaw: make(caddy.ModuleMap)}
 | 
				
			||||||
	if !reflect.DeepEqual(httpApp, caddyhttp.App{}) {
 | 
						if !reflect.DeepEqual(httpApp, caddyhttp.App{}) {
 | 
				
			||||||
@ -299,6 +328,18 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
 | 
				
			|||||||
	if adminConfig, ok := options["admin"].(string); ok && adminConfig != "" {
 | 
						if adminConfig, ok := options["admin"].(string); ok && adminConfig != "" {
 | 
				
			||||||
		cfg.Admin = &caddy.AdminConfig{Listen: adminConfig}
 | 
							cfg.Admin = &caddy.AdminConfig{Listen: adminConfig}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if len(customLogs) > 0 {
 | 
				
			||||||
 | 
							if cfg.Logging == nil {
 | 
				
			||||||
 | 
								cfg.Logging = &caddy.Logging{
 | 
				
			||||||
 | 
									Logs: make(map[string]*caddy.CustomLog),
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, ncl := range customLogs {
 | 
				
			||||||
 | 
								if ncl.name != "" {
 | 
				
			||||||
 | 
									cfg.Logging.Logs[ncl.name] = ncl.log
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return cfg, warnings, nil
 | 
						return cfg, warnings, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -335,6 +376,8 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
 | 
				
			|||||||
			val, err = parseOptEmail(disp)
 | 
								val, err = parseOptEmail(disp)
 | 
				
			||||||
		case "admin":
 | 
							case "admin":
 | 
				
			||||||
			val, err = parseOptAdmin(disp)
 | 
								val, err = parseOptAdmin(disp)
 | 
				
			||||||
 | 
							case "debug":
 | 
				
			||||||
 | 
								options["debug"] = true
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			return nil, fmt.Errorf("unrecognized parameter name: %s", dir)
 | 
								return nil, fmt.Errorf("unrecognized parameter name: %s", dir)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -506,6 +549,25 @@ func (st *ServerType) serversFromPairings(
 | 
				
			|||||||
					srv.Errors.Routes = appendSubrouteToRouteList(srv.Errors.Routes, sr, matcherSetsEnc, p, warnings)
 | 
										srv.Errors.Routes = appendSubrouteToRouteList(srv.Errors.Routes, sr, matcherSetsEnc, p, warnings)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// add log associations
 | 
				
			||||||
 | 
								for _, cval := range sblock.pile["custom_log"] {
 | 
				
			||||||
 | 
									ncl := cval.Value.(namedCustomLog)
 | 
				
			||||||
 | 
									if srv.Logs == nil {
 | 
				
			||||||
 | 
										srv.Logs = &caddyhttp.ServerLogConfig{
 | 
				
			||||||
 | 
											LoggerNames: make(map[string]string),
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									hosts, err := st.hostsFromServerBlockKeys(sblock.block)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									for _, h := range hosts {
 | 
				
			||||||
 | 
										if ncl.name != "" {
 | 
				
			||||||
 | 
											srv.Logs.LoggerNames[h] = ncl.name
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// a catch-all TLS conn policy is necessary to ensure TLS can
 | 
							// a catch-all TLS conn policy is necessary to ensure TLS can
 | 
				
			||||||
@ -940,6 +1002,11 @@ type matcherSetAndTokens struct {
 | 
				
			|||||||
	tokens     []caddyfile.Token
 | 
						tokens     []caddyfile.Token
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type namedCustomLog struct {
 | 
				
			||||||
 | 
						name string
 | 
				
			||||||
 | 
						log  *caddy.CustomLog
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// sbAddrAssocation is a mapping from a list of
 | 
					// sbAddrAssocation is a mapping from a list of
 | 
				
			||||||
// addresses to a list of server blocks that are
 | 
					// addresses to a list of server blocks that are
 | 
				
			||||||
// served on those addresses.
 | 
					// served on those addresses.
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							@ -7,7 +7,7 @@ require (
 | 
				
			|||||||
	github.com/alecthomas/chroma v0.7.0
 | 
						github.com/alecthomas/chroma v0.7.0
 | 
				
			||||||
	github.com/andybalholm/brotli v0.0.0-20190821151343-b60f0d972eeb
 | 
						github.com/andybalholm/brotli v0.0.0-20190821151343-b60f0d972eeb
 | 
				
			||||||
	github.com/cenkalti/backoff/v3 v3.1.1 // indirect
 | 
						github.com/cenkalti/backoff/v3 v3.1.1 // indirect
 | 
				
			||||||
	github.com/dustin/go-humanize v1.0.0
 | 
						github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac
 | 
				
			||||||
	github.com/go-acme/lego/v3 v3.3.0
 | 
						github.com/go-acme/lego/v3 v3.3.0
 | 
				
			||||||
	github.com/golang/groupcache v0.0.0-20191002201903-404acd9df4cc
 | 
						github.com/golang/groupcache v0.0.0-20191002201903-404acd9df4cc
 | 
				
			||||||
	github.com/ilibs/json5 v1.0.1
 | 
						github.com/ilibs/json5 v1.0.1
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							@ -94,6 +94,8 @@ github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s9
 | 
				
			|||||||
github.com/dnsimple/dnsimple-go v0.30.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
 | 
					github.com/dnsimple/dnsimple-go v0.30.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
 | 
				
			||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 | 
					github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 | 
				
			||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 | 
					github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 | 
				
			||||||
 | 
					github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac h1:opbrjaN/L8gg6Xh5D04Tem+8xVcz6ajZlGCs49mQgyg=
 | 
				
			||||||
 | 
					github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 | 
				
			||||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
 | 
					github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
 | 
				
			||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
 | 
					github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
 | 
				
			||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
 | 
					github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
 | 
				
			||||||
 | 
				
			|||||||
@ -21,6 +21,7 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/caddyserver/caddy/v2"
 | 
						"github.com/caddyserver/caddy/v2"
 | 
				
			||||||
 | 
						"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
 | 
				
			||||||
	zaplogfmt "github.com/jsternberg/zap-logfmt"
 | 
						zaplogfmt "github.com/jsternberg/zap-logfmt"
 | 
				
			||||||
	"go.uber.org/zap"
 | 
						"go.uber.org/zap"
 | 
				
			||||||
	"go.uber.org/zap/buffer"
 | 
						"go.uber.org/zap/buffer"
 | 
				
			||||||
@ -31,7 +32,7 @@ func init() {
 | 
				
			|||||||
	caddy.RegisterModule(ConsoleEncoder{})
 | 
						caddy.RegisterModule(ConsoleEncoder{})
 | 
				
			||||||
	caddy.RegisterModule(JSONEncoder{})
 | 
						caddy.RegisterModule(JSONEncoder{})
 | 
				
			||||||
	caddy.RegisterModule(LogfmtEncoder{})
 | 
						caddy.RegisterModule(LogfmtEncoder{})
 | 
				
			||||||
	caddy.RegisterModule(StringEncoder{})
 | 
						caddy.RegisterModule(SingleFieldEncoder{})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ConsoleEncoder encodes log entries that are mostly human-readable.
 | 
					// ConsoleEncoder encodes log entries that are mostly human-readable.
 | 
				
			||||||
@ -54,6 +55,27 @@ func (ce *ConsoleEncoder) Provision(_ caddy.Context) error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     console {
 | 
				
			||||||
 | 
					//         <common encoder config subdirectives...>
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// See the godoc on the LogEncoderConfig type for the syntax of
 | 
				
			||||||
 | 
					// subdirectives that are common to most/all encoders.
 | 
				
			||||||
 | 
					func (ce *ConsoleEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | 
				
			||||||
 | 
						for d.Next() {
 | 
				
			||||||
 | 
							if d.NextArg() {
 | 
				
			||||||
 | 
								return d.ArgErr()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err := ce.LogEncoderConfig.UnmarshalCaddyfile(d)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// JSONEncoder encodes entries as JSON.
 | 
					// JSONEncoder encodes entries as JSON.
 | 
				
			||||||
type JSONEncoder struct {
 | 
					type JSONEncoder struct {
 | 
				
			||||||
	zapcore.Encoder `json:"-"`
 | 
						zapcore.Encoder `json:"-"`
 | 
				
			||||||
@ -74,6 +96,27 @@ func (je *JSONEncoder) Provision(_ caddy.Context) error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     json {
 | 
				
			||||||
 | 
					//         <common encoder config subdirectives...>
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// See the godoc on the LogEncoderConfig type for the syntax of
 | 
				
			||||||
 | 
					// subdirectives that are common to most/all encoders.
 | 
				
			||||||
 | 
					func (je *JSONEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | 
				
			||||||
 | 
						for d.Next() {
 | 
				
			||||||
 | 
							if d.NextArg() {
 | 
				
			||||||
 | 
								return d.ArgErr()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err := je.LogEncoderConfig.UnmarshalCaddyfile(d)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// LogfmtEncoder encodes log entries as logfmt:
 | 
					// LogfmtEncoder encodes log entries as logfmt:
 | 
				
			||||||
// https://www.brandur.org/logfmt
 | 
					// https://www.brandur.org/logfmt
 | 
				
			||||||
type LogfmtEncoder struct {
 | 
					type LogfmtEncoder struct {
 | 
				
			||||||
@ -95,26 +138,47 @@ func (lfe *LogfmtEncoder) Provision(_ caddy.Context) error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// StringEncoder writes a log entry that consists entirely
 | 
					// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     logfmt {
 | 
				
			||||||
 | 
					//         <common encoder config subdirectives...>
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// See the godoc on the LogEncoderConfig type for the syntax of
 | 
				
			||||||
 | 
					// subdirectives that are common to most/all encoders.
 | 
				
			||||||
 | 
					func (lfe *LogfmtEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | 
				
			||||||
 | 
						for d.Next() {
 | 
				
			||||||
 | 
							if d.NextArg() {
 | 
				
			||||||
 | 
								return d.ArgErr()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err := lfe.LogEncoderConfig.UnmarshalCaddyfile(d)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SingleFieldEncoder writes a log entry that consists entirely
 | 
				
			||||||
// of a single string field in the log entry. This is useful
 | 
					// of a single string field in the log entry. This is useful
 | 
				
			||||||
// for custom, self-encoded log entries that consist of a
 | 
					// for custom, self-encoded log entries that consist of a
 | 
				
			||||||
// single field in the structured log entry.
 | 
					// single field in the structured log entry.
 | 
				
			||||||
type StringEncoder struct {
 | 
					type SingleFieldEncoder struct {
 | 
				
			||||||
	zapcore.Encoder `json:"-"`
 | 
						zapcore.Encoder `json:"-"`
 | 
				
			||||||
	FieldName       string          `json:"field,omitempty"`
 | 
						FieldName       string          `json:"field,omitempty"`
 | 
				
			||||||
	FallbackRaw     json.RawMessage `json:"fallback,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
 | 
						FallbackRaw     json.RawMessage `json:"fallback,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CaddyModule returns the Caddy module information.
 | 
					// CaddyModule returns the Caddy module information.
 | 
				
			||||||
func (StringEncoder) CaddyModule() caddy.ModuleInfo {
 | 
					func (SingleFieldEncoder) CaddyModule() caddy.ModuleInfo {
 | 
				
			||||||
	return caddy.ModuleInfo{
 | 
						return caddy.ModuleInfo{
 | 
				
			||||||
		ID:  "caddy.logging.encoders.string",
 | 
							ID:  "caddy.logging.encoders.single_field",
 | 
				
			||||||
		New: func() caddy.Module { return new(StringEncoder) },
 | 
							New: func() caddy.Module { return new(SingleFieldEncoder) },
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Provision sets up the encoder.
 | 
					// Provision sets up the encoder.
 | 
				
			||||||
func (se *StringEncoder) Provision(ctx caddy.Context) error {
 | 
					func (se *SingleFieldEncoder) Provision(ctx caddy.Context) error {
 | 
				
			||||||
	if se.FallbackRaw != nil {
 | 
						if se.FallbackRaw != nil {
 | 
				
			||||||
		val, err := ctx.LoadModule(se, "FallbackRaw")
 | 
							val, err := ctx.LoadModule(se, "FallbackRaw")
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
@ -132,16 +196,16 @@ func (se *StringEncoder) Provision(ctx caddy.Context) error {
 | 
				
			|||||||
// necessary because we implement our own EncodeEntry,
 | 
					// necessary because we implement our own EncodeEntry,
 | 
				
			||||||
// and if we simply let the embedded encoder's Clone
 | 
					// and if we simply let the embedded encoder's Clone
 | 
				
			||||||
// be promoted, it would return a clone of that, and
 | 
					// be promoted, it would return a clone of that, and
 | 
				
			||||||
// we'd lose our StringEncoder's EncodeEntry.
 | 
					// we'd lose our SingleFieldEncoder's EncodeEntry.
 | 
				
			||||||
func (se StringEncoder) Clone() zapcore.Encoder {
 | 
					func (se SingleFieldEncoder) Clone() zapcore.Encoder {
 | 
				
			||||||
	return StringEncoder{
 | 
						return SingleFieldEncoder{
 | 
				
			||||||
		Encoder:   se.Encoder.Clone(),
 | 
							Encoder:   se.Encoder.Clone(),
 | 
				
			||||||
		FieldName: se.FieldName,
 | 
							FieldName: se.FieldName,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EncodeEntry partially implements the zapcore.Encoder interface.
 | 
					// EncodeEntry partially implements the zapcore.Encoder interface.
 | 
				
			||||||
func (se StringEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
 | 
					func (se SingleFieldEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
 | 
				
			||||||
	for _, f := range fields {
 | 
						for _, f := range fields {
 | 
				
			||||||
		if f.Key == se.FieldName {
 | 
							if f.Key == se.FieldName {
 | 
				
			||||||
			buf := bufferpool.Get()
 | 
								buf := bufferpool.Get()
 | 
				
			||||||
@ -158,6 +222,21 @@ func (se StringEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (
 | 
				
			|||||||
	return se.Encoder.EncodeEntry(ent, fields)
 | 
						return se.Encoder.EncodeEntry(ent, fields)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     single_field <field_name>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					func (se *SingleFieldEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | 
				
			||||||
 | 
						for d.Next() {
 | 
				
			||||||
 | 
							var fieldName string
 | 
				
			||||||
 | 
							if !d.AllArgs(&fieldName) {
 | 
				
			||||||
 | 
								return d.ArgErr()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							se.FieldName = d.Val()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// LogEncoderConfig holds configuration common to most encoders.
 | 
					// LogEncoderConfig holds configuration common to most encoders.
 | 
				
			||||||
type LogEncoderConfig struct {
 | 
					type LogEncoderConfig struct {
 | 
				
			||||||
	MessageKey     *string `json:"message_key,omitempty"`
 | 
						MessageKey     *string `json:"message_key,omitempty"`
 | 
				
			||||||
@ -172,6 +251,53 @@ type LogEncoderConfig struct {
 | 
				
			|||||||
	LevelFormat    string  `json:"level_format,omitempty"`
 | 
						LevelFormat    string  `json:"level_format,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UnmarshalCaddyfile populates the struct from Caddyfile tokens. Syntax:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     {
 | 
				
			||||||
 | 
					//         message_key <key>
 | 
				
			||||||
 | 
					//         level_key   <key>
 | 
				
			||||||
 | 
					//         time_key    <key>
 | 
				
			||||||
 | 
					//         name_key    <key>
 | 
				
			||||||
 | 
					//         caller_key  <key>
 | 
				
			||||||
 | 
					//         stacktrace_key <key>
 | 
				
			||||||
 | 
					//         line_ending  <char>
 | 
				
			||||||
 | 
					//         time_format  <format>
 | 
				
			||||||
 | 
					//         level_format <format>
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					func (lec *LogEncoderConfig) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | 
				
			||||||
 | 
						for nesting := d.Nesting(); d.NextBlock(nesting); {
 | 
				
			||||||
 | 
							subdir := d.Val()
 | 
				
			||||||
 | 
							var arg string
 | 
				
			||||||
 | 
							if !d.AllArgs(&arg) {
 | 
				
			||||||
 | 
								return d.ArgErr()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							switch subdir {
 | 
				
			||||||
 | 
							case "message_key":
 | 
				
			||||||
 | 
								lec.MessageKey = &arg
 | 
				
			||||||
 | 
							case "level_key":
 | 
				
			||||||
 | 
								lec.LevelKey = &arg
 | 
				
			||||||
 | 
							case "time_key":
 | 
				
			||||||
 | 
								lec.TimeKey = &arg
 | 
				
			||||||
 | 
							case "name_key":
 | 
				
			||||||
 | 
								lec.NameKey = &arg
 | 
				
			||||||
 | 
							case "caller_key":
 | 
				
			||||||
 | 
								lec.CallerKey = &arg
 | 
				
			||||||
 | 
							case "stacktrace_key":
 | 
				
			||||||
 | 
								lec.StacktraceKey = &arg
 | 
				
			||||||
 | 
							case "line_ending":
 | 
				
			||||||
 | 
								lec.LineEnding = &arg
 | 
				
			||||||
 | 
							case "time_format":
 | 
				
			||||||
 | 
								lec.TimeFormat = arg
 | 
				
			||||||
 | 
							case "level_format":
 | 
				
			||||||
 | 
								lec.LevelFormat = arg
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								return d.Errf("unrecognized subdirective %s", subdir)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ZapcoreEncoderConfig returns the equivalent zapcore.EncoderConfig.
 | 
					// ZapcoreEncoderConfig returns the equivalent zapcore.EncoderConfig.
 | 
				
			||||||
// If lec is nil, zap.NewProductionEncoderConfig() is returned.
 | 
					// If lec is nil, zap.NewProductionEncoderConfig() is returned.
 | 
				
			||||||
func (lec *LogEncoderConfig) ZapcoreEncoderConfig() zapcore.EncoderConfig {
 | 
					func (lec *LogEncoderConfig) ZapcoreEncoderConfig() zapcore.EncoderConfig {
 | 
				
			||||||
@ -263,5 +389,10 @@ var (
 | 
				
			|||||||
	_ zapcore.Encoder = (*ConsoleEncoder)(nil)
 | 
						_ zapcore.Encoder = (*ConsoleEncoder)(nil)
 | 
				
			||||||
	_ zapcore.Encoder = (*JSONEncoder)(nil)
 | 
						_ zapcore.Encoder = (*JSONEncoder)(nil)
 | 
				
			||||||
	_ zapcore.Encoder = (*LogfmtEncoder)(nil)
 | 
						_ zapcore.Encoder = (*LogfmtEncoder)(nil)
 | 
				
			||||||
	_ zapcore.Encoder = (*StringEncoder)(nil)
 | 
						_ zapcore.Encoder = (*SingleFieldEncoder)(nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_ caddyfile.Unmarshaler = (*ConsoleEncoder)(nil)
 | 
				
			||||||
 | 
						_ caddyfile.Unmarshaler = (*JSONEncoder)(nil)
 | 
				
			||||||
 | 
						_ caddyfile.Unmarshaler = (*LogfmtEncoder)(nil)
 | 
				
			||||||
 | 
						_ caddyfile.Unmarshaler = (*SingleFieldEncoder)(nil)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
@ -19,8 +19,12 @@ import (
 | 
				
			|||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/caddyserver/caddy/v2"
 | 
						"github.com/caddyserver/caddy/v2"
 | 
				
			||||||
 | 
						"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
 | 
				
			||||||
 | 
						"github.com/dustin/go-humanize"
 | 
				
			||||||
	"gopkg.in/natefinch/lumberjack.v2"
 | 
						"gopkg.in/natefinch/lumberjack.v2"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -125,7 +129,77 @@ func (fw FileWriter) OpenWriter() (io.WriteCloser, error) {
 | 
				
			|||||||
	return os.OpenFile(fw.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
 | 
						return os.OpenFile(fw.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     file <filename> {
 | 
				
			||||||
 | 
					//         roll_disabled
 | 
				
			||||||
 | 
					//         roll_size     <size>
 | 
				
			||||||
 | 
					//         roll_keep     <num>
 | 
				
			||||||
 | 
					//         roll_keep_for <days>
 | 
				
			||||||
 | 
					//     }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The roll_size value will be rounded down to number of megabytes (MiB).
 | 
				
			||||||
 | 
					// The roll_keep_for duration will be rounded down to number of days.
 | 
				
			||||||
 | 
					func (fw *FileWriter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | 
				
			||||||
 | 
						for d.Next() {
 | 
				
			||||||
 | 
							if !d.NextArg() {
 | 
				
			||||||
 | 
								return d.ArgErr()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							fw.Filename = d.Val()
 | 
				
			||||||
 | 
							if d.NextArg() {
 | 
				
			||||||
 | 
								return d.ArgErr()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for d.NextBlock(0) {
 | 
				
			||||||
 | 
								switch d.Val() {
 | 
				
			||||||
 | 
								case "roll_disabled":
 | 
				
			||||||
 | 
									var f bool
 | 
				
			||||||
 | 
									fw.Roll = &f
 | 
				
			||||||
 | 
									if d.NextArg() {
 | 
				
			||||||
 | 
										return d.ArgErr()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case "roll_size":
 | 
				
			||||||
 | 
									var sizeStr string
 | 
				
			||||||
 | 
									if !d.AllArgs(&sizeStr) {
 | 
				
			||||||
 | 
										return d.ArgErr()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									size, err := humanize.ParseBytes(sizeStr)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return d.Errf("parsing size: %v", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									fw.RollSizeMB = int(size) / 1024 / 1024
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case "roll_keep":
 | 
				
			||||||
 | 
									var keepStr string
 | 
				
			||||||
 | 
									if !d.AllArgs(&keepStr) {
 | 
				
			||||||
 | 
										return d.ArgErr()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									keep, err := strconv.Atoi(keepStr)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return d.Errf("parsing roll_keep number: %v", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									fw.RollKeep = keep
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case "roll_keep_for":
 | 
				
			||||||
 | 
									var keepForStr string
 | 
				
			||||||
 | 
									if !d.AllArgs(&keepForStr) {
 | 
				
			||||||
 | 
										return d.ArgErr()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									keepFor, err := time.ParseDuration(keepForStr)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return d.Errf("parsing roll_keep_for duration: %v", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									fw.RollKeepDays = int(keepFor.Hours()) / 24
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Interface guards
 | 
					// Interface guards
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	_ caddy.Provisioner     = (*FileWriter)(nil)
 | 
						_ caddy.Provisioner     = (*FileWriter)(nil)
 | 
				
			||||||
 | 
						_ caddy.WriterOpener    = (*FileWriter)(nil)
 | 
				
			||||||
 | 
						_ caddyfile.Unmarshaler = (*FileWriter)(nil)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
@ -20,6 +20,7 @@ import (
 | 
				
			|||||||
	"net"
 | 
						"net"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/caddyserver/caddy/v2"
 | 
						"github.com/caddyserver/caddy/v2"
 | 
				
			||||||
 | 
						"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
@ -75,8 +76,26 @@ func (nw NetWriter) OpenWriter() (io.WriteCloser, error) {
 | 
				
			|||||||
	return net.Dial(nw.addr.Network, nw.addr.JoinHostPort(0))
 | 
						return net.Dial(nw.addr.Network, nw.addr.JoinHostPort(0))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     net <address>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					func (nw *NetWriter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | 
				
			||||||
 | 
						for d.Next() {
 | 
				
			||||||
 | 
							if !d.NextArg() {
 | 
				
			||||||
 | 
								return d.ArgErr()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							nw.Address = d.Val()
 | 
				
			||||||
 | 
							if d.NextArg() {
 | 
				
			||||||
 | 
								return d.ArgErr()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Interface guards
 | 
					// Interface guards
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	_ caddy.Provisioner     = (*NetWriter)(nil)
 | 
						_ caddy.Provisioner     = (*NetWriter)(nil)
 | 
				
			||||||
	_ caddy.WriterOpener    = (*NetWriter)(nil)
 | 
						_ caddy.WriterOpener    = (*NetWriter)(nil)
 | 
				
			||||||
 | 
						_ caddyfile.Unmarshaler = (*NetWriter)(nil)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
@ -148,11 +148,10 @@ func (r *Replacer) replace(input, empty string,
 | 
				
			|||||||
			if errOnUnknown {
 | 
								if errOnUnknown {
 | 
				
			||||||
				return "", fmt.Errorf("unrecognized placeholder %s%s%s",
 | 
									return "", fmt.Errorf("unrecognized placeholder %s%s%s",
 | 
				
			||||||
					string(phOpen), key, string(phClose))
 | 
										string(phOpen), key, string(phClose))
 | 
				
			||||||
			} else if treatUnknownAsEmpty {
 | 
								} else if !treatUnknownAsEmpty {
 | 
				
			||||||
				if empty != "" {
 | 
									// if treatUnknownAsEmpty is true, we'll
 | 
				
			||||||
					sb.WriteString(empty)
 | 
									// handle an empty val later; so only
 | 
				
			||||||
				}
 | 
									// continue otherwise
 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				lastWriteCursor = i
 | 
									lastWriteCursor = i
 | 
				
			||||||
				continue
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
				
			|||||||
@ -67,6 +67,11 @@ func TestReplacer(t *testing.T) {
 | 
				
			|||||||
			input:  `{{}`,
 | 
								input:  `{{}`,
 | 
				
			||||||
			expect: "",
 | 
								expect: "",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								input:  `{unknown}`,
 | 
				
			||||||
 | 
								empty:  "-",
 | 
				
			||||||
 | 
								expect: "-",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	} {
 | 
						} {
 | 
				
			||||||
		actual := rep.ReplaceAll(tc.input, tc.empty)
 | 
							actual := rep.ReplaceAll(tc.input, tc.empty)
 | 
				
			||||||
		if actual != tc.expect {
 | 
							if actual != tc.expect {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user