Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 12, 13:15 UTC
logging: Add support for additional logger filters other than hostname (#6082)
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Aziz Rmadi committed
May 11, 2024 at 08:31 UTC
4356635d123ee71b72654738a0c30653a3a29d98
6 files changed
+216
-14
caddyconfig/httpcaddyfile/builtins.go
+19
-2
@@ -51,6 +51,7 @@ func init() {
51
RegisterDirective("log", parseLog)
52
RegisterHandlerDirective("skip_log", parseLogSkip)
53
RegisterHandlerDirective("log_skip", parseLogSkip)
54
+ RegisterHandlerDirective("log_name", parseLogName)
55
}
56
57
// parseBind parses the bind directive. Syntax:
@@ -914,7 +915,7 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
915
// this is useful for setting up loggers per subdomain in a site block
916
// with a wildcard domain
917
customHostnames := []string{}
917
-
918
+ noHostname := false
919
for h.NextBlock(0) {
920
switch h.Val() {
921
case "hostnames":
@@ -1000,6 +1001,12 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
1001
cl.Exclude = append(cl.Exclude, h.Val())
1002
}
1003
1004
+ case "no_hostname":
1005
+ if h.NextArg() {
1006
+ return nil, h.ArgErr()
1007
+ }
1008
+ noHostname = true
1009
+
1010
default:
1011
return nil, h.Errf("unrecognized subdirective: %s", h.Val())
1012
}
@@ -1007,7 +1014,7 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
1014
1015
var val namedCustomLog
1016
val.hostnames = customHostnames
1010
-
1017
+ val.noHostname = noHostname
1018
isEmptyConfig := reflect.DeepEqual(cl, new(caddy.CustomLog))
1019
1020
// Skip handling of empty logging configs
@@ -1058,3 +1065,13 @@ func parseLogSkip(h Helper) (caddyhttp.MiddlewareHandler, error) {
1065
}
1066
return caddyhttp.VarsMiddleware{"log_skip": true}, nil
1067
}
1068
+
1069
+// parseLogName parses the log_name directive. Syntax:
1070
+//
1071
+// log_name <names...>
1072
+func parseLogName(h Helper) (caddyhttp.MiddlewareHandler, error) {
1073
+ h.Next() // consume directive name
1074
+ return caddyhttp.VarsMiddleware{
1075
+ caddyhttp.AccessLoggerNameVarKey: h.RemainingArgs(),
1076
+ }, nil
1077
+}
caddyconfig/httpcaddyfile/directives.go
+1
@@ -53,6 +53,7 @@ var defaultDirectiveOrder = []string{
53
"log_append",
54
"skip_log", // TODO: deprecated, renamed to log_skip
55
"log_skip",
56
+ "log_name",
57
58
"header",
59
"copy_response_headers", // only in reverse_proxy's handle_response
caddyconfig/httpcaddyfile/httptype.go
+13
-3
@@ -797,6 +797,15 @@ func (st *ServerType) serversFromPairings(
797
sblockLogHosts := sblock.hostsFromKeys(true)
798
for _, cval := range sblock.pile["custom_log"] {
799
ncl := cval.Value.(namedCustomLog)
800
+
801
+ // if `no_hostname` is set, then this logger will not
802
+ // be associated with any of the site block's hostnames,
803
+ // and only be usable via the `log_name` directive
804
+ // or the `access_logger_names` variable
805
+ if ncl.noHostname {
806
+ continue
807
+ }
808
+
809
if sblock.hasHostCatchAllKey() && len(ncl.hostnames) == 0 {
810
// all requests for hosts not able to be listed should use
811
// this log because it's a catch-all-hosts server block
@@ -1598,9 +1607,10 @@ func (c counter) nextGroup() string {
1607
}
1608
1609
type namedCustomLog struct {
1601
- name string
1602
- hostnames []string
1603
- log *caddy.CustomLog
1610
+ name string
1611
+ hostnames []string
1612
+ log *caddy.CustomLog
1613
+ noHostname bool
1614
}
1615
1616
// sbAddrAssociation is a mapping from a list of
caddytest/integration/caddyfile_adapt/log_filter_with_header.txt
new
+151
@@ -0,0 +1,151 @@
1
+localhost {
2
+ log {
3
+ output file ./caddy.access.log
4
+ }
5
+ log health_check_log {
6
+ output file ./caddy.access.health.log
7
+ no_hostname
8
+ }
9
+ log general_log {
10
+ output file ./caddy.access.general.log
11
+ no_hostname
12
+ }
13
+ @healthCheck `header_regexp('User-Agent', '^some-regexp$') || path('/healthz*')`
14
+ handle @healthCheck {
15
+ log_name health_check_log general_log
16
+ respond "Healthy"
17
+ }
18
+
19
+ handle {
20
+ respond "Hello World"
21
+ }
22
+}
23
+----------
24
+{
25
+ "logging": {
26
+ "logs": {
27
+ "default": {
28
+ "exclude": [
29
+ "http.log.access.general_log",
30
+ "http.log.access.health_check_log",
31
+ "http.log.access.log0"
32
+ ]
33
+ },
34
+ "general_log": {
35
+ "writer": {
36
+ "filename": "./caddy.access.general.log",
37
+ "output": "file"
38
+ },
39
+ "include": [
40
+ "http.log.access.general_log"
41
+ ]
42
+ },
43
+ "health_check_log": {
44
+ "writer": {
45
+ "filename": "./caddy.access.health.log",
46
+ "output": "file"
47
+ },
48
+ "include": [
49
+ "http.log.access.health_check_log"
50
+ ]
51
+ },
52
+ "log0": {
53
+ "writer": {
54
+ "filename": "./caddy.access.log",
55
+ "output": "file"
56
+ },
57
+ "include": [
58
+ "http.log.access.log0"
59
+ ]
60
+ }
61
+ }
62
+ },
63
+ "apps": {
64
+ "http": {
65
+ "servers": {
66
+ "srv0": {
67
+ "listen": [
68
+ ":443"
69
+ ],
70
+ "routes": [
71
+ {
72
+ "match": [
73
+ {
74
+ "host": [
75
+ "localhost"
76
+ ]
77
+ }
78
+ ],
79
+ "handle": [
80
+ {
81
+ "handler": "subroute",
82
+ "routes": [
83
+ {
84
+ "group": "group2",
85
+ "handle": [
86
+ {
87
+ "handler": "subroute",
88
+ "routes": [
89
+ {
90
+ "handle": [
91
+ {
92
+ "access_logger_names": [
93
+ "health_check_log",
94
+ "general_log"
95
+ ],
96
+ "handler": "vars"
97
+ },
98
+ {
99
+ "body": "Healthy",
100
+ "handler": "static_response"
101
+ }
102
+ ]
103
+ }
104
+ ]
105
+ }
106
+ ],
107
+ "match": [
108
+ {
109
+ "expression": {
110
+ "expr": "header_regexp('User-Agent', '^some-regexp$') || path('/healthz*')",
111
+ "name": "healthCheck"
112
+ }
113
+ }
114
+ ]
115
+ },
116
+ {
117
+ "group": "group2",
118
+ "handle": [
119
+ {
120
+ "handler": "subroute",
121
+ "routes": [
122
+ {
123
+ "handle": [
124
+ {
125
+ "body": "Hello World",
126
+ "handler": "static_response"
127
+ }
128
+ ]
129
+ }
130
+ ]
131
+ }
132
+ ]
133
+ }
134
+ ]
135
+ }
136
+ ],
137
+ "terminal": true
138
+ }
139
+ ],
140
+ "logs": {
141
+ "logger_names": {
142
+ "localhost": [
143
+ "log0"
144
+ ]
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+}
modules/caddyhttp/logging.go
+30
-6
@@ -69,15 +69,35 @@ type ServerLogConfig struct {
69
70
// wrapLogger wraps logger in one or more logger named
71
// according to user preferences for the given host.
72
-func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, host string) []*zap.Logger {
73
- // logger config should always be only
74
- // the hostname, without the port
75
- hostWithoutPort, _, err := net.SplitHostPort(host)
72
+func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, req *http.Request) []*zap.Logger {
73
+ // using the `log_name` directive or the `access_logger_names` variable,
74
+ // the logger names can be overridden for the current request
75
+ if names := GetVar(req.Context(), AccessLoggerNameVarKey); names != nil {
76
+ if namesSlice, ok := names.([]any); ok {
77
+ loggers := make([]*zap.Logger, 0, len(namesSlice))
78
+ for _, loggerName := range namesSlice {
79
+ // no name, use the default logger
80
+ if loggerName == "" {
81
+ loggers = append(loggers, logger)
82
+ continue
83
+ }
84
+ // make a logger with the given name
85
+ loggers = append(loggers, logger.Named(loggerName.(string)))
86
+ }
87
+ return loggers
88
+ }
89
+ }
90
+
91
+ // get the hostname from the request, with the port number stripped
92
+ host, _, err := net.SplitHostPort(req.Host)
93
if err != nil {
77
- hostWithoutPort = host
94
+ host = req.Host
95
}
96
80
- hosts := slc.getLoggerHosts(hostWithoutPort)
97
+ // get the logger names for this host from the config
98
+ hosts := slc.getLoggerHosts(host)
99
+
100
+ // make a list of named loggers, or the default logger
101
loggers := make([]*zap.Logger, 0, len(hosts))
102
for _, loggerName := range hosts {
103
// no name, use the default logger
@@ -85,6 +105,7 @@ func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, host string) []*zap.Lo
105
loggers = append(loggers, logger)
106
continue
107
}
108
+ // make a logger with the given name
109
loggers = append(loggers, logger.Named(loggerName))
110
}
111
return loggers
@@ -211,4 +232,7 @@ const (
232
233
// For adding additional fields to the access logs
234
ExtraLogFieldsCtxKey caddy.CtxKey = "extra_log_fields"
235
+
236
+ // Variable name used to indicate the logger to be used
237
+ AccessLoggerNameVarKey string = "access_logger_names"
238
)
modules/caddyhttp/server.go
+2
-3
@@ -369,7 +369,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
369
errLog = errLog.With(zap.Duration("duration", duration))
370
errLoggers := []*zap.Logger{errLog}
371
if s.Logs != nil {
372
- errLoggers = s.Logs.wrapLogger(errLog, r.Host)
372
+ errLoggers = s.Logs.wrapLogger(errLog, r)
373
}
374
375
// get the values that will be used to log the error
@@ -778,7 +778,7 @@ func (s *Server) logRequest(
778
779
loggers := []*zap.Logger{accLog}
780
if s.Logs != nil {
781
- loggers = s.Logs.wrapLogger(accLog, r.Host)
781
+ loggers = s.Logs.wrapLogger(accLog, r)
782
}
783
784
// 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
835
ctx = context.WithValue(ctx, OriginalRequestCtxKey, originalRequest(r, &url2))
836
837
ctx = context.WithValue(ctx, ExtraLogFieldsCtxKey, new(ExtraLogFields))
838
-
838
r = r.WithContext(ctx)
839
840
// once the pointer to the request won't change