mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-03 19:17:29 -05:00 
			
		
		
		
	
		
			Some checks failed
		
		
	
	Tests / test (./cmd/caddy/caddy, ~1.22.3, ubuntu-latest, 0, 1.22, linux) (push) Failing after 1m35s
				
			Tests / test (./cmd/caddy/caddy, ~1.23.0, ubuntu-latest, 0, 1.23, linux) (push) Failing after 1m20s
				
			Tests / test (s390x on IBM Z) (push) Has been skipped
				
			Tests / goreleaser-check (push) Has been skipped
				
			Cross-Build / build (~1.22.3, 1.22, aix) (push) Successful in 1m22s
				
			Cross-Build / build (~1.22.3, 1.22, darwin) (push) Successful in 1m25s
				
			Cross-Build / build (~1.22.3, 1.22, dragonfly) (push) Successful in 1m22s
				
			Cross-Build / build (~1.22.3, 1.22, freebsd) (push) Successful in 1m26s
				
			Cross-Build / build (~1.22.3, 1.22, illumos) (push) Successful in 1m21s
				
			Cross-Build / build (~1.22.3, 1.22, linux) (push) Successful in 1m30s
				
			Cross-Build / build (~1.22.3, 1.22, netbsd) (push) Successful in 1m20s
				
			Cross-Build / build (~1.22.3, 1.22, openbsd) (push) Successful in 1m20s
				
			Cross-Build / build (~1.22.3, 1.22, solaris) (push) Successful in 1m21s
				
			Cross-Build / build (~1.22.3, 1.22, windows) (push) Successful in 1m23s
				
			Cross-Build / build (~1.23.0, 1.23, aix) (push) Successful in 1m18s
				
			Cross-Build / build (~1.23.0, 1.23, darwin) (push) Successful in 1m13s
				
			Cross-Build / build (~1.23.0, 1.23, dragonfly) (push) Successful in 1m24s
				
			Cross-Build / build (~1.23.0, 1.23, freebsd) (push) Successful in 1m12s
				
			Cross-Build / build (~1.23.0, 1.23, illumos) (push) Successful in 1m14s
				
			Cross-Build / build (~1.23.0, 1.23, linux) (push) Successful in 1m13s
				
			Cross-Build / build (~1.23.0, 1.23, netbsd) (push) Successful in 1m13s
				
			Cross-Build / build (~1.23.0, 1.23, openbsd) (push) Successful in 1m15s
				
			Cross-Build / build (~1.23.0, 1.23, solaris) (push) Successful in 1m12s
				
			Cross-Build / build (~1.23.0, 1.23, windows) (push) Successful in 1m13s
				
			Lint / lint (ubuntu-latest, linux) (push) Successful in 2m16s
				
			Lint / govulncheck (push) Successful in 1m9s
				
			Tests / test (./cmd/caddy/caddy, ~1.22.3, macos-14, 0, 1.22, mac) (push) Has been cancelled
				
			Tests / test (./cmd/caddy/caddy, ~1.23.0, macos-14, 0, 1.23, mac) (push) Has been cancelled
				
			Tests / test (./cmd/caddy/caddy.exe, ~1.22.3, windows-latest, True, 1.22, windows) (push) Has been cancelled
				
			Tests / test (./cmd/caddy/caddy.exe, ~1.23.0, windows-latest, True, 1.23, windows) (push) Has been cancelled
				
			Lint / lint (macos-14, mac) (push) Has been cancelled
				
			Lint / lint (windows-latest, windows) (push) Has been cancelled
				
			This reverts commit 932dac157a3c4693b80576477498bb86208b9b30. Somehow the code I was looking at changed when I committed, without realizing it. This has already been fixed in #6777.
		
			
				
	
	
		
			257 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			257 lines
		
	
	
		
			7.8 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
 | 
						|
	}
 | 
						|
	fields = func() []zapcore.Field {
 | 
						|
		return []zapcore.Field{
 | 
						|
			zap.Error(err),
 | 
						|
		}
 | 
						|
	}
 | 
						|
	status = http.StatusInternalServerError
 | 
						|
	msg = err.Error()
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// 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"
 | 
						|
)
 |