mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-14 23:06:27 +03:00
caddyhttp: General improvements to access logging (#3301)
* httpcaddyfile: Exclude access logs written to files from default log Even though any logs can just be ignored, most users don't seem to like configuring an access log to go to a file only to have it doubly appear in the default log. Related to: - #3294 - https://caddy.community/t/v2-logging-format/7642/4?u=matt - https://caddy.community/t/caddyfile-questions/7651/3?u=matt * caddyhttp: General improvements to access log controls (fixes #3310) * caddyhttp: Move log config nil check higher * Rename LoggerName -> DefaultLoggerName
This commit is contained in:
parent
c11d0e47a3
commit
10db57027d
2 changed files with 106 additions and 34 deletions
|
@ -38,7 +38,7 @@ type ServerType struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup makes a config from the tokens.
|
// Setup makes a config from the tokens.
|
||||||
func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
||||||
options map[string]interface{}) (*caddy.Config, []caddyconfig.Warning, error) {
|
options map[string]interface{}) (*caddy.Config, []caddyconfig.Warning, error) {
|
||||||
var warnings []caddyconfig.Warning
|
var warnings []caddyconfig.Warning
|
||||||
gc := counter{new(int)}
|
gc := counter{new(int)}
|
||||||
|
@ -50,15 +50,15 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
||||||
// chosen to handle a request - we actually will make each
|
// chosen to handle a request - we actually will make each
|
||||||
// server block's route terminal so that only one will run
|
// server block's route terminal so that only one will run
|
||||||
sbKeys := make(map[string]struct{})
|
sbKeys := make(map[string]struct{})
|
||||||
serverBlocks := make([]serverBlock, 0, len(originalServerBlocks))
|
originalServerBlocks := make([]serverBlock, 0, len(inputServerBlocks))
|
||||||
for i, sblock := range originalServerBlocks {
|
for i, sblock := range inputServerBlocks {
|
||||||
for j, k := range sblock.Keys {
|
for j, k := range sblock.Keys {
|
||||||
if _, ok := sbKeys[k]; ok {
|
if _, ok := sbKeys[k]; ok {
|
||||||
return nil, warnings, fmt.Errorf("duplicate site address not allowed: '%s' in %v (site block %d, key %d)", k, sblock.Keys, i, j)
|
return nil, warnings, fmt.Errorf("duplicate site address not allowed: '%s' in %v (site block %d, key %d)", k, sblock.Keys, i, j)
|
||||||
}
|
}
|
||||||
sbKeys[k] = struct{}{}
|
sbKeys[k] = struct{}{}
|
||||||
}
|
}
|
||||||
serverBlocks = append(serverBlocks, serverBlock{
|
originalServerBlocks = append(originalServerBlocks, serverBlock{
|
||||||
block: sblock,
|
block: sblock,
|
||||||
pile: make(map[string][]ConfigValue),
|
pile: make(map[string][]ConfigValue),
|
||||||
})
|
})
|
||||||
|
@ -66,12 +66,12 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
||||||
|
|
||||||
// apply any global options
|
// apply any global options
|
||||||
var err error
|
var err error
|
||||||
serverBlocks, err = st.evaluateGlobalOptionsBlock(serverBlocks, options)
|
originalServerBlocks, err = st.evaluateGlobalOptionsBlock(originalServerBlocks, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, warnings, err
|
return nil, warnings, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sb := range serverBlocks {
|
for _, sb := range originalServerBlocks {
|
||||||
// replace shorthand placeholders (which are
|
// replace shorthand placeholders (which are
|
||||||
// convenient when writing a Caddyfile) with
|
// convenient when writing a Caddyfile) with
|
||||||
// their actual placeholder identifiers or
|
// their actual placeholder identifiers or
|
||||||
|
@ -155,7 +155,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
||||||
}
|
}
|
||||||
|
|
||||||
// map
|
// map
|
||||||
sbmap, err := st.mapAddressToServerBlocks(serverBlocks, options)
|
sbmap, err := st.mapAddressToServerBlocks(originalServerBlocks, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, warnings, err
|
return nil, warnings, err
|
||||||
}
|
}
|
||||||
|
@ -193,21 +193,24 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
||||||
// extract any custom logs, and enforce configured levels
|
// extract any custom logs, and enforce configured levels
|
||||||
var customLogs []namedCustomLog
|
var customLogs []namedCustomLog
|
||||||
var hasDefaultLog bool
|
var hasDefaultLog bool
|
||||||
for _, sb := range serverBlocks {
|
for _, p := range pairings {
|
||||||
for _, clVal := range sb.pile["custom_log"] {
|
for _, sb := range p.serverBlocks {
|
||||||
ncl := clVal.Value.(namedCustomLog)
|
for _, clVal := range sb.pile["custom_log"] {
|
||||||
if ncl.name == "" {
|
ncl := clVal.Value.(namedCustomLog)
|
||||||
continue
|
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 ncl.name == "default" {
|
|
||||||
hasDefaultLog = true
|
|
||||||
}
|
|
||||||
if _, ok := options["debug"]; ok && ncl.log.Level == "" {
|
|
||||||
ncl.log.Level = "DEBUG"
|
|
||||||
}
|
|
||||||
customLogs = append(customLogs, ncl)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasDefaultLog {
|
if !hasDefaultLog {
|
||||||
// if the default log was not customized, ensure we
|
// if the default log was not customized, ensure we
|
||||||
// configure it with any applicable options
|
// configure it with any applicable options
|
||||||
|
@ -250,6 +253,17 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
||||||
if ncl.name != "" {
|
if ncl.name != "" {
|
||||||
cfg.Logging.Logs[ncl.name] = ncl.log
|
cfg.Logging.Logs[ncl.name] = ncl.log
|
||||||
}
|
}
|
||||||
|
// most users seem to prefer not writing access logs
|
||||||
|
// to the default log when they are directed to a
|
||||||
|
// file or have any other special customization
|
||||||
|
if len(ncl.log.Include) > 0 {
|
||||||
|
defaultLog, ok := cfg.Logging.Logs["default"]
|
||||||
|
if !ok {
|
||||||
|
defaultLog = new(caddy.CustomLog)
|
||||||
|
cfg.Logging.Logs["default"] = defaultLog
|
||||||
|
}
|
||||||
|
defaultLog.Exclude = append(defaultLog.Exclude, ncl.log.Include...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,23 +465,46 @@ func (st *ServerType) serversFromPairings(
|
||||||
}
|
}
|
||||||
|
|
||||||
// add log associations
|
// add log associations
|
||||||
|
// see https://github.com/caddyserver/caddy/issues/3310
|
||||||
|
sblockLogHosts := sblock.hostsFromKeys(true)
|
||||||
for _, cval := range sblock.pile["custom_log"] {
|
for _, cval := range sblock.pile["custom_log"] {
|
||||||
ncl := cval.Value.(namedCustomLog)
|
ncl := cval.Value.(namedCustomLog)
|
||||||
if srv.Logs == nil {
|
if srv.Logs == nil {
|
||||||
srv.Logs = &caddyhttp.ServerLogConfig{
|
srv.Logs = new(caddyhttp.ServerLogConfig)
|
||||||
LoggerNames: make(map[string]string),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if sblock.hasHostCatchAllKey() {
|
if sblock.hasHostCatchAllKey() {
|
||||||
srv.Logs.LoggerName = ncl.name
|
// all requests for hosts not able to be listed should use
|
||||||
|
// this log because it's a catch-all-hosts server block
|
||||||
|
srv.Logs.DefaultLoggerName = ncl.name
|
||||||
} else {
|
} else {
|
||||||
for _, h := range sblock.hostsFromKeys(true) {
|
// map each host to the user's desired logger name
|
||||||
if ncl.name != "" {
|
for _, h := range sblockLogHosts {
|
||||||
|
// if the custom logger name is non-empty, add it to
|
||||||
|
// the map; otherwise, only map to an empty logger
|
||||||
|
// name if the server block has a catch-all host (in
|
||||||
|
// which case only requests with mapped hostnames will
|
||||||
|
// be access-logged, so it'll be necessary to add them
|
||||||
|
// to the map even if they use default logger)
|
||||||
|
if ncl.name != "" || len(hosts) == 0 {
|
||||||
|
if srv.Logs.LoggerNames == nil {
|
||||||
|
srv.Logs.LoggerNames = make(map[string]string)
|
||||||
|
}
|
||||||
srv.Logs.LoggerNames[h] = ncl.name
|
srv.Logs.LoggerNames[h] = ncl.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if srv.Logs != nil && len(sblock.pile["custom_log"]) == 0 {
|
||||||
|
// server has access logs enabled, but this server block does not
|
||||||
|
// enable access logs; therefore, all hosts of this server block
|
||||||
|
// should not be access-logged
|
||||||
|
if len(hosts) == 0 {
|
||||||
|
// if the server block has a catch-all-hosts key, then we should
|
||||||
|
// not log reqs to any host unless it appears in the map
|
||||||
|
srv.Logs.SkipUnmappedHosts = true
|
||||||
|
}
|
||||||
|
srv.Logs.SkipHosts = append(srv.Logs.SkipHosts, sblockLogHosts...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// a server cannot (natively) serve both HTTP and HTTPS at the
|
// a server cannot (natively) serve both HTTP and HTTPS at the
|
||||||
|
|
|
@ -101,9 +101,9 @@ type Server struct {
|
||||||
// client authentication.
|
// client authentication.
|
||||||
StrictSNIHost *bool `json:"strict_sni_host,omitempty"`
|
StrictSNIHost *bool `json:"strict_sni_host,omitempty"`
|
||||||
|
|
||||||
// Customizes how access logs are handled in this server. To
|
// Enables access logging and configures how access logs are handled
|
||||||
// minimally enable access logs, simply set this to a non-null,
|
// in this server. To minimally enable access logs, simply set this
|
||||||
// empty struct.
|
// to a non-null, empty struct.
|
||||||
Logs *ServerLogConfig `json:"logs,omitempty"`
|
Logs *ServerLogConfig `json:"logs,omitempty"`
|
||||||
|
|
||||||
// Enable experimental HTTP/3 support. Note that HTTP/3 is not a
|
// Enable experimental HTTP/3 support. Note that HTTP/3 is not a
|
||||||
|
@ -157,7 +157,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
loggableReq,
|
loggableReq,
|
||||||
)
|
)
|
||||||
|
|
||||||
if s.accessLogger != nil {
|
if s.shouldLogRequest(r) {
|
||||||
wrec := NewResponseRecorder(w, nil, nil)
|
wrec := NewResponseRecorder(w, nil, nil)
|
||||||
w = wrec
|
w = wrec
|
||||||
|
|
||||||
|
@ -390,17 +390,52 @@ func (*HTTPErrorConfig) WithError(r *http.Request, err error) *http.Request {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerLogConfig describes a server's logging configuration.
|
// shouldLogRequest returns true if this request should be logged.
|
||||||
|
func (s *Server) shouldLogRequest(r *http.Request) bool {
|
||||||
|
if s.accessLogger == nil || s.Logs == nil {
|
||||||
|
// logging is disabled
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, dh := range s.Logs.SkipHosts {
|
||||||
|
// logging for this particular host is disabled
|
||||||
|
if r.Host == dh {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := s.Logs.LoggerNames[r.Host]; ok {
|
||||||
|
// this host is mapped to a particular logger name
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if s.Logs.SkipUnmappedHosts {
|
||||||
|
// this host is not mapped and thus must not be logged
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerLogConfig describes a server's logging configuration. If
|
||||||
|
// enabled without customization, all requests to this server are
|
||||||
|
// logged to the default logger; logger destinations may be
|
||||||
|
// customized per-request-host.
|
||||||
type ServerLogConfig struct {
|
type ServerLogConfig struct {
|
||||||
// The logger name for all logs emitted by this server unless
|
// The default logger name for all logs emitted by this server for
|
||||||
// the hostname is found in the LoggerNames (logger_names) map.
|
// hostnames that are not in the LoggerNames (logger_names) map.
|
||||||
LoggerName string `json:"log_name,omitempty"`
|
DefaultLoggerName string `json:"default_logger_name,omitempty"`
|
||||||
|
|
||||||
// LoggerNames maps request hostnames to a custom logger name.
|
// LoggerNames maps request hostnames to a custom logger name.
|
||||||
// For example, a mapping of "example.com" to "example" would
|
// For example, a mapping of "example.com" to "example" would
|
||||||
// cause access logs from requests with a Host of example.com
|
// cause access logs from requests with a Host of example.com
|
||||||
// to be emitted by a logger named "http.log.access.example".
|
// to be emitted by a logger named "http.log.access.example".
|
||||||
LoggerNames map[string]string `json:"logger_names,omitempty"`
|
LoggerNames map[string]string `json:"logger_names,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.
|
||||||
|
SkipUnmappedHosts bool `json:"skip_unmapped_hosts,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrapLogger wraps logger in a logger named according to user preferences for the given host.
|
// wrapLogger wraps logger in a logger named according to user preferences for the given host.
|
||||||
|
@ -415,7 +450,7 @@ func (slc ServerLogConfig) getLoggerName(host string) string {
|
||||||
if loggerName, ok := slc.LoggerNames[host]; ok {
|
if loggerName, ok := slc.LoggerNames[host]; ok {
|
||||||
return loggerName
|
return loggerName
|
||||||
}
|
}
|
||||||
return slc.LoggerName
|
return slc.DefaultLoggerName
|
||||||
}
|
}
|
||||||
|
|
||||||
// errLogValues inspects err and returns the status code
|
// errLogValues inspects err and returns the status code
|
||||||
|
|
Loading…
Reference in a new issue