summaryrefslogtreecommitdiff
path: root/caddyconfig
diff options
context:
space:
mode:
authorMatt Holt <mholt@users.noreply.github.com>2020-02-25 22:00:33 -0700
committerGitHub <noreply@github.com>2020-02-25 22:00:33 -0700
commit5d97522d18da39cd3f9dd309774a5ad2c51f4c51 (patch)
tree8d90c0200edbf2f252a7d57c6f65255a0e38b9e0 /caddyconfig
parentf6b9cb7122c670ae063a9049455ec99edf0dc8be (diff)
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)
Diffstat (limited to 'caddyconfig')
-rw-r--r--caddyconfig/httpcaddyfile/builtins.go110
-rw-r--r--caddyconfig/httpcaddyfile/httptype.go67
2 files changed, 177 insertions, 0 deletions
diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go
index 8cfca18..43e184e 100644
--- a/caddyconfig/httpcaddyfile/builtins.go
+++ b/caddyconfig/httpcaddyfile/builtins.go
@@ -24,8 +24,10 @@ import (
"github.com/caddyserver/caddy/v2"
"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/caddytls"
+ "go.uber.org/zap/zapcore"
)
func init() {
@@ -37,6 +39,7 @@ func init() {
RegisterHandlerDirective("route", parseRoute)
RegisterHandlerDirective("handle", parseSegmentAsSubroute)
RegisterDirective("handle_errors", parseHandleErrors)
+ RegisterDirective("log", parseLog)
}
// parseBind parses the bind directive. Syntax:
@@ -426,9 +429,116 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
}, 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.
// This is used to remember which tag is used for each
// certificate files, since we need to avoid loading
// the same certificate files more than once, overwriting
// previous tags
var tlsCertTags = make(map[string]string)
+
+var logCounter int
diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go
index d4c2bb6..e3fcdd2 100644
--- a/caddyconfig/httpcaddyfile/httptype.go
+++ b/caddyconfig/httpcaddyfile/httptype.go
@@ -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!
cfg := &caddy.Config{AppsRaw: make(caddy.ModuleMap)}
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 != "" {
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
}
@@ -335,6 +376,8 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
val, err = parseOptEmail(disp)
case "admin":
val, err = parseOptAdmin(disp)
+ case "debug":
+ options["debug"] = true
default:
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)
}
}
+
+ // 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
@@ -940,6 +1002,11 @@ type matcherSetAndTokens struct {
tokens []caddyfile.Token
}
+type namedCustomLog struct {
+ name string
+ log *caddy.CustomLog
+}
+
// sbAddrAssocation is a mapping from a list of
// addresses to a list of server blocks that are
// served on those addresses.