@marcel91200 / caddy Mirror · read-only / commits / 4356635d
Fork 0

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