mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 22:23:48 +03:00
httpcaddyfile: Allow hostnames
& logger name overrides for log directive (#5643)
* httpcaddyfile: Allow `hostnames` override for log directive * Implement access logger name overrides * Fix panic & default logger clobbering edgecase
This commit is contained in:
parent
da23501457
commit
5c51c1db2c
6 changed files with 342 additions and 29 deletions
|
@ -788,7 +788,8 @@ func parseInvoke(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
|
|
||||||
// parseLog parses the log directive. Syntax:
|
// parseLog parses the log directive. Syntax:
|
||||||
//
|
//
|
||||||
// log {
|
// log <logger_name> {
|
||||||
|
// hostnames <hostnames...>
|
||||||
// output <writer_module> ...
|
// output <writer_module> ...
|
||||||
// format <encoder_module> ...
|
// format <encoder_module> ...
|
||||||
// level <level>
|
// level <level>
|
||||||
|
@ -809,11 +810,13 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||||
var configValues []ConfigValue
|
var configValues []ConfigValue
|
||||||
for h.Next() {
|
for h.Next() {
|
||||||
// Logic below expects that a name is always present when a
|
// Logic below expects that a name is always present when a
|
||||||
// global option is being parsed.
|
// global option is being parsed; or an optional override
|
||||||
var globalLogName string
|
// is supported for access logs.
|
||||||
|
var logName string
|
||||||
|
|
||||||
if parseAsGlobalOption {
|
if parseAsGlobalOption {
|
||||||
if h.NextArg() {
|
if h.NextArg() {
|
||||||
globalLogName = h.Val()
|
logName = h.Val()
|
||||||
|
|
||||||
// Only a single argument is supported.
|
// Only a single argument is supported.
|
||||||
if h.NextArg() {
|
if h.NextArg() {
|
||||||
|
@ -824,26 +827,47 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||||
// reference the default logger. See the
|
// reference the default logger. See the
|
||||||
// setupNewDefault function in the logging
|
// setupNewDefault function in the logging
|
||||||
// package for where this is configured.
|
// package for where this is configured.
|
||||||
globalLogName = caddy.DefaultLoggerName
|
logName = caddy.DefaultLoggerName
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify this name is unused.
|
// Verify this name is unused.
|
||||||
_, used := globalLogNames[globalLogName]
|
_, used := globalLogNames[logName]
|
||||||
if used {
|
if used {
|
||||||
return nil, h.Err("duplicate global log option for: " + globalLogName)
|
return nil, h.Err("duplicate global log option for: " + logName)
|
||||||
}
|
}
|
||||||
globalLogNames[globalLogName] = struct{}{}
|
globalLogNames[logName] = struct{}{}
|
||||||
} else {
|
} else {
|
||||||
// No arguments are supported for the server block log directive
|
// An optional override of the logger name can be provided;
|
||||||
|
// otherwise a default will be used, like "log0", "log1", etc.
|
||||||
if h.NextArg() {
|
if h.NextArg() {
|
||||||
return nil, h.ArgErr()
|
logName = h.Val()
|
||||||
|
|
||||||
|
// Only a single argument is supported.
|
||||||
|
if h.NextArg() {
|
||||||
|
return nil, h.ArgErr()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cl := new(caddy.CustomLog)
|
cl := new(caddy.CustomLog)
|
||||||
|
|
||||||
|
// allow overriding the current site block's hostnames for this logger;
|
||||||
|
// this is useful for setting up loggers per subdomain in a site block
|
||||||
|
// with a wildcard domain
|
||||||
|
customHostnames := []string{}
|
||||||
|
|
||||||
for h.NextBlock(0) {
|
for h.NextBlock(0) {
|
||||||
switch h.Val() {
|
switch h.Val() {
|
||||||
|
case "hostnames":
|
||||||
|
if parseAsGlobalOption {
|
||||||
|
return nil, h.Err("hostnames is not allowed in the log global options")
|
||||||
|
}
|
||||||
|
args := h.RemainingArgs()
|
||||||
|
if len(args) == 0 {
|
||||||
|
return nil, h.ArgErr()
|
||||||
|
}
|
||||||
|
customHostnames = append(customHostnames, args...)
|
||||||
|
|
||||||
case "output":
|
case "output":
|
||||||
if !h.NextArg() {
|
if !h.NextArg() {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
|
@ -902,18 +926,16 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||||
}
|
}
|
||||||
|
|
||||||
case "include":
|
case "include":
|
||||||
// This configuration is only allowed in the global options
|
|
||||||
if !parseAsGlobalOption {
|
if !parseAsGlobalOption {
|
||||||
return nil, h.ArgErr()
|
return nil, h.Err("include is not allowed in the log directive")
|
||||||
}
|
}
|
||||||
for h.NextArg() {
|
for h.NextArg() {
|
||||||
cl.Include = append(cl.Include, h.Val())
|
cl.Include = append(cl.Include, h.Val())
|
||||||
}
|
}
|
||||||
|
|
||||||
case "exclude":
|
case "exclude":
|
||||||
// This configuration is only allowed in the global options
|
|
||||||
if !parseAsGlobalOption {
|
if !parseAsGlobalOption {
|
||||||
return nil, h.ArgErr()
|
return nil, h.Err("exclude is not allowed in the log directive")
|
||||||
}
|
}
|
||||||
for h.NextArg() {
|
for h.NextArg() {
|
||||||
cl.Exclude = append(cl.Exclude, h.Val())
|
cl.Exclude = append(cl.Exclude, h.Val())
|
||||||
|
@ -925,24 +947,34 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||||
}
|
}
|
||||||
|
|
||||||
var val namedCustomLog
|
var val namedCustomLog
|
||||||
|
val.hostnames = customHostnames
|
||||||
|
|
||||||
|
isEmptyConfig := reflect.DeepEqual(cl, new(caddy.CustomLog))
|
||||||
|
|
||||||
// Skip handling of empty logging configs
|
// Skip handling of empty logging configs
|
||||||
if !reflect.DeepEqual(cl, new(caddy.CustomLog)) {
|
|
||||||
if parseAsGlobalOption {
|
if parseAsGlobalOption {
|
||||||
// Use indicated name for global log options
|
// Use indicated name for global log options
|
||||||
val.name = globalLogName
|
val.name = logName
|
||||||
val.log = cl
|
} else {
|
||||||
} else {
|
if logName != "" {
|
||||||
|
val.name = logName
|
||||||
|
} else if !isEmptyConfig {
|
||||||
// Construct a log name for server log streams
|
// Construct a log name for server log streams
|
||||||
logCounter, ok := h.State["logCounter"].(int)
|
logCounter, ok := h.State["logCounter"].(int)
|
||||||
if !ok {
|
if !ok {
|
||||||
logCounter = 0
|
logCounter = 0
|
||||||
}
|
}
|
||||||
val.name = fmt.Sprintf("log%d", logCounter)
|
val.name = fmt.Sprintf("log%d", logCounter)
|
||||||
cl.Include = []string{"http.log.access." + val.name}
|
|
||||||
val.log = cl
|
|
||||||
logCounter++
|
logCounter++
|
||||||
h.State["logCounter"] = logCounter
|
h.State["logCounter"] = logCounter
|
||||||
}
|
}
|
||||||
|
if val.name != "" {
|
||||||
|
cl.Include = []string{"http.log.access." + val.name}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isEmptyConfig {
|
||||||
|
val.log = cl
|
||||||
}
|
}
|
||||||
configValues = append(configValues, ConfigValue{
|
configValues = append(configValues, ConfigValue{
|
||||||
Class: "custom_log",
|
Class: "custom_log",
|
||||||
|
|
|
@ -52,12 +52,13 @@ func TestLogDirectiveSyntax(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `:8080 {
|
input: `:8080 {
|
||||||
log invalid {
|
log name-override {
|
||||||
output file foo.log
|
output file foo.log
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
expectError: true,
|
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.name-override"]},"name-override":{"writer":{"filename":"foo.log","output":"file"},"include":["http.log.access.name-override"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"name-override"}}}}}}`,
|
||||||
|
expectError: false,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import (
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -241,7 +242,7 @@ func (st ServerType) Setup(
|
||||||
if ncl.name == caddy.DefaultLoggerName {
|
if ncl.name == caddy.DefaultLoggerName {
|
||||||
hasDefaultLog = true
|
hasDefaultLog = true
|
||||||
}
|
}
|
||||||
if _, ok := options["debug"]; ok && ncl.log.Level == "" {
|
if _, ok := options["debug"]; ok && ncl.log != nil && ncl.log.Level == "" {
|
||||||
ncl.log.Level = zap.DebugLevel.CapitalString()
|
ncl.log.Level = zap.DebugLevel.CapitalString()
|
||||||
}
|
}
|
||||||
customLogs = append(customLogs, ncl)
|
customLogs = append(customLogs, ncl)
|
||||||
|
@ -324,7 +325,21 @@ func (st ServerType) Setup(
|
||||||
Logs: make(map[string]*caddy.CustomLog),
|
Logs: make(map[string]*caddy.CustomLog),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the default log first if defined, so that it doesn't
|
||||||
|
// accidentally get re-created below due to the Exclude logic
|
||||||
for _, ncl := range customLogs {
|
for _, ncl := range customLogs {
|
||||||
|
if ncl.name == caddy.DefaultLoggerName && ncl.log != nil {
|
||||||
|
cfg.Logging.Logs[caddy.DefaultLoggerName] = ncl.log
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the rest of the custom logs
|
||||||
|
for _, ncl := range customLogs {
|
||||||
|
if ncl.log == nil || ncl.name == caddy.DefaultLoggerName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if ncl.name != "" {
|
if ncl.name != "" {
|
||||||
cfg.Logging.Logs[ncl.name] = ncl.log
|
cfg.Logging.Logs[ncl.name] = ncl.log
|
||||||
}
|
}
|
||||||
|
@ -338,8 +353,16 @@ func (st ServerType) Setup(
|
||||||
cfg.Logging.Logs[caddy.DefaultLoggerName] = defaultLog
|
cfg.Logging.Logs[caddy.DefaultLoggerName] = defaultLog
|
||||||
}
|
}
|
||||||
defaultLog.Exclude = append(defaultLog.Exclude, ncl.log.Include...)
|
defaultLog.Exclude = append(defaultLog.Exclude, ncl.log.Include...)
|
||||||
|
|
||||||
|
// avoid duplicates by sorting + compacting
|
||||||
|
slices.Sort[string](defaultLog.Exclude)
|
||||||
|
defaultLog.Exclude = slices.Compact[[]string, string](defaultLog.Exclude)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// we may have not actually added anything, so remove if empty
|
||||||
|
if len(cfg.Logging.Logs) == 0 {
|
||||||
|
cfg.Logging = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg, warnings, nil
|
return cfg, warnings, nil
|
||||||
|
@ -770,12 +793,20 @@ 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 sblock.hasHostCatchAllKey() {
|
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
|
||||||
srv.Logs.DefaultLoggerName = ncl.name
|
srv.Logs.DefaultLoggerName = ncl.name
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
|
srv.Logs.LoggerNames[h] = ncl.name
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// map each host to the user's desired logger name
|
// otherwise, map each host to the logger name
|
||||||
for _, h := range sblockLogHosts {
|
for _, h := range sblockLogHosts {
|
||||||
if srv.Logs.LoggerNames == nil {
|
if srv.Logs.LoggerNames == nil {
|
||||||
srv.Logs.LoggerNames = make(map[string]string)
|
srv.Logs.LoggerNames = make(map[string]string)
|
||||||
|
@ -1564,8 +1595,9 @@ func (c counter) nextGroup() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type namedCustomLog struct {
|
type namedCustomLog struct {
|
||||||
name string
|
name string
|
||||||
log *caddy.CustomLog
|
hostnames []string
|
||||||
|
log *caddy.CustomLog
|
||||||
}
|
}
|
||||||
|
|
||||||
// sbAddrAssociation is a mapping from a list of
|
// sbAddrAssociation is a mapping from a list of
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
*.example.com {
|
||||||
|
log {
|
||||||
|
hostnames foo.example.com bar.example.com
|
||||||
|
output file /foo-bar.txt
|
||||||
|
}
|
||||||
|
log {
|
||||||
|
hostnames baz.example.com
|
||||||
|
output file /baz.txt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"logging": {
|
||||||
|
"logs": {
|
||||||
|
"default": {
|
||||||
|
"exclude": [
|
||||||
|
"http.log.access.log0",
|
||||||
|
"http.log.access.log1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"log0": {
|
||||||
|
"writer": {
|
||||||
|
"filename": "/foo-bar.txt",
|
||||||
|
"output": "file"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"http.log.access.log0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"log1": {
|
||||||
|
"writer": {
|
||||||
|
"filename": "/baz.txt",
|
||||||
|
"output": "file"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"http.log.access.log1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"*.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"logs": {
|
||||||
|
"logger_names": {
|
||||||
|
"bar.example.com": "log0",
|
||||||
|
"baz.example.com": "log1",
|
||||||
|
"foo.example.com": "log0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
{
|
||||||
|
log access-console {
|
||||||
|
include http.log.access.foo
|
||||||
|
output file access-localhost.log
|
||||||
|
format console
|
||||||
|
}
|
||||||
|
|
||||||
|
log access-json {
|
||||||
|
include http.log.access.foo
|
||||||
|
output file access-localhost.json
|
||||||
|
format json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http://localhost:8881 {
|
||||||
|
log foo
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"logging": {
|
||||||
|
"logs": {
|
||||||
|
"access-console": {
|
||||||
|
"writer": {
|
||||||
|
"filename": "access-localhost.log",
|
||||||
|
"output": "file"
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"format": "console"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"http.log.access.foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"access-json": {
|
||||||
|
"writer": {
|
||||||
|
"filename": "access-localhost.json",
|
||||||
|
"output": "file"
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"format": "json"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"http.log.access.foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"exclude": [
|
||||||
|
"http.log.access.foo"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":8881"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"automatic_https": {
|
||||||
|
"skip": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"logs": {
|
||||||
|
"logger_names": {
|
||||||
|
"localhost:8881": "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
{
|
||||||
|
debug
|
||||||
|
|
||||||
|
log access-console {
|
||||||
|
include http.log.access.foo
|
||||||
|
output file access-localhost.log
|
||||||
|
format console
|
||||||
|
}
|
||||||
|
|
||||||
|
log access-json {
|
||||||
|
include http.log.access.foo
|
||||||
|
output file access-localhost.json
|
||||||
|
format json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http://localhost:8881 {
|
||||||
|
log foo
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"logging": {
|
||||||
|
"logs": {
|
||||||
|
"access-console": {
|
||||||
|
"writer": {
|
||||||
|
"filename": "access-localhost.log",
|
||||||
|
"output": "file"
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"format": "console"
|
||||||
|
},
|
||||||
|
"level": "DEBUG",
|
||||||
|
"include": [
|
||||||
|
"http.log.access.foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"access-json": {
|
||||||
|
"writer": {
|
||||||
|
"filename": "access-localhost.json",
|
||||||
|
"output": "file"
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"format": "json"
|
||||||
|
},
|
||||||
|
"level": "DEBUG",
|
||||||
|
"include": [
|
||||||
|
"http.log.access.foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"level": "DEBUG",
|
||||||
|
"exclude": [
|
||||||
|
"http.log.access.foo"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":8881"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"automatic_https": {
|
||||||
|
"skip": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"logs": {
|
||||||
|
"logger_names": {
|
||||||
|
"localhost:8881": "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue