mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 18:30:56 +08:00 
			
		
		
		
	 25be2f26fc
			
		
	
	25be2f26fc
	
	
	
		
			
			* chore: ugh, lint fix... Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * more lint fixes Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> --------- Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
		
			
				
	
	
		
			257 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			257 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 Matthew Holt and The Caddy Authors
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //     http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package caddyhttp
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"strings"
 | |
| 
 | |
| 	"go.uber.org/zap"
 | |
| 	"go.uber.org/zap/zapcore"
 | |
| 
 | |
| 	"github.com/caddyserver/caddy/v2"
 | |
| )
 | |
| 
 | |
| // ServerLogConfig describes a server's logging configuration. If
 | |
| // enabled without customization, all requests to this server are
 | |
| // logged to the default logger; logger destinations may be
 | |
| // customized per-request-host.
 | |
| type ServerLogConfig struct {
 | |
| 	// The default logger name for all logs emitted by this server for
 | |
| 	// hostnames that are not in the logger_names map.
 | |
| 	DefaultLoggerName string `json:"default_logger_name,omitempty"`
 | |
| 
 | |
| 	// LoggerNames maps request hostnames to one or more custom logger
 | |
| 	// names. For example, a mapping of `"example.com": ["example"]` would
 | |
| 	// cause access logs from requests with a Host of example.com to be
 | |
| 	// emitted by a logger named "http.log.access.example". If there are
 | |
| 	// multiple logger names, then the log will be emitted to all of them.
 | |
| 	// If the logger name is an empty, the default logger is used, i.e.
 | |
| 	// the logger "http.log.access".
 | |
| 	//
 | |
| 	// Keys must be hostnames (without ports), and may contain wildcards
 | |
| 	// to match subdomains. The value is an array of logger names.
 | |
| 	//
 | |
| 	// For backwards compatibility, if the value is a string, it is treated
 | |
| 	// as a single-element array.
 | |
| 	LoggerNames map[string]StringArray `json:"logger_names,omitempty"`
 | |
| 
 | |
| 	// By default, all requests to this server will be logged if
 | |
| 	// access logging is enabled. This field lists the request
 | |
| 	// hosts for which access logging should be disabled.
 | |
| 	SkipHosts []string `json:"skip_hosts,omitempty"`
 | |
| 
 | |
| 	// If true, requests to any host not appearing in the
 | |
| 	// logger_names map will not be logged.
 | |
| 	SkipUnmappedHosts bool `json:"skip_unmapped_hosts,omitempty"`
 | |
| 
 | |
| 	// If true, credentials that are otherwise omitted, will be logged.
 | |
| 	// The definition of credentials is defined by https://fetch.spec.whatwg.org/#credentials,
 | |
| 	// and this includes some request and response headers, i.e `Cookie`,
 | |
| 	// `Set-Cookie`, `Authorization`, and `Proxy-Authorization`.
 | |
| 	ShouldLogCredentials bool `json:"should_log_credentials,omitempty"`
 | |
| 
 | |
| 	// Log each individual handler that is invoked.
 | |
| 	// Requires that the log emit at DEBUG level.
 | |
| 	//
 | |
| 	// NOTE: This may log the configuration of your
 | |
| 	// HTTP handler modules; do not enable this in
 | |
| 	// insecure contexts when there is sensitive
 | |
| 	// data in the configuration.
 | |
| 	//
 | |
| 	// EXPERIMENTAL: Subject to change or removal.
 | |
| 	Trace bool `json:"trace,omitempty"`
 | |
| }
 | |
| 
 | |
| // wrapLogger wraps logger in one or more logger named
 | |
| // according to user preferences for the given host.
 | |
| func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, req *http.Request) []*zap.Logger {
 | |
| 	// using the `log_name` directive or the `access_logger_names` variable,
 | |
| 	// the logger names can be overridden for the current request
 | |
| 	if names := GetVar(req.Context(), AccessLoggerNameVarKey); names != nil {
 | |
| 		if namesSlice, ok := names.([]any); ok {
 | |
| 			loggers := make([]*zap.Logger, 0, len(namesSlice))
 | |
| 			for _, loggerName := range namesSlice {
 | |
| 				// no name, use the default logger
 | |
| 				if loggerName == "" {
 | |
| 					loggers = append(loggers, logger)
 | |
| 					continue
 | |
| 				}
 | |
| 				// make a logger with the given name
 | |
| 				loggers = append(loggers, logger.Named(loggerName.(string)))
 | |
| 			}
 | |
| 			return loggers
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// get the hostname from the request, with the port number stripped
 | |
| 	host, _, err := net.SplitHostPort(req.Host)
 | |
| 	if err != nil {
 | |
| 		host = req.Host
 | |
| 	}
 | |
| 
 | |
| 	// get the logger names for this host from the config
 | |
| 	hosts := slc.getLoggerHosts(host)
 | |
| 
 | |
| 	// make a list of named loggers, or the default logger
 | |
| 	loggers := make([]*zap.Logger, 0, len(hosts))
 | |
| 	for _, loggerName := range hosts {
 | |
| 		// no name, use the default logger
 | |
| 		if loggerName == "" {
 | |
| 			loggers = append(loggers, logger)
 | |
| 			continue
 | |
| 		}
 | |
| 		// make a logger with the given name
 | |
| 		loggers = append(loggers, logger.Named(loggerName))
 | |
| 	}
 | |
| 	return loggers
 | |
| }
 | |
| 
 | |
| func (slc ServerLogConfig) getLoggerHosts(host string) []string {
 | |
| 	// try the exact hostname first
 | |
| 	if hosts, ok := slc.LoggerNames[host]; ok {
 | |
| 		return hosts
 | |
| 	}
 | |
| 
 | |
| 	// try matching wildcard domains if other non-specific loggers exist
 | |
| 	labels := strings.Split(host, ".")
 | |
| 	for i := range labels {
 | |
| 		if labels[i] == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 		labels[i] = "*"
 | |
| 		wildcardHost := strings.Join(labels, ".")
 | |
| 		if hosts, ok := slc.LoggerNames[wildcardHost]; ok {
 | |
| 			return hosts
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return []string{slc.DefaultLoggerName}
 | |
| }
 | |
| 
 | |
| func (slc *ServerLogConfig) clone() *ServerLogConfig {
 | |
| 	clone := &ServerLogConfig{
 | |
| 		DefaultLoggerName:    slc.DefaultLoggerName,
 | |
| 		LoggerNames:          make(map[string]StringArray),
 | |
| 		SkipHosts:            append([]string{}, slc.SkipHosts...),
 | |
| 		SkipUnmappedHosts:    slc.SkipUnmappedHosts,
 | |
| 		ShouldLogCredentials: slc.ShouldLogCredentials,
 | |
| 	}
 | |
| 	for k, v := range slc.LoggerNames {
 | |
| 		clone.LoggerNames[k] = append([]string{}, v...)
 | |
| 	}
 | |
| 	return clone
 | |
| }
 | |
| 
 | |
| // StringArray is a slices of strings, but also accepts
 | |
| // a single string as a value when JSON unmarshaling,
 | |
| // converting it to a slice of one string.
 | |
| type StringArray []string
 | |
| 
 | |
| // UnmarshalJSON satisfies json.Unmarshaler.
 | |
| func (sa *StringArray) UnmarshalJSON(b []byte) error {
 | |
| 	var jsonObj any
 | |
| 	err := json.Unmarshal(b, &jsonObj)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	switch obj := jsonObj.(type) {
 | |
| 	case string:
 | |
| 		*sa = StringArray([]string{obj})
 | |
| 		return nil
 | |
| 	case []any:
 | |
| 		s := make([]string, 0, len(obj))
 | |
| 		for _, v := range obj {
 | |
| 			value, ok := v.(string)
 | |
| 			if !ok {
 | |
| 				return errors.New("unsupported type")
 | |
| 			}
 | |
| 			s = append(s, value)
 | |
| 		}
 | |
| 		*sa = StringArray(s)
 | |
| 		return nil
 | |
| 	}
 | |
| 	return errors.New("unsupported type")
 | |
| }
 | |
| 
 | |
| // errLogValues inspects err and returns the status code
 | |
| // to use, the error log message, and any extra fields.
 | |
| // If err is a HandlerError, the returned values will
 | |
| // have richer information.
 | |
| func errLogValues(err error) (status int, msg string, fields func() []zapcore.Field) {
 | |
| 	var handlerErr HandlerError
 | |
| 	if errors.As(err, &handlerErr) {
 | |
| 		status = handlerErr.StatusCode
 | |
| 		if handlerErr.Err == nil {
 | |
| 			msg = err.Error()
 | |
| 		} else {
 | |
| 			msg = handlerErr.Err.Error()
 | |
| 		}
 | |
| 		fields = func() []zapcore.Field {
 | |
| 			return []zapcore.Field{
 | |
| 				zap.Int("status", handlerErr.StatusCode),
 | |
| 				zap.String("err_id", handlerErr.ID),
 | |
| 				zap.String("err_trace", handlerErr.Trace),
 | |
| 			}
 | |
| 		}
 | |
| 		return status, msg, fields
 | |
| 	}
 | |
| 	fields = func() []zapcore.Field {
 | |
| 		return []zapcore.Field{
 | |
| 			zap.Error(err),
 | |
| 		}
 | |
| 	}
 | |
| 	status = http.StatusInternalServerError
 | |
| 	msg = err.Error()
 | |
| 	return status, msg, fields
 | |
| }
 | |
| 
 | |
| // ExtraLogFields is a list of extra fields to log with every request.
 | |
| type ExtraLogFields struct {
 | |
| 	fields []zapcore.Field
 | |
| }
 | |
| 
 | |
| // Add adds a field to the list of extra fields to log.
 | |
| func (e *ExtraLogFields) Add(field zap.Field) {
 | |
| 	e.fields = append(e.fields, field)
 | |
| }
 | |
| 
 | |
| // Set sets a field in the list of extra fields to log.
 | |
| // If the field already exists, it is replaced.
 | |
| func (e *ExtraLogFields) Set(field zap.Field) {
 | |
| 	for i := range e.fields {
 | |
| 		if e.fields[i].Key == field.Key {
 | |
| 			e.fields[i] = field
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 	e.fields = append(e.fields, field)
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	// Variable name used to indicate that this request
 | |
| 	// should be omitted from the access logs
 | |
| 	LogSkipVar string = "log_skip"
 | |
| 
 | |
| 	// For adding additional fields to the access logs
 | |
| 	ExtraLogFieldsCtxKey caddy.CtxKey = "extra_log_fields"
 | |
| 
 | |
| 	// Variable name used to indicate the logger to be used
 | |
| 	AccessLoggerNameVarKey string = "access_logger_names"
 | |
| )
 |