@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 364 lines 10.8 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 caddyhttp
16
17 import (
18 "context"
19 "encoding/json"
20 "errors"
21 "log/slog"
22 "maps"
23 "net"
24 "net/http"
25 "slices"
26 "strings"
27 "sync"
28
29 "go.uber.org/zap"
30 "go.uber.org/zap/exp/zapslog"
31 "go.uber.org/zap/zapcore"
32
33 "github.com/caddyserver/caddy/v2"
34 )
35
36 func init() {
37 caddy.RegisterSlogHandlerFactory(func(handler slog.Handler, core zapcore.Core, moduleID string) slog.Handler {
38 return &extraFieldsSlogHandler{defaultHandler: handler, core: core, moduleID: moduleID}
39 })
40 }
41
42 // ServerLogConfig describes a server's logging configuration. If
43 // enabled without customization, all requests to this server are
44 // logged to the default logger; logger destinations may be
45 // customized per-request-host.
46 type ServerLogConfig struct {
47 // The default logger name for all logs emitted by this server for
48 // hostnames that are not in the logger_names map.
49 DefaultLoggerName string `json:"default_logger_name,omitempty"`
50
51 // LoggerNames maps request hostnames to one or more custom logger
52 // names. For example, a mapping of `"example.com": ["example"]` would
53 // cause access logs from requests with a Host of example.com to be
54 // emitted by a logger named "http.log.access.example". If there are
55 // multiple logger names, then the log will be emitted to all of them.
56 // If the logger name is an empty, the default logger is used, i.e.
57 // the logger "http.log.access".
58 //
59 // Keys must be hostnames (without ports), and may contain wildcards
60 // to match subdomains. The value is an array of logger names.
61 //
62 // For backwards compatibility, if the value is a string, it is treated
63 // as a single-element array.
64 LoggerNames map[string]StringArray `json:"logger_names,omitempty"`
65
66 // By default, all requests to this server will be logged if
67 // access logging is enabled. This field lists the request
68 // hosts for which access logging should be disabled.
69 SkipHosts []string `json:"skip_hosts,omitempty"`
70
71 // If true, requests to any host not appearing in the
72 // logger_names map will not be logged.
73 SkipUnmappedHosts bool `json:"skip_unmapped_hosts,omitempty"`
74
75 // If true, credentials that are otherwise omitted, will be logged.
76 // The definition of credentials is defined by https://fetch.spec.whatwg.org/#credentials,
77 // and this includes some request and response headers, i.e `Cookie`,
78 // `Set-Cookie`, `Authorization`, and `Proxy-Authorization`.
79 ShouldLogCredentials bool `json:"should_log_credentials,omitempty"`
80
81 // Log each individual handler that is invoked.
82 // Requires that the log emit at DEBUG level.
83 //
84 // NOTE: This may log the configuration of your
85 // HTTP handler modules; do not enable this in
86 // insecure contexts when there is sensitive
87 // data in the configuration.
88 //
89 // EXPERIMENTAL: Subject to change or removal.
90 Trace bool `json:"trace,omitempty"`
91 }
92
93 // wrapLogger wraps logger in one or more logger named
94 // according to user preferences for the given host.
95 func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, req *http.Request) []*zap.Logger {
96 // using the `log_name` directive or the `access_logger_names` variable,
97 // the logger names can be overridden for the current request
98 if names := GetVar(req.Context(), AccessLoggerNameVarKey); names != nil {
99 if namesSlice, ok := names.([]any); ok {
100 loggers := make([]*zap.Logger, 0, len(namesSlice))
101 for _, loggerName := range namesSlice {
102 // no name, use the default logger
103 if loggerName == "" {
104 loggers = append(loggers, logger)
105 continue
106 }
107 // make a logger with the given name
108 loggers = append(loggers, logger.Named(loggerName.(string)))
109 }
110 return loggers
111 }
112 }
113
114 // get the hostname from the request, with the port number stripped
115 host, _, err := net.SplitHostPort(req.Host)
116 if err != nil {
117 host = req.Host
118 }
119
120 // get the logger names for this host from the config
121 hosts := slc.getLoggerHosts(host)
122
123 // make a list of named loggers, or the default logger
124 loggers := make([]*zap.Logger, 0, len(hosts))
125 for _, loggerName := range hosts {
126 // no name, use the default logger
127 if loggerName == "" {
128 loggers = append(loggers, logger)
129 continue
130 }
131 // make a logger with the given name
132 loggers = append(loggers, logger.Named(loggerName))
133 }
134 return loggers
135 }
136
137 func (slc ServerLogConfig) getLoggerHosts(host string) []string {
138 // hostnames are case insensitive, keys were lowercased during provisioning
139 host = strings.ToLower(host)
140
141 // try the exact hostname first
142 if hosts, ok := slc.LoggerNames[host]; ok {
143 return hosts
144 }
145
146 // try matching wildcard domains if other non-specific loggers exist
147 labels := strings.Split(host, ".")
148 for i := range labels {
149 if labels[i] == "" {
150 continue
151 }
152 labels[i] = "*"
153 wildcardHost := strings.Join(labels, ".")
154 if hosts, ok := slc.LoggerNames[wildcardHost]; ok {
155 return hosts
156 }
157 }
158
159 return []string{slc.DefaultLoggerName}
160 }
161
162 // normalizeLoggerNames lowercases the hostname keys of LoggerNames
163 // because hostnames are case-insensitive. When two keys differ only
164 // by case, their logger names are merged
165 func (slc *ServerLogConfig) normalizeLoggerNames() {
166 if len(slc.LoggerNames) == 0 {
167 return
168 }
169 normalized := make(map[string]StringArray, len(slc.LoggerNames))
170 // iterate in sorted order so merged logger names are deterministic
171 for _, host := range slices.Sorted(maps.Keys(slc.LoggerNames)) {
172 lowerHost := strings.ToLower(host)
173 merged := normalized[lowerHost]
174 for _, name := range slc.LoggerNames[host] {
175 if !slices.Contains(merged, name) {
176 merged = append(merged, name)
177 }
178 }
179 // keep empty mappings, they disable logging for the host
180 normalized[lowerHost] = merged
181 }
182 slc.LoggerNames = normalized
183 }
184
185 func (slc *ServerLogConfig) clone() *ServerLogConfig {
186 clone := &ServerLogConfig{
187 DefaultLoggerName: slc.DefaultLoggerName,
188 LoggerNames: make(map[string]StringArray),
189 SkipHosts: append([]string{}, slc.SkipHosts...),
190 SkipUnmappedHosts: slc.SkipUnmappedHosts,
191 ShouldLogCredentials: slc.ShouldLogCredentials,
192 }
193 for k, v := range slc.LoggerNames {
194 clone.LoggerNames[k] = append([]string{}, v...)
195 }
196 return clone
197 }
198
199 // StringArray is a slices of strings, but also accepts
200 // a single string as a value when JSON unmarshaling,
201 // converting it to a slice of one string.
202 type StringArray []string
203
204 // UnmarshalJSON satisfies json.Unmarshaler.
205 func (sa *StringArray) UnmarshalJSON(b []byte) error {
206 var jsonObj any
207 err := json.Unmarshal(b, &jsonObj)
208 if err != nil {
209 return err
210 }
211 switch obj := jsonObj.(type) {
212 case string:
213 *sa = StringArray([]string{obj})
214 return nil
215 case []any:
216 s := make([]string, 0, len(obj))
217 for _, v := range obj {
218 value, ok := v.(string)
219 if !ok {
220 return errors.New("unsupported type")
221 }
222 s = append(s, value)
223 }
224 *sa = StringArray(s)
225 return nil
226 }
227 return errors.New("unsupported type")
228 }
229
230 // errLogValues inspects err and returns the status code
231 // to use, the error log message, and any extra fields.
232 // If err is a HandlerError, the returned values will
233 // have richer information.
234 func errLogValues(err error) (status int, msg string, fields func() []zapcore.Field) {
235 if handlerErr, ok := errors.AsType[HandlerError](err); ok {
236 status = handlerErr.StatusCode
237 if handlerErr.Err == nil {
238 msg = err.Error()
239 } else {
240 msg = handlerErr.Err.Error()
241 }
242 fields = func() []zapcore.Field {
243 return []zapcore.Field{
244 zap.Int("status", handlerErr.StatusCode),
245 zap.String("err_id", handlerErr.ID),
246 zap.String("err_trace", handlerErr.Trace),
247 }
248 }
249 return status, msg, fields
250 }
251 fields = func() []zapcore.Field {
252 return []zapcore.Field{
253 zap.Error(err),
254 }
255 }
256 status = http.StatusInternalServerError
257 msg = err.Error()
258 return status, msg, fields
259 }
260
261 // ExtraLogFields is a list of extra fields to log with every request.
262 type ExtraLogFields struct {
263 fields []zapcore.Field
264 handlers sync.Map
265 }
266
267 // Add adds a field to the list of extra fields to log.
268 func (e *ExtraLogFields) Add(field zap.Field) {
269 e.handlers.Clear()
270 e.fields = append(e.fields, field)
271 }
272
273 // Set sets a field in the list of extra fields to log.
274 // If the field already exists, it is replaced.
275 func (e *ExtraLogFields) Set(field zap.Field) {
276 e.handlers.Clear()
277
278 for i := range e.fields {
279 if e.fields[i].Key == field.Key {
280 e.fields[i] = field
281 return
282 }
283 }
284 e.fields = append(e.fields, field)
285 }
286
287 func (e *ExtraLogFields) getSloggerHandler(handler *extraFieldsSlogHandler) (h slog.Handler) {
288 if existing, ok := e.handlers.Load(handler); ok {
289 return existing.(slog.Handler)
290 }
291
292 if handler.moduleID == "" {
293 h = zapslog.NewHandler(handler.core.With(e.fields))
294 } else {
295 h = zapslog.NewHandler(handler.core.With(e.fields), zapslog.WithName(handler.moduleID))
296 }
297
298 if handler.group != "" {
299 h = h.WithGroup(handler.group)
300 }
301 if handler.attrs != nil {
302 h = h.WithAttrs(handler.attrs)
303 }
304
305 e.handlers.Store(handler, h)
306
307 return h
308 }
309
310 const (
311 // Variable name used to indicate that this request
312 // should be omitted from the access logs
313 LogSkipVar string = "log_skip"
314
315 // LogLevelVar is the request variable used to override access log severity.
316 // Supported string values are DEBUG, INFO, WARN, and ERROR (case-insensitive).
317 LogLevelVar string = "log_level"
318
319 // For adding additional fields to the access logs
320 ExtraLogFieldsCtxKey caddy.CtxKey = "extra_log_fields"
321
322 // Variable name used to indicate the logger to be used
323 AccessLoggerNameVarKey string = "access_logger_names"
324 )
325
326 type extraFieldsSlogHandler struct {
327 defaultHandler slog.Handler
328 core zapcore.Core
329 moduleID string
330 group string
331 attrs []slog.Attr
332 }
333
334 func (e *extraFieldsSlogHandler) Enabled(ctx context.Context, level slog.Level) bool {
335 return e.defaultHandler.Enabled(ctx, level)
336 }
337
338 func (e *extraFieldsSlogHandler) Handle(ctx context.Context, record slog.Record) error {
339 if elf, ok := ctx.Value(ExtraLogFieldsCtxKey).(*ExtraLogFields); ok {
340 return elf.getSloggerHandler(e).Handle(ctx, record)
341 }
342
343 return e.defaultHandler.Handle(ctx, record)
344 }
345
346 func (e *extraFieldsSlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
347 return &extraFieldsSlogHandler{
348 e.defaultHandler.WithAttrs(attrs),
349 e.core,
350 e.moduleID,
351 e.group,
352 append(e.attrs, attrs...),
353 }
354 }
355
356 func (e *extraFieldsSlogHandler) WithGroup(name string) slog.Handler {
357 return &extraFieldsSlogHandler{
358 e.defaultHandler.WithGroup(name),
359 e.core,
360 e.moduleID,
361 name,
362 e.attrs,
363 }
364 }