mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-26 13:43:47 +03:00
logging: Add support for additional logger filters other than hostname (#6082)
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
This commit is contained in:
parent
4af38e5ac8
commit
4356635d12
6 changed files with 217 additions and 15 deletions
|
@ -51,6 +51,7 @@ func init() {
|
||||||
RegisterDirective("log", parseLog)
|
RegisterDirective("log", parseLog)
|
||||||
RegisterHandlerDirective("skip_log", parseLogSkip)
|
RegisterHandlerDirective("skip_log", parseLogSkip)
|
||||||
RegisterHandlerDirective("log_skip", parseLogSkip)
|
RegisterHandlerDirective("log_skip", parseLogSkip)
|
||||||
|
RegisterHandlerDirective("log_name", parseLogName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseBind parses the bind directive. Syntax:
|
// parseBind parses the bind directive. Syntax:
|
||||||
|
@ -914,7 +915,7 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||||
// this is useful for setting up loggers per subdomain in a site block
|
// this is useful for setting up loggers per subdomain in a site block
|
||||||
// with a wildcard domain
|
// with a wildcard domain
|
||||||
customHostnames := []string{}
|
customHostnames := []string{}
|
||||||
|
noHostname := false
|
||||||
for h.NextBlock(0) {
|
for h.NextBlock(0) {
|
||||||
switch h.Val() {
|
switch h.Val() {
|
||||||
case "hostnames":
|
case "hostnames":
|
||||||
|
@ -1000,6 +1001,12 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||||
cl.Exclude = append(cl.Exclude, h.Val())
|
cl.Exclude = append(cl.Exclude, h.Val())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "no_hostname":
|
||||||
|
if h.NextArg() {
|
||||||
|
return nil, h.ArgErr()
|
||||||
|
}
|
||||||
|
noHostname = true
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, h.Errf("unrecognized subdirective: %s", h.Val())
|
return nil, h.Errf("unrecognized subdirective: %s", h.Val())
|
||||||
}
|
}
|
||||||
|
@ -1007,7 +1014,7 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||||
|
|
||||||
var val namedCustomLog
|
var val namedCustomLog
|
||||||
val.hostnames = customHostnames
|
val.hostnames = customHostnames
|
||||||
|
val.noHostname = noHostname
|
||||||
isEmptyConfig := reflect.DeepEqual(cl, new(caddy.CustomLog))
|
isEmptyConfig := reflect.DeepEqual(cl, new(caddy.CustomLog))
|
||||||
|
|
||||||
// Skip handling of empty logging configs
|
// Skip handling of empty logging configs
|
||||||
|
@ -1058,3 +1065,13 @@ func parseLogSkip(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
}
|
}
|
||||||
return caddyhttp.VarsMiddleware{"log_skip": true}, nil
|
return caddyhttp.VarsMiddleware{"log_skip": true}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseLogName parses the log_name directive. Syntax:
|
||||||
|
//
|
||||||
|
// log_name <names...>
|
||||||
|
func parseLogName(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
|
h.Next() // consume directive name
|
||||||
|
return caddyhttp.VarsMiddleware{
|
||||||
|
caddyhttp.AccessLoggerNameVarKey: h.RemainingArgs(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ var defaultDirectiveOrder = []string{
|
||||||
"log_append",
|
"log_append",
|
||||||
"skip_log", // TODO: deprecated, renamed to log_skip
|
"skip_log", // TODO: deprecated, renamed to log_skip
|
||||||
"log_skip",
|
"log_skip",
|
||||||
|
"log_name",
|
||||||
|
|
||||||
"header",
|
"header",
|
||||||
"copy_response_headers", // only in reverse_proxy's handle_response
|
"copy_response_headers", // only in reverse_proxy's handle_response
|
||||||
|
|
|
@ -797,6 +797,15 @@ func (st *ServerType) serversFromPairings(
|
||||||
sblockLogHosts := sblock.hostsFromKeys(true)
|
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 `no_hostname` is set, then this logger will not
|
||||||
|
// be associated with any of the site block's hostnames,
|
||||||
|
// and only be usable via the `log_name` directive
|
||||||
|
// or the `access_logger_names` variable
|
||||||
|
if ncl.noHostname {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if sblock.hasHostCatchAllKey() && len(ncl.hostnames) == 0 {
|
if sblock.hasHostCatchAllKey() && len(ncl.hostnames) == 0 {
|
||||||
// all requests for hosts not able to be listed should use
|
// all requests for hosts not able to be listed should use
|
||||||
// this log because it's a catch-all-hosts server block
|
// this log because it's a catch-all-hosts server block
|
||||||
|
@ -1598,9 +1607,10 @@ func (c counter) nextGroup() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type namedCustomLog struct {
|
type namedCustomLog struct {
|
||||||
name string
|
name string
|
||||||
hostnames []string
|
hostnames []string
|
||||||
log *caddy.CustomLog
|
log *caddy.CustomLog
|
||||||
|
noHostname bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// sbAddrAssociation is a mapping from a list of
|
// sbAddrAssociation is a mapping from a list of
|
||||||
|
|
151
caddytest/integration/caddyfile_adapt/log_filter_with_header.txt
Normal file
151
caddytest/integration/caddyfile_adapt/log_filter_with_header.txt
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
localhost {
|
||||||
|
log {
|
||||||
|
output file ./caddy.access.log
|
||||||
|
}
|
||||||
|
log health_check_log {
|
||||||
|
output file ./caddy.access.health.log
|
||||||
|
no_hostname
|
||||||
|
}
|
||||||
|
log general_log {
|
||||||
|
output file ./caddy.access.general.log
|
||||||
|
no_hostname
|
||||||
|
}
|
||||||
|
@healthCheck `header_regexp('User-Agent', '^some-regexp$') || path('/healthz*')`
|
||||||
|
handle @healthCheck {
|
||||||
|
log_name health_check_log general_log
|
||||||
|
respond "Healthy"
|
||||||
|
}
|
||||||
|
|
||||||
|
handle {
|
||||||
|
respond "Hello World"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"logging": {
|
||||||
|
"logs": {
|
||||||
|
"default": {
|
||||||
|
"exclude": [
|
||||||
|
"http.log.access.general_log",
|
||||||
|
"http.log.access.health_check_log",
|
||||||
|
"http.log.access.log0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"general_log": {
|
||||||
|
"writer": {
|
||||||
|
"filename": "./caddy.access.general.log",
|
||||||
|
"output": "file"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"http.log.access.general_log"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"health_check_log": {
|
||||||
|
"writer": {
|
||||||
|
"filename": "./caddy.access.health.log",
|
||||||
|
"output": "file"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"http.log.access.health_check_log"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"log0": {
|
||||||
|
"writer": {
|
||||||
|
"filename": "./caddy.access.log",
|
||||||
|
"output": "file"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"http.log.access.log0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"group": "group2",
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"access_logger_names": [
|
||||||
|
"health_check_log",
|
||||||
|
"general_log"
|
||||||
|
],
|
||||||
|
"handler": "vars"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"body": "Healthy",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"expression": {
|
||||||
|
"expr": "header_regexp('User-Agent', '^some-regexp$') || path('/healthz*')",
|
||||||
|
"name": "healthCheck"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "group2",
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Hello World",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"logs": {
|
||||||
|
"logger_names": {
|
||||||
|
"localhost": [
|
||||||
|
"log0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,15 +69,35 @@ type ServerLogConfig struct {
|
||||||
|
|
||||||
// wrapLogger wraps logger in one or more logger named
|
// wrapLogger wraps logger in one or more logger named
|
||||||
// according to user preferences for the given host.
|
// according to user preferences for the given host.
|
||||||
func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, host string) []*zap.Logger {
|
func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, req *http.Request) []*zap.Logger {
|
||||||
// logger config should always be only
|
// using the `log_name` directive or the `access_logger_names` variable,
|
||||||
// the hostname, without the port
|
// the logger names can be overridden for the current request
|
||||||
hostWithoutPort, _, err := net.SplitHostPort(host)
|
if names := GetVar(req.Context(), AccessLoggerNameVarKey); names != nil {
|
||||||
if err != nil {
|
if namesSlice, ok := names.([]any); ok {
|
||||||
hostWithoutPort = host
|
loggers := make([]*zap.Logger, 0, len(namesSlice))
|
||||||
|
for _, loggerName := range namesSlice {
|
||||||
|
// no name, use the default logger
|
||||||
|
if loggerName == "" {
|
||||||
|
loggers = append(loggers, logger)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// make a logger with the given name
|
||||||
|
loggers = append(loggers, logger.Named(loggerName.(string)))
|
||||||
|
}
|
||||||
|
return loggers
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hosts := slc.getLoggerHosts(hostWithoutPort)
|
// get the hostname from the request, with the port number stripped
|
||||||
|
host, _, err := net.SplitHostPort(req.Host)
|
||||||
|
if err != nil {
|
||||||
|
host = req.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the logger names for this host from the config
|
||||||
|
hosts := slc.getLoggerHosts(host)
|
||||||
|
|
||||||
|
// make a list of named loggers, or the default logger
|
||||||
loggers := make([]*zap.Logger, 0, len(hosts))
|
loggers := make([]*zap.Logger, 0, len(hosts))
|
||||||
for _, loggerName := range hosts {
|
for _, loggerName := range hosts {
|
||||||
// no name, use the default logger
|
// no name, use the default logger
|
||||||
|
@ -85,6 +105,7 @@ func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, host string) []*zap.Lo
|
||||||
loggers = append(loggers, logger)
|
loggers = append(loggers, logger)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// make a logger with the given name
|
||||||
loggers = append(loggers, logger.Named(loggerName))
|
loggers = append(loggers, logger.Named(loggerName))
|
||||||
}
|
}
|
||||||
return loggers
|
return loggers
|
||||||
|
@ -211,4 +232,7 @@ const (
|
||||||
|
|
||||||
// For adding additional fields to the access logs
|
// For adding additional fields to the access logs
|
||||||
ExtraLogFieldsCtxKey caddy.CtxKey = "extra_log_fields"
|
ExtraLogFieldsCtxKey caddy.CtxKey = "extra_log_fields"
|
||||||
|
|
||||||
|
// Variable name used to indicate the logger to be used
|
||||||
|
AccessLoggerNameVarKey string = "access_logger_names"
|
||||||
)
|
)
|
||||||
|
|
|
@ -369,7 +369,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
errLog = errLog.With(zap.Duration("duration", duration))
|
errLog = errLog.With(zap.Duration("duration", duration))
|
||||||
errLoggers := []*zap.Logger{errLog}
|
errLoggers := []*zap.Logger{errLog}
|
||||||
if s.Logs != nil {
|
if s.Logs != nil {
|
||||||
errLoggers = s.Logs.wrapLogger(errLog, r.Host)
|
errLoggers = s.Logs.wrapLogger(errLog, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the values that will be used to log the error
|
// get the values that will be used to log the error
|
||||||
|
@ -778,7 +778,7 @@ func (s *Server) logRequest(
|
||||||
|
|
||||||
loggers := []*zap.Logger{accLog}
|
loggers := []*zap.Logger{accLog}
|
||||||
if s.Logs != nil {
|
if s.Logs != nil {
|
||||||
loggers = s.Logs.wrapLogger(accLog, r.Host)
|
loggers = s.Logs.wrapLogger(accLog, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrapping may return multiple loggers, so we log to all of them
|
// wrapping may return multiple loggers, so we log to all of them
|
||||||
|
@ -835,7 +835,6 @@ func PrepareRequest(r *http.Request, repl *caddy.Replacer, w http.ResponseWriter
|
||||||
ctx = context.WithValue(ctx, OriginalRequestCtxKey, originalRequest(r, &url2))
|
ctx = context.WithValue(ctx, OriginalRequestCtxKey, originalRequest(r, &url2))
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, ExtraLogFieldsCtxKey, new(ExtraLogFields))
|
ctx = context.WithValue(ctx, ExtraLogFieldsCtxKey, new(ExtraLogFields))
|
||||||
|
|
||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
// once the pointer to the request won't change
|
// once the pointer to the request won't change
|
||||||
|
|
Loading…
Reference in a new issue