Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 13, 13:00 UTC
caddyhttp: Add 'skip_log' var to omit request from logs (#4691)
* caddyhttp: Implement `skip_log` handler * Refactor to use vars middleware Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
Francis Lavoie committed
Sep 15, 2022 at 12:05 UTC
9ad0ebc956f4317111c12e2c5428acd18522f025
5 files changed
+195
-116
caddyconfig/httpcaddyfile/builtins.go
+13
@@ -48,6 +48,7 @@ func init() {
48
RegisterHandlerDirective("handle", parseHandle)
49
RegisterDirective("handle_errors", parseHandleErrors)
50
RegisterDirective("log", parseLog)
51
+ RegisterHandlerDirective("skip_log", parseSkipLog)
52
}
53
54
// parseBind parses the bind directive. Syntax:
@@ -858,3 +859,15 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
859
}
860
return configValues, nil
861
}
862
+
863
+// parseSkipLog parses the skip_log directive. Syntax:
864
+//
865
+// skip_log [<matcher>]
866
+func parseSkipLog(h Helper) (caddyhttp.MiddlewareHandler, error) {
867
+ for h.Next() {
868
+ if h.NextArg() {
869
+ return nil, h.ArgErr()
870
+ }
871
+ }
872
+ return caddyhttp.VarsMiddleware{"skip_log": true}, nil
873
+}
caddyconfig/httpcaddyfile/directives.go
+1
@@ -42,6 +42,7 @@ var directiveOrder = []string{
42
"map",
43
"vars",
44
"root",
45
+ "skip_log",
46
47
"header",
48
"copy_response_headers", // only in reverse_proxy's handle_response
caddytest/integration/caddyfile_adapt/log_except_catchall_blocks.txt
+32
@@ -1,5 +1,7 @@
1
http://localhost:2020 {
2
log
3
+ skip_log /first-hidden*
4
+ skip_log /second-hidden*
5
respond 200
6
}
7
@@ -28,6 +30,36 @@ http://localhost:2020 {
30
{
31
"handler": "subroute",
32
"routes": [
33
+ {
34
+ "handle": [
35
+ {
36
+ "handler": "vars",
37
+ "skip_log": true
38
+ }
39
+ ],
40
+ "match": [
41
+ {
42
+ "path": [
43
+ "/second-hidden*"
44
+ ]
45
+ }
46
+ ]
47
+ },
48
+ {
49
+ "handle": [
50
+ {
51
+ "handler": "vars",
52
+ "skip_log": true
53
+ }
54
+ ],
55
+ "match": [
56
+ {
57
+ "path": [
58
+ "/first-hidden*"
59
+ ]
60
+ }
61
+ ]
62
+ },
63
{
64
"handle": [
65
{
modules/caddyhttp/logging.go
new
+144
@@ -0,0 +1,144 @@
1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package caddyhttp
16
+
17
+import (
18
+ "errors"
19
+ "net"
20
+ "net/http"
21
+ "strings"
22
+
23
+ "go.uber.org/zap"
24
+ "go.uber.org/zap/zapcore"
25
+)
26
+
27
+// ServerLogConfig describes a server's logging configuration. If
28
+// enabled without customization, all requests to this server are
29
+// logged to the default logger; logger destinations may be
30
+// customized per-request-host.
31
+type ServerLogConfig struct {
32
+ // The default logger name for all logs emitted by this server for
33
+ // hostnames that are not in the LoggerNames (logger_names) map.
34
+ DefaultLoggerName string `json:"default_logger_name,omitempty"`
35
+
36
+ // LoggerNames maps request hostnames to a custom logger name.
37
+ // For example, a mapping of "example.com" to "example" would
38
+ // cause access logs from requests with a Host of example.com
39
+ // to be emitted by a logger named "http.log.access.example".
40
+ LoggerNames map[string]string `json:"logger_names,omitempty"`
41
+
42
+ // By default, all requests to this server will be logged if
43
+ // access logging is enabled. This field lists the request
44
+ // hosts for which access logging should be disabled.
45
+ SkipHosts []string `json:"skip_hosts,omitempty"`
46
+
47
+ // If true, requests to any host not appearing in the
48
+ // LoggerNames (logger_names) map will not be logged.
49
+ SkipUnmappedHosts bool `json:"skip_unmapped_hosts,omitempty"`
50
+
51
+ // If true, credentials that are otherwise omitted, will be logged.
52
+ // The definition of credentials is defined by https://fetch.spec.whatwg.org/#credentials,
53
+ // and this includes some request and response headers, i.e `Cookie`,
54
+ // `Set-Cookie`, `Authorization`, and `Proxy-Authorization`.
55
+ ShouldLogCredentials bool `json:"should_log_credentials,omitempty"`
56
+}
57
+
58
+// wrapLogger wraps logger in a logger named according to user preferences for the given host.
59
+func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, host string) *zap.Logger {
60
+ if loggerName := slc.getLoggerName(host); loggerName != "" {
61
+ return logger.Named(loggerName)
62
+ }
63
+ return logger
64
+}
65
+
66
+func (slc ServerLogConfig) getLoggerName(host string) string {
67
+ tryHost := func(key string) (string, bool) {
68
+ // first try exact match
69
+ if loggerName, ok := slc.LoggerNames[key]; ok {
70
+ return loggerName, ok
71
+ }
72
+ // strip port and try again (i.e. Host header of "example.com:1234" should
73
+ // match "example.com" if there is no "example.com:1234" in the map)
74
+ hostOnly, _, err := net.SplitHostPort(key)
75
+ if err != nil {
76
+ return "", false
77
+ }
78
+ loggerName, ok := slc.LoggerNames[hostOnly]
79
+ return loggerName, ok
80
+ }
81
+
82
+ // try the exact hostname first
83
+ if loggerName, ok := tryHost(host); ok {
84
+ return loggerName
85
+ }
86
+
87
+ // try matching wildcard domains if other non-specific loggers exist
88
+ labels := strings.Split(host, ".")
89
+ for i := range labels {
90
+ if labels[i] == "" {
91
+ continue
92
+ }
93
+ labels[i] = "*"
94
+ wildcardHost := strings.Join(labels, ".")
95
+ if loggerName, ok := tryHost(wildcardHost); ok {
96
+ return loggerName
97
+ }
98
+ }
99
+
100
+ return slc.DefaultLoggerName
101
+}
102
+
103
+func (slc *ServerLogConfig) clone() *ServerLogConfig {
104
+ clone := &ServerLogConfig{
105
+ DefaultLoggerName: slc.DefaultLoggerName,
106
+ LoggerNames: make(map[string]string),
107
+ SkipHosts: append([]string{}, slc.SkipHosts...),
108
+ SkipUnmappedHosts: slc.SkipUnmappedHosts,
109
+ ShouldLogCredentials: slc.ShouldLogCredentials,
110
+ }
111
+ for k, v := range slc.LoggerNames {
112
+ clone.LoggerNames[k] = v
113
+ }
114
+ return clone
115
+}
116
+
117
+// errLogValues inspects err and returns the status code
118
+// to use, the error log message, and any extra fields.
119
+// If err is a HandlerError, the returned values will
120
+// have richer information.
121
+func errLogValues(err error) (status int, msg string, fields []zapcore.Field) {
122
+ var handlerErr HandlerError
123
+ if errors.As(err, &handlerErr) {
124
+ status = handlerErr.StatusCode
125
+ if handlerErr.Err == nil {
126
+ msg = err.Error()
127
+ } else {
128
+ msg = handlerErr.Err.Error()
129
+ }
130
+ fields = []zapcore.Field{
131
+ zap.Int("status", handlerErr.StatusCode),
132
+ zap.String("err_id", handlerErr.ID),
133
+ zap.String("err_trace", handlerErr.Trace),
134
+ }
135
+ return
136
+ }
137
+ status = http.StatusInternalServerError
138
+ msg = err.Error()
139
+ return
140
+}
141
+
142
+// Variable name used to indicate that this request
143
+// should be omitted from the access logs
144
+const SkipLogVar = "skip_log"
modules/caddyhttp/server.go
+5
-116
@@ -18,7 +18,6 @@ import (
18
"context"
19
"crypto/tls"
20
"encoding/json"
21
- "errors"
21
"fmt"
22
"net"
23
"net/http"
@@ -226,6 +225,11 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
225
accLog := s.accessLogger.With(loggableReq)
226
227
defer func() {
228
+ // this request may be flagged as omitted from the logs
229
+ if skipLog, ok := GetVar(r.Context(), SkipLogVar).(bool); ok && skipLog {
230
+ return
231
+ }
232
+
233
repl.Set("http.response.status", wrec.Status())
234
repl.Set("http.response.size", wrec.Size())
235
repl.Set("http.response.duration", duration)
@@ -592,96 +596,6 @@ func (s *Server) protocol(proto string) bool {
596
// EXPERIMENTAL: Subject to change or removal.
597
func (s *Server) Listeners() []net.Listener { return s.listeners }
598
595
-// ServerLogConfig describes a server's logging configuration. If
596
-// enabled without customization, all requests to this server are
597
-// logged to the default logger; logger destinations may be
598
-// customized per-request-host.
599
-type ServerLogConfig struct {
600
- // The default logger name for all logs emitted by this server for
601
- // hostnames that are not in the LoggerNames (logger_names) map.
602
- DefaultLoggerName string `json:"default_logger_name,omitempty"`
603
-
604
- // LoggerNames maps request hostnames to a custom logger name.
605
- // For example, a mapping of "example.com" to "example" would
606
- // cause access logs from requests with a Host of example.com
607
- // to be emitted by a logger named "http.log.access.example".
608
- LoggerNames map[string]string `json:"logger_names,omitempty"`
609
-
610
- // By default, all requests to this server will be logged if
611
- // access logging is enabled. This field lists the request
612
- // hosts for which access logging should be disabled.
613
- SkipHosts []string `json:"skip_hosts,omitempty"`
614
-
615
- // If true, requests to any host not appearing in the
616
- // LoggerNames (logger_names) map will not be logged.
617
- SkipUnmappedHosts bool `json:"skip_unmapped_hosts,omitempty"`
618
-
619
- // If true, credentials that are otherwise omitted, will be logged.
620
- // The definition of credentials is defined by https://fetch.spec.whatwg.org/#credentials,
621
- // and this includes some request and response headers, i.e `Cookie`,
622
- // `Set-Cookie`, `Authorization`, and `Proxy-Authorization`.
623
- ShouldLogCredentials bool `json:"should_log_credentials,omitempty"`
624
-}
625
-
626
-// wrapLogger wraps logger in a logger named according to user preferences for the given host.
627
-func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, host string) *zap.Logger {
628
- if loggerName := slc.getLoggerName(host); loggerName != "" {
629
- return logger.Named(loggerName)
630
- }
631
- return logger
632
-}
633
-
634
-func (slc ServerLogConfig) getLoggerName(host string) string {
635
- tryHost := func(key string) (string, bool) {
636
- // first try exact match
637
- if loggerName, ok := slc.LoggerNames[key]; ok {
638
- return loggerName, ok
639
- }
640
- // strip port and try again (i.e. Host header of "example.com:1234" should
641
- // match "example.com" if there is no "example.com:1234" in the map)
642
- hostOnly, _, err := net.SplitHostPort(key)
643
- if err != nil {
644
- return "", false
645
- }
646
- loggerName, ok := slc.LoggerNames[hostOnly]
647
- return loggerName, ok
648
- }
649
-
650
- // try the exact hostname first
651
- if loggerName, ok := tryHost(host); ok {
652
- return loggerName
653
- }
654
-
655
- // try matching wildcard domains if other non-specific loggers exist
656
- labels := strings.Split(host, ".")
657
- for i := range labels {
658
- if labels[i] == "" {
659
- continue
660
- }
661
- labels[i] = "*"
662
- wildcardHost := strings.Join(labels, ".")
663
- if loggerName, ok := tryHost(wildcardHost); ok {
664
- return loggerName
665
- }
666
- }
667
-
668
- return slc.DefaultLoggerName
669
-}
670
-
671
-func (slc *ServerLogConfig) clone() *ServerLogConfig {
672
- clone := &ServerLogConfig{
673
- DefaultLoggerName: slc.DefaultLoggerName,
674
- LoggerNames: make(map[string]string),
675
- SkipHosts: append([]string{}, slc.SkipHosts...),
676
- SkipUnmappedHosts: slc.SkipUnmappedHosts,
677
- ShouldLogCredentials: slc.ShouldLogCredentials,
678
- }
679
- for k, v := range slc.LoggerNames {
680
- clone.LoggerNames[k] = v
681
- }
682
- return clone
683
-}
684
-
599
// PrepareRequest fills the request r for use in a Caddy HTTP handler chain. w and s can
600
// be nil, but the handlers will lose response placeholders and access to the server.
601
func PrepareRequest(r *http.Request, repl *caddy.Replacer, w http.ResponseWriter, s *Server) *http.Request {
@@ -701,31 +615,6 @@ func PrepareRequest(r *http.Request, repl *caddy.Replacer, w http.ResponseWriter
615
return r
616
}
617
704
-// errLogValues inspects err and returns the status code
705
-// to use, the error log message, and any extra fields.
706
-// If err is a HandlerError, the returned values will
707
-// have richer information.
708
-func errLogValues(err error) (status int, msg string, fields []zapcore.Field) {
709
- var handlerErr HandlerError
710
- if errors.As(err, &handlerErr) {
711
- status = handlerErr.StatusCode
712
- if handlerErr.Err == nil {
713
- msg = err.Error()
714
- } else {
715
- msg = handlerErr.Err.Error()
716
- }
717
- fields = []zapcore.Field{
718
- zap.Int("status", handlerErr.StatusCode),
719
- zap.String("err_id", handlerErr.ID),
720
- zap.String("err_trace", handlerErr.Trace),
721
- }
722
- return
723
- }
724
- status = http.StatusInternalServerError
725
- msg = err.Error()
726
- return
727
-}
728
-
618
// originalRequest returns a partial, shallow copy of
619
// req, including: req.Method, deep copy of req.URL
620
// (into the urlCopy parameter, which should be on the