Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

logging: Add support for additional logger filters other than hostname #6082

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 19 additions & 2 deletions caddyconfig/httpcaddyfile/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func init() {
RegisterDirective("log", parseLog)
RegisterHandlerDirective("skip_log", parseLogSkip)
RegisterHandlerDirective("log_skip", parseLogSkip)
RegisterHandlerDirective("log_name", parseLogName)
}

// parseBind parses the bind directive. Syntax:
Expand Down Expand Up @@ -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
// with a wildcard domain
customHostnames := []string{}

noHostname := false
for h.NextBlock(0) {
switch h.Val() {
case "hostnames":
Expand Down Expand Up @@ -1000,14 +1001,20 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
cl.Exclude = append(cl.Exclude, h.Val())
}

case "no_hostname":
armadi1809 marked this conversation as resolved.
Show resolved Hide resolved
if h.NextArg() {
return nil, h.ArgErr()
}
noHostname = true

default:
return nil, h.Errf("unrecognized subdirective: %s", h.Val())
}
}

var val namedCustomLog
val.hostnames = customHostnames

val.noHostname = noHostname
isEmptyConfig := reflect.DeepEqual(cl, new(caddy.CustomLog))

// Skip handling of empty logging configs
Expand Down Expand Up @@ -1058,3 +1065,13 @@ func parseLogSkip(h Helper) (caddyhttp.MiddlewareHandler, error) {
}
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
}
1 change: 1 addition & 0 deletions caddyconfig/httpcaddyfile/directives.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ var defaultDirectiveOrder = []string{
"log_append",
"skip_log", // TODO: deprecated, renamed to log_skip
"log_skip",
"log_name",

"header",
"copy_response_headers", // only in reverse_proxy's handle_response
Expand Down
10 changes: 7 additions & 3 deletions caddyconfig/httpcaddyfile/httptype.go
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,9 @@ func (st *ServerType) serversFromPairings(
sblockLogHosts := sblock.hostsFromKeys(true)
for _, cval := range sblock.pile["custom_log"] {
ncl := cval.Value.(namedCustomLog)
if ncl.noHostname {
continue
}
if sblock.hasHostCatchAllKey() && len(ncl.hostnames) == 0 {
// all requests for hosts not able to be listed should use
// this log because it's a catch-all-hosts server block
Expand Down Expand Up @@ -1598,9 +1601,10 @@ func (c counter) nextGroup() string {
}

type namedCustomLog struct {
name string
hostnames []string
log *caddy.CustomLog
name string
hostnames []string
log *caddy.CustomLog
noHostname bool
}

// sbAddrAssociation is a mapping from a list of
Expand Down
151 changes: 151 additions & 0 deletions caddytest/integration/caddyfile_adapt/log_filter_with_header.txt
Original file line number Diff line number Diff line change
@@ -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"
],
francislavoie marked this conversation as resolved.
Show resolved Hide resolved
"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"
]
}
}
}
}
}
}
}
36 changes: 30 additions & 6 deletions modules/caddyhttp/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,22 +69,43 @@ type ServerLogConfig struct {

// 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 {
// logger config should always be only
// the hostname, without the port
hostWithoutPort, _, err := net.SplitHostPort(host)
func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, req *http.Request) []*zap.Logger {
// using the `log_name` directive or the `access_logger_names` variable,
// the logger names can be overridden for the current request
if names := GetVar(req.Context(), AccessLoggerNameVarKey); names != nil {
if namesSlice, ok := names.([]any); ok {
francislavoie marked this conversation as resolved.
Show resolved Hide resolved
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
}
}

// get the hostname from the request, with the port number stripped
host, _, err := net.SplitHostPort(req.Host)
if err != nil {
hostWithoutPort = host
host = req.Host
}

hosts := slc.getLoggerHosts(hostWithoutPort)
// 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))
for _, loggerName := range hosts {
// 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))
}
return loggers
Expand Down Expand Up @@ -211,4 +232,7 @@ const (

// For adding additional fields to the access logs
ExtraLogFieldsCtxKey caddy.CtxKey = "extra_log_fields"

// Variable name used to indicate the logger to be used
AccessLoggerNameVarKey string = "access_logger_names"
)
5 changes: 2 additions & 3 deletions modules/caddyhttp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
errLog = errLog.With(zap.Duration("duration", duration))
errLoggers := []*zap.Logger{errLog}
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
Expand Down Expand Up @@ -778,7 +778,7 @@ func (s *Server) logRequest(

loggers := []*zap.Logger{accLog}
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
Expand Down Expand Up @@ -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, ExtraLogFieldsCtxKey, new(ExtraLogFields))

r = r.WithContext(ctx)

// once the pointer to the request won't change
Expand Down