@marcel91200 / caddy Mirror · read-only
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 11, 01:00 UTC

master
go 173 lines 5.31 KB
Raw
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 )