Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 01:00 UTC
| 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 logging |
| 16 | |
| 17 | import ( |
| 18 | "bytes" |
| 19 | "encoding/base64" |
| 20 | "net/http" |
| 21 | "strings" |
| 22 | |
| 23 | "go.uber.org/zap" |
| 24 | |
| 25 | "github.com/caddyserver/caddy/v2" |
| 26 | "github.com/caddyserver/caddy/v2/modules/caddyhttp" |
| 27 | ) |
| 28 | |
| 29 | func init() { |
| 30 | caddy.RegisterModule(LogAppend{}) |
| 31 | } |
| 32 | |
| 33 | // LogAppend implements a middleware that takes a key and value, where |
| 34 | // the key is the name of a log field and the value is a placeholder, |
| 35 | // or variable key, or constant value to use for that field. |
| 36 | type LogAppend struct { |
| 37 | // Key is the name of the log field. |
| 38 | Key string `json:"key,omitempty"` |
| 39 | |
| 40 | // Value is the value to use for the log field. |
| 41 | // If it is a placeholder (with surrounding `{}`), |
| 42 | // it will be evaluated when the log is written. |
| 43 | // If the value is a key that exists in the `vars` |
| 44 | // map, the value of that key will be used. Otherwise |
| 45 | // the value will be used as-is as a constant string. |
| 46 | Value string `json:"value,omitempty"` |
| 47 | |
| 48 | // Early, if true, adds the log field before calling |
| 49 | // the next handler in the chain. By default, the log |
| 50 | // field is added on the way back up the middleware chain, |
| 51 | // after all subsequent handlers have completed. |
| 52 | Early bool `json:"early,omitempty"` |
| 53 | } |
| 54 | |
| 55 | // CaddyModule returns the Caddy module information. |
| 56 | func (LogAppend) CaddyModule() caddy.ModuleInfo { |
| 57 | return caddy.ModuleInfo{ |
| 58 | ID: "http.handlers.log_append", |
| 59 | New: func() caddy.Module { return new(LogAppend) }, |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | func (h LogAppend) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { |
| 64 | // Determine if we need to add the log field early. |
| 65 | // We do if the Early flag is set, or for convenience, |
| 66 | // if the value is a special placeholder for the request body. |
| 67 | needsEarly := h.Early || h.Value == placeholderRequestBody || h.Value == placeholderRequestBodyBase64 |
| 68 | |
| 69 | // Check if we need to buffer the response for special placeholders |
| 70 | needsResponseBody := h.Value == placeholderResponseBody || h.Value == placeholderResponseBodyBase64 |
| 71 | |
| 72 | if needsEarly && !needsResponseBody { |
| 73 | // Add the log field before calling the next handler |
| 74 | // (but not if we need the response body, which isn't available yet) |
| 75 | h.addLogField(r, nil) |
| 76 | } |
| 77 | |
| 78 | var rec caddyhttp.ResponseRecorder |
| 79 | var buf *bytes.Buffer |
| 80 | |
| 81 | if needsResponseBody { |
| 82 | // Wrap the response writer with a recorder to capture the response body |
| 83 | buf = new(bytes.Buffer) |
| 84 | rec = caddyhttp.NewResponseRecorder(w, buf, func(status int, header http.Header) bool { |
| 85 | // Always buffer the response when we need to log the body |
| 86 | return true |
| 87 | }) |
| 88 | w = rec |
| 89 | } |
| 90 | |
| 91 | // Run the next handler in the chain. |
| 92 | // If an error occurs, we still want to add |
| 93 | // any extra log fields that we can, so we |
| 94 | // hold onto the error and return it later. |
| 95 | handlerErr := next.ServeHTTP(w, r) |
| 96 | |
| 97 | if needsResponseBody { |
| 98 | // Write the buffered response to the client |
| 99 | if rec.Buffered() { |
| 100 | h.addLogField(r, buf) |
| 101 | err := rec.WriteResponse() |
| 102 | if err != nil { |
| 103 | return err |
| 104 | } |
| 105 | } |
| 106 | return handlerErr |
| 107 | } |
| 108 | |
| 109 | if !h.Early { |
| 110 | // Add the log field after the handler completes |
| 111 | h.addLogField(r, buf) |
| 112 | } |
| 113 | |
| 114 | return handlerErr |
| 115 | } |
| 116 | |
| 117 | // addLogField adds the log field to the request's extra log fields. |
| 118 | // If buf is not nil, it contains the buffered response body for special |
| 119 | // response body placeholders. |
| 120 | func (h LogAppend) addLogField(r *http.Request, buf *bytes.Buffer) { |
| 121 | ctx := r.Context() |
| 122 | |
| 123 | vars := ctx.Value(caddyhttp.VarsCtxKey).(map[string]any) |
| 124 | repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) |
| 125 | extra := ctx.Value(caddyhttp.ExtraLogFieldsCtxKey).(*caddyhttp.ExtraLogFields) |
| 126 | |
| 127 | var varValue any |
| 128 | |
| 129 | // Handle special case placeholders for response body |
| 130 | if h.Value == placeholderResponseBody { |
| 131 | if buf != nil { |
| 132 | varValue = buf.String() |
| 133 | } else { |
| 134 | varValue = "" |
| 135 | } |
| 136 | } else if h.Value == placeholderResponseBodyBase64 { |
| 137 | if buf != nil { |
| 138 | varValue = base64.StdEncoding.EncodeToString(buf.Bytes()) |
| 139 | } else { |
| 140 | varValue = "" |
| 141 | } |
| 142 | } else if strings.HasPrefix(h.Value, "{") && |
| 143 | strings.HasSuffix(h.Value, "}") && |
| 144 | strings.Count(h.Value, "{") == 1 { |
| 145 | // the value looks like a placeholder, so get its value |
| 146 | varValue, _ = repl.Get(strings.Trim(h.Value, "{}")) |
| 147 | } else if val, ok := vars[h.Value]; ok { |
| 148 | // the value is a key in the vars map |
| 149 | varValue = val |
| 150 | } else { |
| 151 | // the value is a constant string |
| 152 | varValue = h.Value |
| 153 | } |
| 154 | |
| 155 | // Add the field to the extra log fields. |
| 156 | // We use zap.Any because it will reflect |
| 157 | // to the correct type for us. |
| 158 | extra.Add(zap.Any(h.Key, varValue)) |
| 159 | } |
| 160 | |
| 161 | const ( |
| 162 | // Special placeholder values that are handled by log_append |
| 163 | // rather than by the replacer. |
| 164 | placeholderRequestBody = "{http.request.body}" |
| 165 | placeholderRequestBodyBase64 = "{http.request.body_base64}" |
| 166 | placeholderResponseBody = "{http.response.body}" |
| 167 | placeholderResponseBodyBase64 = "{http.response.body_base64}" |
| 168 | ) |
| 169 | |
| 170 | // Interface guards |
| 171 | var ( |
| 172 | _ caddyhttp.MiddlewareHandler = (*LogAppend)(nil) |
| 173 | ) |