diff options
author | Francis Lavoie <[email protected]> | 2024-02-09 00:05:35 -0500 |
---|---|---|
committer | Francis Lavoie <[email protected]> | 2024-04-15 15:10:05 -0400 |
commit | 4326c737708f72356bc0c376a8353072386ea88a (patch) | |
tree | 637bcd1a7c44d7f1a305c6cece97b182bae84383 | |
parent | 26748d06b4a39f1e1d02863245573a7ecd1bebc4 (diff) | |
download | caddy-4326c737708f72356bc0c376a8353072386ea88a.tar.gz caddy-4326c737708f72356bc0c376a8353072386ea88a.zip |
caddyhttp: Support multiple logger names per host
9 files changed, 131 insertions, 64 deletions
diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index 99411d1cc..0d961b2de 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -804,23 +804,23 @@ func (st *ServerType) serversFromPairings( } else if len(ncl.hostnames) > 0 { // if the logger overrides the hostnames, map that to the logger name for _, h := range ncl.hostnames { - if srv.Logs.LoggerNames == nil { - srv.Logs.LoggerNames = make(map[string]string) + if srv.Logs.LoggerMapping == nil { + srv.Logs.LoggerMapping = make(map[string][]string) } - srv.Logs.LoggerNames[h] = ncl.name + srv.Logs.LoggerMapping[h] = append(srv.Logs.LoggerMapping[h], ncl.name) } } else { // otherwise, map each host to the logger name for _, h := range sblockLogHosts { - if srv.Logs.LoggerNames == nil { - srv.Logs.LoggerNames = make(map[string]string) - } // strip the port from the host, if any host, _, err := net.SplitHostPort(h) if err != nil { host = h } - srv.Logs.LoggerNames[host] = ncl.name + if srv.Logs.LoggerMapping == nil { + srv.Logs.LoggerMapping = make(map[string][]string) + } + srv.Logs.LoggerMapping[host] = append(srv.Logs.LoggerMapping[host], ncl.name) } } } diff --git a/caddytest/integration/caddyfile_adapt/import_args_snippet.caddyfiletest b/caddytest/integration/caddyfile_adapt/import_args_snippet.caddyfiletest index 10d9f4cfc..109fb4cff 100644 --- a/caddytest/integration/caddyfile_adapt/import_args_snippet.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/import_args_snippet.caddyfiletest @@ -71,9 +71,13 @@ b.example.com { } ], "logs": { - "logger_names": { - "a.example.com": "log0", - "b.example.com": "log1" + "logger_mapping": { + "a.example.com": [ + "log0" + ], + "b.example.com": [ + "log1" + ] } } } diff --git a/caddytest/integration/caddyfile_adapt/log_except_catchall_blocks.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_except_catchall_blocks.caddyfiletest index e1362f8fb..332fa3d11 100644 --- a/caddytest/integration/caddyfile_adapt/log_except_catchall_blocks.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/log_except_catchall_blocks.caddyfiletest @@ -98,8 +98,10 @@ http://localhost:2020 { ] }, "logs": { - "logger_names": { - "localhost": "" + "logger_mapping": { + "localhost": [ + "" + ] }, "skip_unmapped_hosts": true } diff --git a/caddytest/integration/caddyfile_adapt/log_override_hostname.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_override_hostname.caddyfiletest index c36ba23f8..76162d522 100644 --- a/caddytest/integration/caddyfile_adapt/log_override_hostname.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/log_override_hostname.caddyfiletest @@ -74,10 +74,16 @@ example.com:8443 { } ], "logs": { - "logger_names": { - "bar.example.com": "log0", - "baz.example.com": "log1", - "foo.example.com": "log0" + "logger_mapping": { + "bar.example.com": [ + "log0" + ], + "baz.example.com": [ + "log1" + ], + "foo.example.com": [ + "log0" + ] } } }, @@ -98,8 +104,10 @@ example.com:8443 { } ], "logs": { - "logger_names": { - "example.com": "log2" + "logger_mapping": { + "example.com": [ + "log2" + ] } } } diff --git a/caddytest/integration/caddyfile_adapt/log_override_name_multiaccess.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_override_name_multiaccess.caddyfiletest index d48857a2d..ddb742676 100644 --- a/caddytest/integration/caddyfile_adapt/log_override_name_multiaccess.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/log_override_name_multiaccess.caddyfiletest @@ -75,8 +75,10 @@ http://localhost:8881 { ] }, "logs": { - "logger_names": { - "localhost": "foo" + "logger_mapping": { + "localhost": [ + "foo" + ] } } } diff --git a/caddytest/integration/caddyfile_adapt/log_override_name_multiaccess_debug.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_override_name_multiaccess_debug.caddyfiletest index d024dc386..5a1002e23 100644 --- a/caddytest/integration/caddyfile_adapt/log_override_name_multiaccess_debug.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/log_override_name_multiaccess_debug.caddyfiletest @@ -80,8 +80,10 @@ http://localhost:8881 { ] }, "logs": { - "logger_names": { - "localhost": "foo" + "logger_mapping": { + "localhost": [ + "foo" + ] } } } diff --git a/caddytest/integration/caddyfile_adapt/log_skip_hosts.caddyfiletest b/caddytest/integration/caddyfile_adapt/log_skip_hosts.caddyfiletest index 8fdd57156..b21af2b0a 100644 --- a/caddytest/integration/caddyfile_adapt/log_skip_hosts.caddyfiletest +++ b/caddytest/integration/caddyfile_adapt/log_skip_hosts.caddyfiletest @@ -62,8 +62,10 @@ example.com { } ], "logs": { - "logger_names": { - "one.example.com": "" + "logger_mapping": { + "one.example.com": [ + "" + ] }, "skip_hosts": [ "example.com", diff --git a/modules/caddyhttp/logging.go b/modules/caddyhttp/logging.go index 81b2830fc..0fc85ab90 100644 --- a/modules/caddyhttp/logging.go +++ b/modules/caddyhttp/logging.go @@ -32,22 +32,31 @@ import ( // customized per-request-host. type ServerLogConfig struct { // The default logger name for all logs emitted by this server for - // hostnames that are not in the LoggerNames (logger_names) map. + // hostnames that are not in logger_names or logger_mapping. DefaultLoggerName string `json:"default_logger_name,omitempty"` // LoggerNames maps request hostnames to a custom logger name. // For example, a mapping of "example.com" to "example" would // cause access logs from requests with a Host of example.com // to be emitted by a logger named "http.log.access.example". + // DEPRECATED: Use LoggerMapping instead. LoggerNames map[string]string `json:"logger_names,omitempty"` + // LoggerMapping maps request hostnames to one or more a custom + // logger name. For example, a mapping of "example.com" to "example" + // would cause access logs from requests with a Host of example.com + // to be emitted by a logger named "http.log.access.example". If + // there are multiple logger names, then the log will be emitted + // to all of them. + LoggerMapping map[string][]string `json:"logger_mapping,omitempty"` + // By default, all requests to this server will be logged if // access logging is enabled. This field lists the request // hosts for which access logging should be disabled. SkipHosts []string `json:"skip_hosts,omitempty"` - // If true, requests to any host not appearing in the - // LoggerNames (logger_names) map will not be logged. + // If true, requests to any host not appearing in + // logger_names or logger_mapping will not be logged. SkipUnmappedHosts bool `json:"skip_unmapped_hosts,omitempty"` // If true, credentials that are otherwise omitted, will be logged. @@ -57,33 +66,54 @@ type ServerLogConfig struct { ShouldLogCredentials bool `json:"should_log_credentials,omitempty"` } -// wrapLogger wraps logger in a logger named according to user preferences for the given host. -func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, host string) *zap.Logger { - if loggerName := slc.getLoggerName(host); loggerName != "" { - return logger.Named(loggerName) +// wrapLogger wraps logger in one or more logger named +// according to user preferences for the given host. +func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, host string) []*zap.Logger { + var loggers []*zap.Logger + for _, loggerName := range slc.getLoggerHosts(host) { + if loggerName == "" { + continue + } + loggers = append(loggers, logger.Named(loggerName)) } - return logger + return loggers } -func (slc ServerLogConfig) getLoggerName(host string) string { - tryHost := func(key string) (string, bool) { +func (slc ServerLogConfig) getLoggerHosts(host string) []string { + tryHost := func(key string) ([]string, bool) { // first try exact match - if loggerName, ok := slc.LoggerNames[key]; ok { - return loggerName, ok + if hosts, ok := slc.LoggerMapping[key]; ok { + return hosts, ok } // strip port and try again (i.e. Host header of "example.com:1234" should // match "example.com" if there is no "example.com:1234" in the map) hostOnly, _, err := net.SplitHostPort(key) if err != nil { - return "", false + return []string{""}, false + } + if hosts, ok := slc.LoggerMapping[hostOnly]; ok { + return hosts, ok } - loggerName, ok := slc.LoggerNames[hostOnly] - return loggerName, ok + + // Now try the deprecated LoggerNames + + // first try exact match + if host, ok := slc.LoggerNames[key]; ok { + return []string{host}, ok + } + // strip port and try again (i.e. Host header of "example.com:1234" should + // match "example.com" if there is no "example.com:1234" in the map) + hostOnly, _, err = net.SplitHostPort(key) + if err != nil { + return []string{""}, false + } + host, ok := slc.LoggerNames[hostOnly] + return []string{host}, ok } // try the exact hostname first - if loggerName, ok := tryHost(host); ok { - return loggerName + if hosts, ok := tryHost(host); ok { + return hosts } // try matching wildcard domains if other non-specific loggers exist @@ -94,18 +124,19 @@ func (slc ServerLogConfig) getLoggerName(host string) string { } labels[i] = "*" wildcardHost := strings.Join(labels, ".") - if loggerName, ok := tryHost(wildcardHost); ok { - return loggerName + if hosts, ok := tryHost(wildcardHost); ok { + return hosts } } - return slc.DefaultLoggerName + return []string{slc.DefaultLoggerName} } func (slc *ServerLogConfig) clone() *ServerLogConfig { clone := &ServerLogConfig{ DefaultLoggerName: slc.DefaultLoggerName, LoggerNames: make(map[string]string), + LoggerMapping: make(map[string][]string), SkipHosts: append([]string{}, slc.SkipHosts...), SkipUnmappedHosts: slc.SkipUnmappedHosts, ShouldLogCredentials: slc.ShouldLogCredentials, @@ -113,6 +144,9 @@ func (slc *ServerLogConfig) clone() *ServerLogConfig { for k, v := range slc.LoggerNames { clone.LoggerNames[k] = v } + for k, v := range slc.LoggerMapping { + clone.LoggerMapping[k] = append([]string{}, v...) + } return clone } diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index c7e5a5f61..7c68a1740 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -361,11 +361,11 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { cloneURL(origReq.URL, r.URL) // prepare the error log - logger := errLog + errLog = errLog.With(zap.Duration("duration", duration)) + errLoggers := []*zap.Logger{errLog} if s.Logs != nil { - logger = s.Logs.wrapLogger(logger, r.Host) + errLoggers = s.Logs.wrapLogger(errLog, r.Host) } - logger = logger.With(zap.Duration("duration", duration)) // get the values that will be used to log the error errStatus, errMsg, errFields := errLogValues(err) @@ -379,7 +379,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err2 == nil { // user's error route handled the error response // successfully, so now just log the error - logger.Debug(errMsg, errFields...) + for _, logger := range errLoggers { + logger.Debug(errMsg, errFields...) + } } else { // well... this is awkward errFields = append([]zapcore.Field{ @@ -387,7 +389,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { zap.Namespace("first_error"), zap.String("msg", errMsg), }, errFields...) - logger.Error("error handling handler error", errFields...) + for _, logger := range errLoggers { + logger.Error("error handling handler error", errFields...) + } if handlerErr, ok := err.(HandlerError); ok { w.WriteHeader(handlerErr.StatusCode) } else { @@ -395,10 +399,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } } else { - if errStatus >= 500 { - logger.Error(errMsg, errFields...) - } else { - logger.Debug(errMsg, errFields...) + for _, logger := range errLoggers { + if errStatus >= 500 { + logger.Error(errMsg, errFields...) + } else { + logger.Debug(errMsg, errFields...) + } } w.WriteHeader(errStatus) } @@ -706,6 +712,10 @@ func (s *Server) shouldLogRequest(r *http.Request) bool { // logging is disabled return false } + if _, ok := s.Logs.LoggerMapping[r.Host]; ok { + // this host is mapped to a particular logger name + return true + } if _, ok := s.Logs.LoggerNames[r.Host]; ok { // this host is mapped to a particular logger name return true @@ -735,16 +745,6 @@ func (s *Server) logRequest( repl.Set("http.response.duration", duration) repl.Set("http.response.duration_ms", duration.Seconds()*1e3) // multiply seconds to preserve decimal (see #4666) - logger := accLog - if s.Logs != nil { - logger = s.Logs.wrapLogger(logger, r.Host) - } - - log := logger.Info - if wrec.Status() >= 400 { - log = logger.Error - } - userID, _ := repl.GetString("http.auth.user.id") reqBodyLength := 0 @@ -768,7 +768,20 @@ func (s *Server) logRequest( })) fields = append(fields, extra.fields...) - log("handled request", fields...) + loggers := []*zap.Logger{accLog} + if s.Logs != nil { + loggers = s.Logs.wrapLogger(accLog, r.Host) + } + + // wrapping may return multiple loggers, so we log to all of them + for _, logger := range loggers { + logAtLevel := logger.Info + if wrec.Status() >= 400 { + logAtLevel = logger.Error + } + + logAtLevel("handled request", fields...) + } } // protocol returns true if the protocol proto is configured/enabled. |