@marcel91200 / caddy Mirror · read-only / commits / 9ad0ebc9
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 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