mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 10:37:24 -04:00 
			
		
		
		
	v2: Logging! (#2831)
* logging: Initial implementation * logging: More encoder formats, better defaults * logging: Fix repetition bug with FilterEncoder; add more presets * logging: DiscardWriter; delete or no-op logs that discard their output * logging: Add http.handlers.log module; enhance Replacer methods The Replacer interface has new methods to customize how to handle empty or unrecognized placeholders. Closes #2815. * logging: Overhaul HTTP logging, fix bugs, improve filtering, etc. * logging: General cleanup, begin transitioning to using new loggers * Fixes after merge conflict
This commit is contained in:
		
							parent
							
								
									6c533558a3
								
							
						
					
					
						commit
						b00dfd3965
					
				
							
								
								
									
										17
									
								
								admin.go
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								admin.go
									
									
									
									
									
								
							| @ -34,6 +34,7 @@ import ( | ||||
| 	"github.com/caddyserver/caddy/v2/caddyconfig" | ||||
| 	"github.com/mholt/certmagic" | ||||
| 	"github.com/rs/cors" | ||||
| 	"go.uber.org/zap" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| @ -52,6 +53,8 @@ var DefaultAdminConfig = &AdminConfig{ | ||||
| 	Listen: DefaultAdminListen, | ||||
| } | ||||
| 
 | ||||
| // TODO: holy smokes, the admin endpoint might not have to live in caddy's core. | ||||
| 
 | ||||
| // StartAdmin starts Caddy's administration endpoint, | ||||
| // bootstrapping it with an optional configuration | ||||
| // in the format of JSON bytes. It opens a listener | ||||
| @ -113,7 +116,15 @@ func StartAdmin(initialConfigJSON []byte) error { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	handler := cors.Default().Handler(mux) | ||||
| 	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
| 		// TODO: improve/organize this logging | ||||
| 		Log().Named("admin.request").Info("", | ||||
| 			zap.String("method", r.Method), | ||||
| 			zap.String("uri", r.RequestURI), | ||||
| 			zap.String("remote", r.RemoteAddr), | ||||
| 		) | ||||
| 		cors.Default().Handler(mux).ServeHTTP(w, r) | ||||
| 	}) | ||||
| 
 | ||||
| 	cfgEndptSrv = &http.Server{ | ||||
| 		Handler:           handler, | ||||
| @ -125,14 +136,14 @@ func StartAdmin(initialConfigJSON []byte) error { | ||||
| 
 | ||||
| 	go cfgEndptSrv.Serve(ln) | ||||
| 
 | ||||
| 	log.Println("Caddy 2 admin endpoint listening on", adminConfig.Listen) | ||||
| 	fmt.Println("Caddy 2 admin endpoint listening on", adminConfig.Listen) | ||||
| 
 | ||||
| 	if len(initialConfigJSON) > 0 { | ||||
| 		err := Load(bytes.NewReader(initialConfigJSON)) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("loading initial config: %v", err) | ||||
| 		} | ||||
| 		log.Println("Caddy 2 serving initial configuration") | ||||
| 		fmt.Println("Caddy 2 serving initial configuration") | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
|  | ||||
							
								
								
									
										41
									
								
								caddy.go
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								caddy.go
									
									
									
									
									
								
							| @ -30,15 +30,12 @@ import ( | ||||
| // Config represents a Caddy configuration. | ||||
| type Config struct { | ||||
| 	Admin      *AdminConfig               `json:"admin,omitempty"` | ||||
| 
 | ||||
| 	Logging    *Logging                   `json:"logging,omitempty"` | ||||
| 	StorageRaw json.RawMessage            `json:"storage,omitempty"` | ||||
| 	storage    certmagic.Storage | ||||
| 
 | ||||
| 	AppsRaw    map[string]json.RawMessage `json:"apps,omitempty"` | ||||
| 
 | ||||
| 	// apps stores the decoded Apps values, | ||||
| 	// keyed by module name. | ||||
| 	apps    map[string]App | ||||
| 	storage certmagic.Storage | ||||
| 
 | ||||
| 	cancelFunc context.CancelFunc | ||||
| } | ||||
| @ -75,7 +72,11 @@ func Run(newCfg *Config) error { | ||||
| // is performed if any modules were provisioned; | ||||
| // apps that were started already will be stopped, | ||||
| // so this function should not leak resources if | ||||
| // an error is returned. | ||||
| // an error is returned. However, if no error is | ||||
| // returned and start == false, you should cancel | ||||
| // the config if you are not going to start it, | ||||
| // so that each provisioned module will be | ||||
| // cleaned up. | ||||
| func run(newCfg *Config, start bool) error { | ||||
| 	if newCfg == nil { | ||||
| 		return nil | ||||
| @ -102,12 +103,30 @@ func run(newCfg *Config, start bool) error { | ||||
| 	ctx, cancel := NewContext(Context{Context: context.Background(), cfg: newCfg}) | ||||
| 	defer func() { | ||||
| 		if err != nil { | ||||
| 			cancel() // clean up now | ||||
| 			// if there were any errors during startup, | ||||
| 			// we should cancel the new context we created | ||||
| 			// since the associated config won't be used; | ||||
| 			// this will cause all modules that were newly | ||||
| 			// provisioned to clean themselves up | ||||
| 			cancel() | ||||
| 
 | ||||
| 			// also undo any other state changes we made | ||||
| 			if currentCfg != nil { | ||||
| 				certmagic.Default.Storage = currentCfg.storage | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| 	newCfg.cancelFunc = cancel // clean up later | ||||
| 
 | ||||
| 	// set up storage and make it CertMagic's default storage, too | ||||
| 	// set up logging before anything bad happens | ||||
| 	if newCfg.Logging != nil { | ||||
| 		err := newCfg.Logging.openLogs(ctx) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// set up global storage and make it CertMagic's default storage, too | ||||
| 	err = func() error { | ||||
| 		if newCfg.StorageRaw != nil { | ||||
| 			val, err := ctx.LoadModuleInline("module", "caddy.storage", newCfg.StorageRaw) | ||||
| @ -214,7 +233,11 @@ func unsyncedStop(cfg *Config) { | ||||
| // Validate loads, provisions, and validates | ||||
| // cfg, but does not start running it. | ||||
| func Validate(cfg *Config) error { | ||||
| 	return run(cfg, false) | ||||
| 	err := run(cfg, false) | ||||
| 	if err == nil { | ||||
| 		cfg.cancelFunc() // call Cleanup on all modules | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // Duration is a JSON-string-unmarshable duration type. | ||||
|  | ||||
| @ -40,6 +40,7 @@ import ( | ||||
| 	_ "github.com/caddyserver/caddy/v2/modules/caddytls" | ||||
| 	_ "github.com/caddyserver/caddy/v2/modules/caddytls/standardstek" | ||||
| 	_ "github.com/caddyserver/caddy/v2/modules/filestorage" | ||||
| 	_ "github.com/caddyserver/caddy/v2/modules/logging" | ||||
| ) | ||||
| 
 | ||||
| func main() { | ||||
|  | ||||
| @ -387,7 +387,7 @@ func cmdAdaptConfig(fl Flags) (int, error) { | ||||
| 		if warn.Directive != "" { | ||||
| 			msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message) | ||||
| 		} | ||||
| 		log.Printf("[WARNING][%s] %s:%d: %s", adaptCmdAdapterFlag, warn.File, warn.Line, msg) | ||||
| 		fmt.Fprintf(os.Stderr, "[WARNING][%s] %s:%d: %s\n", adaptCmdAdapterFlag, warn.File, warn.Line, msg) | ||||
| 	} | ||||
| 
 | ||||
| 	// print result to stdout | ||||
| @ -436,7 +436,7 @@ func cmdValidateConfig(fl Flags) (int, error) { | ||||
| 			if warn.Directive != "" { | ||||
| 				msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message) | ||||
| 			} | ||||
| 			log.Printf("[WARNING][%s] %s:%d: %s", validateCmdAdapterFlag, warn.File, warn.Line, msg) | ||||
| 			fmt.Fprintf(os.Stderr, "[WARNING][%s] %s:%d: %s\n", validateCmdAdapterFlag, warn.File, warn.Line, msg) | ||||
| 		} | ||||
| 
 | ||||
| 		input = adaptedConfig | ||||
|  | ||||
| @ -130,9 +130,9 @@ not quit after printing, and can be useful for troubleshooting.`, | ||||
| 	RegisterCommand(Command{ | ||||
| 		Name:  "stop", | ||||
| 		Func:  cmdStop, | ||||
| 		Short: "Gracefully stops the running Caddy process", | ||||
| 		Short: "Gracefully stops a started Caddy process", | ||||
| 		Long: ` | ||||
| Stops the running Caddy process as gracefully as possible. | ||||
| Stops the background Caddy process as gracefully as possible. | ||||
| 
 | ||||
| On Windows, this stop is forceful and Caddy will not have an opportunity to | ||||
| clean up any active locks; for a graceful shutdown on Windows, use Ctrl+C | ||||
|  | ||||
							
								
								
									
										13
									
								
								cmd/main.go
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								cmd/main.go
									
									
									
									
									
								
							| @ -20,7 +20,6 @@ import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| @ -38,7 +37,7 @@ func Main() { | ||||
| 
 | ||||
| 	switch len(os.Args) { | ||||
| 	case 0: | ||||
| 		log.Printf("[FATAL] no arguments provided by OS; args[0] must be command") | ||||
| 		fmt.Printf("[FATAL] no arguments provided by OS; args[0] must be command\n") | ||||
| 		os.Exit(caddy.ExitCodeFailedStartup) | ||||
| 	case 1: | ||||
| 		os.Args = append(os.Args, "help") | ||||
| @ -49,9 +48,9 @@ func Main() { | ||||
| 	if !ok { | ||||
| 		if strings.HasPrefix(os.Args[1], "-") { | ||||
| 			// user probably forgot to type the subcommand | ||||
| 			log.Println("[ERROR] first argument must be a subcommand; see 'caddy help'") | ||||
| 			fmt.Println("[ERROR] first argument must be a subcommand; see 'caddy help'") | ||||
| 		} else { | ||||
| 			log.Printf("[ERROR] '%s' is not a recognized subcommand; see 'caddy help'", os.Args[1]) | ||||
| 			fmt.Printf("[ERROR] '%s' is not a recognized subcommand; see 'caddy help'\n", os.Args[1]) | ||||
| 		} | ||||
| 		os.Exit(caddy.ExitCodeFailedStartup) | ||||
| 	} | ||||
| @ -63,13 +62,13 @@ func Main() { | ||||
| 
 | ||||
| 	err := fs.Parse(os.Args[2:]) | ||||
| 	if err != nil { | ||||
| 		log.Println(err) | ||||
| 		fmt.Println(err) | ||||
| 		os.Exit(caddy.ExitCodeFailedStartup) | ||||
| 	} | ||||
| 
 | ||||
| 	exitCode, err := subcommand.Func(Flags{fs}) | ||||
| 	if err != nil { | ||||
| 		log.Printf("%s: %v", subcommand.Name, err) | ||||
| 		fmt.Printf("%s: %v\n", subcommand.Name, err) | ||||
| 	} | ||||
| 
 | ||||
| 	os.Exit(exitCode) | ||||
| @ -149,7 +148,7 @@ func loadConfig(configFile, adapterName string) ([]byte, error) { | ||||
| 			if warn.Directive != "" { | ||||
| 				msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message) | ||||
| 			} | ||||
| 			fmt.Printf("[WARNING][%s] %s:%d: %s", adapterName, warn.File, warn.Line, msg) | ||||
| 			fmt.Printf("[WARNING][%s] %s:%d: %s\n", adapterName, warn.File, warn.Line, msg) | ||||
| 		} | ||||
| 		config = adaptedConfig | ||||
| 	} | ||||
|  | ||||
| @ -24,7 +24,7 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| func gracefullyStopProcess(pid int) error { | ||||
| 	fmt.Printf("Graceful stop...") | ||||
| 	fmt.Printf("Graceful stop...\n") | ||||
| 	err := syscall.Kill(pid, syscall.SIGINT) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("kill: %v", err) | ||||
|  | ||||
| @ -23,7 +23,7 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| func gracefullyStopProcess(pid int) error { | ||||
| 	fmt.Printf("Forceful Stop...") | ||||
| 	fmt.Printf("Forceful Stop...\n") | ||||
| 	// process on windows will not stop unless forced with /f | ||||
| 	cmd := exec.Command("taskkill", "/pid", strconv.Itoa(pid), "/f") | ||||
| 	if err := cmd.Run(); err != nil { | ||||
|  | ||||
| @ -22,6 +22,7 @@ import ( | ||||
| 	"reflect" | ||||
| 
 | ||||
| 	"github.com/mholt/certmagic" | ||||
| 	"go.uber.org/zap" | ||||
| ) | ||||
| 
 | ||||
| // Context is a type which defines the lifetime of modules that | ||||
| @ -206,3 +207,8 @@ func (ctx Context) App(name string) (interface{}, error) { | ||||
| func (ctx Context) Storage() certmagic.Storage { | ||||
| 	return ctx.cfg.storage | ||||
| } | ||||
| 
 | ||||
| // Logger returns a logger that can be used by mod. | ||||
| func (ctx Context) Logger(mod Module) *zap.Logger { | ||||
| 	return ctx.cfg.Logging.Logger(mod) | ||||
| } | ||||
|  | ||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							| @ -11,18 +11,24 @@ require ( | ||||
| 	github.com/golang/groupcache v0.0.0-20191002201903-404acd9df4cc | ||||
| 	github.com/ilibs/json5 v1.0.1 | ||||
| 	github.com/imdario/mergo v0.3.8 // indirect | ||||
| 	github.com/jsternberg/zap-logfmt v1.2.0 | ||||
| 	github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19 | ||||
| 	github.com/klauspost/compress v1.8.6 | ||||
| 	github.com/klauspost/cpuid v1.2.1 | ||||
| 	github.com/lucas-clemente/quic-go v0.12.1 | ||||
| 	github.com/mholt/certmagic v0.8.3 | ||||
| 	github.com/muhammadmuzzammil1998/jsonc v0.0.0-20190906142622-1265e9b150c6 | ||||
| 	github.com/onsi/ginkgo v1.8.0 // indirect | ||||
| 	github.com/onsi/gomega v1.5.0 // indirect | ||||
| 	github.com/rs/cors v1.7.0 | ||||
| 	github.com/russross/blackfriday/v2 v2.0.1 | ||||
| 	github.com/starlight-go/starlight v0.0.0-20181207205707-b06f321544f3 | ||||
| 	github.com/vulcand/oxy v1.0.0 | ||||
| 	go.starlark.net v0.0.0-20190919145610-979af19b165c | ||||
| 	go.uber.org/multierr v1.2.0 // indirect | ||||
| 	go.uber.org/zap v1.10.0 | ||||
| 	golang.org/x/crypto v0.0.0-20191010185427-af544f31c8ac | ||||
| 	golang.org/x/net v0.0.0-20191009170851-d66e71096ffb | ||||
| 	golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0 | ||||
| 	gopkg.in/natefinch/lumberjack.v2 v2.0.0 | ||||
| ) | ||||
|  | ||||
							
								
								
									
										18
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								go.sum
									
									
									
									
									
								
							| @ -15,6 +15,7 @@ github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocm | ||||
| github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= | ||||
| github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= | ||||
| github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88= | ||||
| github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= | ||||
| github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | ||||
| github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= | ||||
| github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= | ||||
| @ -134,6 +135,8 @@ github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV | ||||
| github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= | ||||
| github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | ||||
| github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= | ||||
| github.com/jsternberg/zap-logfmt v1.2.0 h1:1v+PK4/B48cy8cfQbxL4FmmNZrjnIMr2BsnyEmXqv2o= | ||||
| github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0= | ||||
| github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= | ||||
| github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= | ||||
| github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19 h1:WjT3fLi9n8YWh/Ih8Q1LHAPsTqGddPcHqscN+PJ3i68= | ||||
| @ -201,13 +204,18 @@ github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXW | ||||
| github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||
| github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= | ||||
| github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||
| github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= | ||||
| github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||
| github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= | ||||
| github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= | ||||
| github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= | ||||
| github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= | ||||
| github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= | ||||
| github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= | ||||
| github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ= | ||||
| github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= | ||||
| github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= | ||||
| github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| @ -268,8 +276,15 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= | ||||
| go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | ||||
| go.starlark.net v0.0.0-20190919145610-979af19b165c h1:WR7X1xgXJlXhQBdorVc9Db3RhwG+J/kp6bLuMyJjfVw= | ||||
| go.starlark.net v0.0.0-20190919145610-979af19b165c/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= | ||||
| go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= | ||||
| go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= | ||||
| go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= | ||||
| go.uber.org/multierr v1.2.0 h1:6I+W7f5VwC5SV9dNrZ3qXrDB9mD0dyGOi/ZJmYw03T4= | ||||
| go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= | ||||
| go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= | ||||
| go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= | ||||
| go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= | ||||
| go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= | ||||
| golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| @ -369,6 +384,9 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy | ||||
| gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= | ||||
| gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||
| gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||
| gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= | ||||
| gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= | ||||
| gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc h1:GAcf+t0o8gdJAdSFYdE9wChu4bIyguMVqz0RHiFL5VY= | ||||
| gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw= | ||||
| gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc= | ||||
| gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= | ||||
|  | ||||
							
								
								
									
										610
									
								
								logging.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										610
									
								
								logging.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,610 @@ | ||||
| // 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 caddy | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"go.uber.org/zap" | ||||
| 	"go.uber.org/zap/zapcore" | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| 	RegisterModule(StdoutWriter{}) | ||||
| 	RegisterModule(StderrWriter{}) | ||||
| 	RegisterModule(DiscardWriter{}) | ||||
| } | ||||
| 
 | ||||
| // Logging facilitates logging within Caddy. | ||||
| type Logging struct { | ||||
| 	Sink *StandardLibLog       `json:"sink,omitempty"` | ||||
| 	Logs map[string]*CustomLog `json:"logs,omitempty"` | ||||
| 
 | ||||
| 	// a list of all keys for open writers; all writers | ||||
| 	// that are opened to provision this logging config | ||||
| 	// must have their keys added to this list so they | ||||
| 	// can be closed when cleaning up | ||||
| 	writerKeys []string | ||||
| } | ||||
| 
 | ||||
| // openLogs sets up the config and opens all the configured writers. | ||||
| // It closes its logs when ctx is cancelled, so it should clean up | ||||
| // after itself. | ||||
| func (logging *Logging) openLogs(ctx Context) error { | ||||
| 	// make sure to deallocate resources when context is done | ||||
| 	ctx.OnCancel(func() { | ||||
| 		err := logging.closeLogs() | ||||
| 		if err != nil { | ||||
| 			Log().Error("closing logs", zap.Error(err)) | ||||
| 		} | ||||
| 	}) | ||||
| 
 | ||||
| 	// set up the "sink" log first (std lib's default global logger) | ||||
| 	if logging.Sink != nil { | ||||
| 		err := logging.Sink.provision(ctx, logging) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("setting up sink log: %v", err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// as a special case, set up the default structured Caddy log next | ||||
| 	if err := logging.setupNewDefault(ctx); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// then set up any other custom logs | ||||
| 	for name, l := range logging.Logs { | ||||
| 		// the default log is already set up | ||||
| 		if name == "default" { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		err := l.provision(ctx, logging) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("setting up custom log '%s': %v", name, err) | ||||
| 		} | ||||
| 
 | ||||
| 		// Any other logs that use the discard writer can be deleted | ||||
| 		// entirely. This avoids encoding and processing of each | ||||
| 		// log entry that would just be thrown away anyway. Notably, | ||||
| 		// we do not reach this point for the default log, which MUST | ||||
| 		// exist, otherwise core log emissions would panic because | ||||
| 		// they use the Log() function directly which expects a non-nil | ||||
| 		// logger. Even if we keep logs with a discard writer, they | ||||
| 		// have a nop core, and keeping them at all seems unnecessary. | ||||
| 		if _, ok := l.writerOpener.(*DiscardWriter); ok { | ||||
| 			delete(logging.Logs, name) | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (logging *Logging) setupNewDefault(ctx Context) error { | ||||
| 	if logging.Logs == nil { | ||||
| 		logging.Logs = make(map[string]*CustomLog) | ||||
| 	} | ||||
| 
 | ||||
| 	// extract the user-defined default log, if any | ||||
| 	newDefault := new(defaultCustomLog) | ||||
| 	if userDefault, ok := logging.Logs["default"]; ok { | ||||
| 		newDefault.CustomLog = userDefault | ||||
| 	} else { | ||||
| 		// if none, make one with our own default settings | ||||
| 		var err error | ||||
| 		newDefault, err = newDefaultProductionLog() | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("setting up default Caddy log: %v", err) | ||||
| 		} | ||||
| 		logging.Logs["default"] = newDefault.CustomLog | ||||
| 	} | ||||
| 
 | ||||
| 	// set up this new log | ||||
| 	err := newDefault.CustomLog.provision(ctx, logging) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("setting up default log: %v", err) | ||||
| 	} | ||||
| 	newDefault.logger = zap.New(newDefault.CustomLog.core) | ||||
| 
 | ||||
| 	// redirect the default caddy logs | ||||
| 	defaultLoggerMu.Lock() | ||||
| 	oldDefault := defaultLogger | ||||
| 	defaultLogger = newDefault | ||||
| 	defaultLoggerMu.Unlock() | ||||
| 
 | ||||
| 	// if the new writer is different, indicate it in the logs for convenience | ||||
| 	var newDefaultLogWriterKey, currentDefaultLogWriterKey string | ||||
| 	var newDefaultLogWriterStr, currentDefaultLogWriterStr string | ||||
| 	if newDefault.writerOpener != nil { | ||||
| 		newDefaultLogWriterKey = newDefault.writerOpener.WriterKey() | ||||
| 		newDefaultLogWriterStr = newDefault.writerOpener.String() | ||||
| 	} | ||||
| 	if oldDefault.writerOpener != nil { | ||||
| 		currentDefaultLogWriterKey = oldDefault.writerOpener.WriterKey() | ||||
| 		currentDefaultLogWriterStr = oldDefault.writerOpener.String() | ||||
| 	} | ||||
| 	if newDefaultLogWriterKey != currentDefaultLogWriterKey { | ||||
| 		oldDefault.logger.Info("redirected default logger", | ||||
| 			zap.String("from", currentDefaultLogWriterStr), | ||||
| 			zap.String("to", newDefaultLogWriterStr), | ||||
| 		) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // closeLogs cleans up resources allocated during openLogs. | ||||
| // A successful call to openLogs calls this automatically | ||||
| // when the context is cancelled. | ||||
| func (logging *Logging) closeLogs() error { | ||||
| 	for _, key := range logging.writerKeys { | ||||
| 		_, err := writers.Delete(key) | ||||
| 		if err != nil { | ||||
| 			log.Printf("[ERROR] Closing log writer %v: %v", key, err) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Logger returns a logger that is ready for the module to use. | ||||
| func (logging *Logging) Logger(mod Module) *zap.Logger { | ||||
| 	modName := mod.CaddyModule().Name | ||||
| 	var cores []zapcore.Core | ||||
| 
 | ||||
| 	if logging != nil { | ||||
| 		for _, l := range logging.Logs { | ||||
| 			if l.matchesModule(modName) { | ||||
| 				if len(l.Include) == 0 && len(l.Exclude) == 0 { | ||||
| 					cores = append(cores, l.core) | ||||
| 					continue | ||||
| 				} | ||||
| 				cores = append(cores, &filteringCore{Core: l.core, cl: l}) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	multiCore := zapcore.NewTee(cores...) | ||||
| 
 | ||||
| 	return zap.New(multiCore).Named(modName) | ||||
| } | ||||
| 
 | ||||
| // openWriter opens a writer using opener, and returns true if | ||||
| // the writer is new, or false if the writer already exists. | ||||
| func (logging *Logging) openWriter(opener WriterOpener) (io.WriteCloser, bool, error) { | ||||
| 	key := opener.WriterKey() | ||||
| 	writer, loaded, err := writers.LoadOrNew(key, func() (Destructor, error) { | ||||
| 		w, err := opener.OpenWriter() | ||||
| 		return writerDestructor{w}, err | ||||
| 	}) | ||||
| 	if err == nil { | ||||
| 		logging.writerKeys = append(logging.writerKeys, key) | ||||
| 	} | ||||
| 	return writer.(io.WriteCloser), !loaded, err | ||||
| } | ||||
| 
 | ||||
| // WriterOpener is a module that can open a log writer. | ||||
| // It can return a human-readable string representation | ||||
| // of itself so that operators can understand where | ||||
| // the logs are going. | ||||
| type WriterOpener interface { | ||||
| 	fmt.Stringer | ||||
| 
 | ||||
| 	// WriterKey is a string that uniquely identifies this | ||||
| 	// writer configuration. It is not shown to humans. | ||||
| 	WriterKey() string | ||||
| 
 | ||||
| 	// OpenWriter opens a log for writing. The writer | ||||
| 	// should be safe for concurrent use but need not | ||||
| 	// be synchronous. | ||||
| 	OpenWriter() (io.WriteCloser, error) | ||||
| } | ||||
| 
 | ||||
| type writerDestructor struct { | ||||
| 	io.WriteCloser | ||||
| } | ||||
| 
 | ||||
| func (wdest writerDestructor) Destruct() error { | ||||
| 	return wdest.Close() | ||||
| } | ||||
| 
 | ||||
| // StandardLibLog configures the default Go standard library | ||||
| // global logger in the log package. This is necessary because | ||||
| // module dependencies which are not built specifically for | ||||
| // Caddy will use the standard logger. | ||||
| type StandardLibLog struct { | ||||
| 	WriterRaw json.RawMessage `json:"writer,omitempty"` | ||||
| 
 | ||||
| 	writer io.WriteCloser | ||||
| } | ||||
| 
 | ||||
| func (sll *StandardLibLog) provision(ctx Context, logging *Logging) error { | ||||
| 	if sll.WriterRaw != nil { | ||||
| 		val, err := ctx.LoadModuleInline("output", "caddy.logging.writers", sll.WriterRaw) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("loading sink log writer module: %v", err) | ||||
| 		} | ||||
| 		wo := val.(WriterOpener) | ||||
| 		sll.WriterRaw = nil // allow GC to deallocate | ||||
| 
 | ||||
| 		var isNew bool | ||||
| 		sll.writer, isNew, err = logging.openWriter(wo) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("opening sink log writer %#v: %v", val, err) | ||||
| 		} | ||||
| 
 | ||||
| 		if isNew { | ||||
| 			log.Printf("[INFO] Redirecting sink to: %s", wo) | ||||
| 			log.SetOutput(sll.writer) | ||||
| 			log.Printf("[INFO] Redirected sink to here (%s)", wo) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // CustomLog represents a custom logger configuration. | ||||
| type CustomLog struct { | ||||
| 	WriterRaw  json.RawMessage `json:"writer,omitempty"` | ||||
| 	EncoderRaw json.RawMessage `json:"encoder,omitempty"` | ||||
| 	Level      string          `json:"level,omitempty"` | ||||
| 	Sampling   *LogSampling    `json:"sampling,omitempty"` | ||||
| 	Include    []string        `json:"include,omitempty"` | ||||
| 	Exclude    []string        `json:"exclude,omitempty"` | ||||
| 
 | ||||
| 	writerOpener WriterOpener | ||||
| 	writer       io.WriteCloser | ||||
| 	encoder      zapcore.Encoder | ||||
| 	levelEnabler zapcore.LevelEnabler | ||||
| 	core         zapcore.Core | ||||
| } | ||||
| 
 | ||||
| func (cl *CustomLog) provision(ctx Context, logging *Logging) error { | ||||
| 	// set up the log level | ||||
| 	switch cl.Level { | ||||
| 	case "debug": | ||||
| 		cl.levelEnabler = zapcore.DebugLevel | ||||
| 	case "", "info": | ||||
| 		cl.levelEnabler = zapcore.InfoLevel | ||||
| 	case "warn": | ||||
| 		cl.levelEnabler = zapcore.WarnLevel | ||||
| 	case "error": | ||||
| 		cl.levelEnabler = zapcore.ErrorLevel | ||||
| 	case "panic": | ||||
| 		cl.levelEnabler = zapcore.PanicLevel | ||||
| 	case "fatal": | ||||
| 		cl.levelEnabler = zapcore.FatalLevel | ||||
| 	default: | ||||
| 		return fmt.Errorf("unrecognized log level: %s", cl.Level) | ||||
| 	} | ||||
| 
 | ||||
| 	// If both Include and Exclude lists are populated, then each item must | ||||
| 	// be a superspace or subspace of an item in the other list, because | ||||
| 	// populating both lists means that any given item is either a rule | ||||
| 	// or an exception to another rule. But if the item is not a super- | ||||
| 	// or sub-space of any item in the other list, it is neither a rule | ||||
| 	// nor an exception, and is a contradiction. Ensure, too, that the | ||||
| 	// sets do not intersect, which is also a contradiction. | ||||
| 	if len(cl.Include) > 0 && len(cl.Exclude) > 0 { | ||||
| 		// prevent intersections | ||||
| 		for _, allow := range cl.Include { | ||||
| 			for _, deny := range cl.Exclude { | ||||
| 				if allow == deny { | ||||
| 					return fmt.Errorf("include and exclude must not intersect, but found %s in both lists", allow) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// ensure namespaces are nested | ||||
| 	outer: | ||||
| 		for _, allow := range cl.Include { | ||||
| 			for _, deny := range cl.Exclude { | ||||
| 				if strings.HasPrefix(allow+".", deny+".") || | ||||
| 					strings.HasPrefix(deny+".", allow+".") { | ||||
| 					continue outer | ||||
| 				} | ||||
| 			} | ||||
| 			return fmt.Errorf("when both include and exclude are populated, each element must be a superspace or subspace of one in the other list; check '%s' in include", allow) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if cl.EncoderRaw != nil { | ||||
| 		val, err := ctx.LoadModuleInline("format", "caddy.logging.encoders", cl.EncoderRaw) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("loading log encoder module: %v", err) | ||||
| 		} | ||||
| 		cl.EncoderRaw = nil // allow GC to deallocate | ||||
| 		cl.encoder = val.(zapcore.Encoder) | ||||
| 	} | ||||
| 	if cl.encoder == nil { | ||||
| 		cl.encoder = zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()) | ||||
| 	} | ||||
| 
 | ||||
| 	if cl.WriterRaw != nil { | ||||
| 		val, err := ctx.LoadModuleInline("output", "caddy.logging.writers", cl.WriterRaw) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("loading log writer module: %v", err) | ||||
| 		} | ||||
| 		cl.WriterRaw = nil // allow GC to deallocate | ||||
| 		cl.writerOpener = val.(WriterOpener) | ||||
| 	} | ||||
| 	if cl.writerOpener == nil { | ||||
| 		cl.writerOpener = StderrWriter{} | ||||
| 	} | ||||
| 	var err error | ||||
| 	cl.writer, _, err = logging.openWriter(cl.writerOpener) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("opening log writer using %#v: %v", cl.writerOpener, err) | ||||
| 	} | ||||
| 
 | ||||
| 	cl.buildCore() | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (cl *CustomLog) buildCore() { | ||||
| 	// logs which only discard their output don't need | ||||
| 	// to perform encoding or any other processing steps | ||||
| 	// at all, so just shorcut to a nop core instead | ||||
| 	if _, ok := cl.writerOpener.(*DiscardWriter); ok { | ||||
| 		cl.core = zapcore.NewNopCore() | ||||
| 		return | ||||
| 	} | ||||
| 	c := zapcore.NewCore( | ||||
| 		cl.encoder, | ||||
| 		zapcore.AddSync(cl.writer), | ||||
| 		cl.levelEnabler, | ||||
| 	) | ||||
| 	if cl.Sampling != nil { | ||||
| 		if cl.Sampling.Interval == 0 { | ||||
| 			cl.Sampling.Interval = 1 * time.Second | ||||
| 		} | ||||
| 		if cl.Sampling.First == 0 { | ||||
| 			cl.Sampling.First = 100 | ||||
| 		} | ||||
| 		if cl.Sampling.Thereafter == 0 { | ||||
| 			cl.Sampling.Thereafter = 100 | ||||
| 		} | ||||
| 		c = zapcore.NewSampler(c, cl.Sampling.Interval, | ||||
| 			cl.Sampling.First, cl.Sampling.Thereafter) | ||||
| 	} | ||||
| 	cl.core = c | ||||
| } | ||||
| 
 | ||||
| func (cl *CustomLog) matchesModule(moduleName string) bool { | ||||
| 	return cl.loggerAllowed(moduleName, true) | ||||
| } | ||||
| 
 | ||||
| // loggerAllowed returns true if name is allowed to emit | ||||
| // to cl. isModule should be true if name is the name of | ||||
| // a module and you want to see if ANY of that module's | ||||
| // logs would be permitted. | ||||
| func (cl *CustomLog) loggerAllowed(name string, isModule bool) bool { | ||||
| 	// accept all loggers by default | ||||
| 	if len(cl.Include) == 0 && len(cl.Exclude) == 0 { | ||||
| 		return true | ||||
| 	} | ||||
| 
 | ||||
| 	// append a dot so that partial names don't match | ||||
| 	// (i.e. we don't want "foo.b" to match "foo.bar"); we | ||||
| 	// will also have to append a dot when we do HasPrefix | ||||
| 	// below to compensate for when when namespaces are equal | ||||
| 	if name != "" && name != "*" && name != "." { | ||||
| 		name += "." | ||||
| 	} | ||||
| 
 | ||||
| 	var longestAccept, longestReject int | ||||
| 
 | ||||
| 	if len(cl.Include) > 0 { | ||||
| 		for _, namespace := range cl.Include { | ||||
| 			var hasPrefix bool | ||||
| 			if isModule { | ||||
| 				hasPrefix = strings.HasPrefix(namespace+".", name) | ||||
| 			} else { | ||||
| 				hasPrefix = strings.HasPrefix(name, namespace+".") | ||||
| 			} | ||||
| 			if hasPrefix && len(namespace) > longestAccept { | ||||
| 				longestAccept = len(namespace) | ||||
| 			} | ||||
| 		} | ||||
| 		// the include list was populated, meaning that | ||||
| 		// a match in this list is absolutely required | ||||
| 		// if we are to accept the entry | ||||
| 		if longestAccept == 0 { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if len(cl.Exclude) > 0 { | ||||
| 		for _, namespace := range cl.Exclude { | ||||
| 			// * == all logs emitted by modules | ||||
| 			// . == all logs emitted by core | ||||
| 			if (namespace == "*" && name != ".") || | ||||
| 				(namespace == "." && name == ".") { | ||||
| 				return false | ||||
| 			} | ||||
| 			if strings.HasPrefix(name, namespace+".") && | ||||
| 				len(namespace) > longestReject { | ||||
| 				longestReject = len(namespace) | ||||
| 			} | ||||
| 		} | ||||
| 		// the reject list is populated, so we have to | ||||
| 		// reject this entry if its match is better | ||||
| 		// than the best from the accept list | ||||
| 		if longestReject > longestAccept { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return (longestAccept > longestReject) || | ||||
| 		(len(cl.Include) == 0 && longestReject == 0) | ||||
| } | ||||
| 
 | ||||
| // filteringCore filters log entries based on logger name, | ||||
| // according to the rules of a CustomLog. | ||||
| type filteringCore struct { | ||||
| 	zapcore.Core | ||||
| 	cl *CustomLog | ||||
| } | ||||
| 
 | ||||
| // With properly wraps With. | ||||
| func (fc *filteringCore) With(fields []zapcore.Field) zapcore.Core { | ||||
| 	return &filteringCore{ | ||||
| 		Core: fc.Core.With(fields), | ||||
| 		cl:   fc.cl, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Check only allows the log entry if its logger name | ||||
| // is allowed from the include/exclude rules of fc.cl. | ||||
| func (fc *filteringCore) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { | ||||
| 	if fc.cl.loggerAllowed(e.LoggerName, false) { | ||||
| 		return fc.Core.Check(e, ce) | ||||
| 	} | ||||
| 	return ce | ||||
| } | ||||
| 
 | ||||
| // LogSampling configures log entry sampling. | ||||
| type LogSampling struct { | ||||
| 	Interval   time.Duration `json:"interval,omitempty"` | ||||
| 	First      int           `json:"first,omitempty"` | ||||
| 	Thereafter int           `json:"thereafter,omitempty"` | ||||
| } | ||||
| 
 | ||||
| type ( | ||||
| 	// StdoutWriter can write logs to stdout. | ||||
| 	StdoutWriter struct{} | ||||
| 
 | ||||
| 	// StderrWriter can write logs to stdout. | ||||
| 	StderrWriter struct{} | ||||
| 
 | ||||
| 	// DiscardWriter discards all writes. | ||||
| 	DiscardWriter struct{} | ||||
| ) | ||||
| 
 | ||||
| // CaddyModule returns the Caddy module information. | ||||
| func (StdoutWriter) CaddyModule() ModuleInfo { | ||||
| 	return ModuleInfo{ | ||||
| 		Name: "caddy.logging.writers.stdout", | ||||
| 		New:  func() Module { return new(StdoutWriter) }, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // CaddyModule returns the Caddy module information. | ||||
| func (StderrWriter) CaddyModule() ModuleInfo { | ||||
| 	return ModuleInfo{ | ||||
| 		Name: "caddy.logging.writers.stderr", | ||||
| 		New:  func() Module { return new(StderrWriter) }, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // CaddyModule returns the Caddy module information. | ||||
| func (DiscardWriter) CaddyModule() ModuleInfo { | ||||
| 	return ModuleInfo{ | ||||
| 		Name: "caddy.logging.writers.discard", | ||||
| 		New:  func() Module { return new(DiscardWriter) }, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (StdoutWriter) String() string  { return "stdout" } | ||||
| func (StderrWriter) String() string  { return "stderr" } | ||||
| func (DiscardWriter) String() string { return "discard" } | ||||
| 
 | ||||
| // WriterKey returns a unique key representing stdout. | ||||
| func (StdoutWriter) WriterKey() string { return "std:out" } | ||||
| 
 | ||||
| // WriterKey returns a unique key representing stderr. | ||||
| func (StderrWriter) WriterKey() string { return "std:err" } | ||||
| 
 | ||||
| // WriterKey returns a unique key representing discard. | ||||
| func (DiscardWriter) WriterKey() string { return "discard" } | ||||
| 
 | ||||
| // OpenWriter returns os.Stdout that can't be closed. | ||||
| func (StdoutWriter) OpenWriter() (io.WriteCloser, error) { | ||||
| 	return notClosable{os.Stdout}, nil | ||||
| } | ||||
| 
 | ||||
| // OpenWriter returns os.Stderr that can't be closed. | ||||
| func (StderrWriter) OpenWriter() (io.WriteCloser, error) { | ||||
| 	return notClosable{os.Stderr}, nil | ||||
| } | ||||
| 
 | ||||
| // OpenWriter returns ioutil.Discard that can't be closed. | ||||
| func (DiscardWriter) OpenWriter() (io.WriteCloser, error) { | ||||
| 	return notClosable{ioutil.Discard}, nil | ||||
| } | ||||
| 
 | ||||
| // notClosable is an io.WriteCloser that can't be closed. | ||||
| type notClosable struct{ io.Writer } | ||||
| 
 | ||||
| func (fc notClosable) Close() error { return nil } | ||||
| 
 | ||||
| type defaultCustomLog struct { | ||||
| 	*CustomLog | ||||
| 	logger *zap.Logger | ||||
| } | ||||
| 
 | ||||
| // newDefaultProductionLog configures a custom log that is | ||||
| // intended for use by default if no other log is specified | ||||
| // in a config. It writes to stderr, uses the console encoder, | ||||
| // and enables INFO-level logs and higher. | ||||
| func newDefaultProductionLog() (*defaultCustomLog, error) { | ||||
| 	cl := new(CustomLog) | ||||
| 	cl.writerOpener = StderrWriter{} | ||||
| 	var err error | ||||
| 	cl.writer, err = cl.writerOpener.OpenWriter() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	encCfg := zap.NewProductionEncoderConfig() | ||||
| 	cl.encoder = zapcore.NewConsoleEncoder(encCfg) | ||||
| 	cl.levelEnabler = zapcore.InfoLevel | ||||
| 
 | ||||
| 	cl.buildCore() | ||||
| 
 | ||||
| 	return &defaultCustomLog{ | ||||
| 		CustomLog: cl, | ||||
| 		logger:    zap.New(cl.core), | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| // Log returns the current default logger. | ||||
| func Log() *zap.Logger { | ||||
| 	defaultLoggerMu.RLock() | ||||
| 	defer defaultLoggerMu.RUnlock() | ||||
| 	return defaultLogger.logger | ||||
| } | ||||
| 
 | ||||
| var ( | ||||
| 	defaultLogger, _ = newDefaultProductionLog() | ||||
| 	defaultLoggerMu  sync.RWMutex | ||||
| ) | ||||
| 
 | ||||
| var writers = NewUsagePool() | ||||
| 
 | ||||
| // Interface guards | ||||
| var ( | ||||
| 	_ io.WriteCloser = (*notClosable)(nil) | ||||
| 	_ WriterOpener   = (*StdoutWriter)(nil) | ||||
| 	_ WriterOpener   = (*StderrWriter)(nil) | ||||
| ) | ||||
| @ -21,7 +21,6 @@ import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	weakrand "math/rand" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| @ -33,6 +32,7 @@ import ( | ||||
| 	"github.com/caddyserver/caddy/v2/modules/caddytls" | ||||
| 	"github.com/lucas-clemente/quic-go/http3" | ||||
| 	"github.com/mholt/certmagic" | ||||
| 	"go.uber.org/zap" | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| @ -40,7 +40,7 @@ func init() { | ||||
| 
 | ||||
| 	err := caddy.RegisterModule(App{}) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 		caddy.Log().Fatal(err.Error()) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -56,6 +56,7 @@ type App struct { | ||||
| 	h3listeners []net.PacketConn | ||||
| 
 | ||||
| 	ctx    caddy.Context | ||||
| 	logger *zap.Logger | ||||
| } | ||||
| 
 | ||||
| // CaddyModule returns the Caddy module information. | ||||
| @ -69,10 +70,15 @@ func (App) CaddyModule() caddy.ModuleInfo { | ||||
| // Provision sets up the app. | ||||
| func (app *App) Provision(ctx caddy.Context) error { | ||||
| 	app.ctx = ctx | ||||
| 	app.logger = ctx.Logger(app) | ||||
| 
 | ||||
| 	repl := caddy.NewReplacer() | ||||
| 
 | ||||
| 	for _, srv := range app.Servers { | ||||
| 	for srvName, srv := range app.Servers { | ||||
| 		srv.logger = app.logger.Named("log") | ||||
| 		srv.accessLogger = app.logger.Named("log.access") | ||||
| 		srv.errorLogger = app.logger.Named("log.error") | ||||
| 
 | ||||
| 		if srv.AutoHTTPS == nil { | ||||
| 			// avoid nil pointer dereferences | ||||
| 			srv.AutoHTTPS = new(AutoHTTPSConfig) | ||||
| @ -93,20 +99,25 @@ func (app *App) Provision(ctx caddy.Context) error { | ||||
| 		} | ||||
| 
 | ||||
| 		for i := range srv.Listen { | ||||
| 			srv.Listen[i] = repl.ReplaceAll(srv.Listen[i], "") | ||||
| 			lnOut, err := repl.ReplaceOrErr(srv.Listen[i], true, true) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("server %s, listener %d: %v", | ||||
| 					srvName, i, err) | ||||
| 			} | ||||
| 			srv.Listen[i] = lnOut | ||||
| 		} | ||||
| 
 | ||||
| 		if srv.Routes != nil { | ||||
| 			err := srv.Routes.Provision(ctx) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("setting up server routes: %v", err) | ||||
| 				return fmt.Errorf("server %s: setting up server routes: %v", srvName, err) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if srv.Errors != nil { | ||||
| 			err := srv.Errors.Routes.Provision(ctx) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("setting up server error handling routes: %v", err) | ||||
| 				return fmt.Errorf("server %s: setting up server error handling routes: %v", srvName, err) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| @ -194,7 +205,9 @@ func (app *App) Start() error { | ||||
| 					///////// | ||||
| 					// TODO: HTTP/3 support is experimental for now | ||||
| 					if srv.ExperimentalHTTP3 { | ||||
| 						log.Printf("[INFO] Enabling experimental HTTP/3 listener on %s", addr) | ||||
| 						app.logger.Info("enabling experimental HTTP/3 listener", | ||||
| 							zap.String("addr", addr), | ||||
| 						) | ||||
| 						h3ln, err := caddy.ListenPacket("udp", addr) | ||||
| 						if err != nil { | ||||
| 							return fmt.Errorf("getting HTTP/3 UDP listener: %v", err) | ||||
| @ -284,19 +297,25 @@ func (app *App) automaticHTTPS() error { | ||||
| 
 | ||||
| 		// skip if all listeners use the HTTP port | ||||
| 		if !srv.listenersUseAnyPortOtherThan(app.httpPort()) { | ||||
| 			log.Printf("[INFO] Server %s is only listening on the HTTP port %d, so no automatic HTTPS will be applied to this server", | ||||
| 				srvName, app.httpPort()) | ||||
| 			app.logger.Info("server is only listening on the HTTP port, so no automatic HTTPS will be applied to this server", | ||||
| 				zap.String("server_name", srvName), | ||||
| 				zap.Int("http_port", app.httpPort()), | ||||
| 			) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// find all qualifying domain names, de-duplicated | ||||
| 		domainSet := make(map[string]struct{}) | ||||
| 		for _, route := range srv.Routes { | ||||
| 			for _, matcherSet := range route.MatcherSets { | ||||
| 				for _, m := range matcherSet { | ||||
| 		for routeIdx, route := range srv.Routes { | ||||
| 			for matcherSetIdx, matcherSet := range route.MatcherSets { | ||||
| 				for matcherIdx, m := range matcherSet { | ||||
| 					if hm, ok := m.(*MatchHost); ok { | ||||
| 						for _, d := range *hm { | ||||
| 							d = repl.ReplaceAll(d, "") | ||||
| 						for hostMatcherIdx, d := range *hm { | ||||
| 							d, err = repl.ReplaceOrErr(d, true, false) | ||||
| 							if err != nil { | ||||
| 								return fmt.Errorf("%s: route %d, matcher set %d, matcher %d, host matcher %d: %v", | ||||
| 									srvName, routeIdx, matcherSetIdx, matcherIdx, hostMatcherIdx, err) | ||||
| 							} | ||||
| 							if certmagic.HostQualifies(d) && | ||||
| 								!srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.Skip) { | ||||
| 								domainSet[d] = struct{}{} | ||||
| @ -318,7 +337,10 @@ func (app *App) automaticHTTPS() error { | ||||
| 					// supposed to ignore loaded certificates | ||||
| 					if !srv.AutoHTTPS.IgnoreLoadedCerts && | ||||
| 						len(tlsApp.AllMatchingCertificates(d)) > 0 { | ||||
| 						log.Printf("[INFO][%s] Skipping automatic certificate management because one or more matching certificates are already loaded", d) | ||||
| 						app.logger.Info("skipping automatic certificate management because one or more matching certificates are already loaded", | ||||
| 							zap.String("domain", d), | ||||
| 							zap.String("server_name", srvName), | ||||
| 						) | ||||
| 						continue | ||||
| 					} | ||||
| 					domainsForCerts = append(domainsForCerts, d) | ||||
| @ -351,7 +373,9 @@ func (app *App) automaticHTTPS() error { | ||||
| 				}) | ||||
| 
 | ||||
| 			// manage their certificates | ||||
| 			log.Printf("[INFO] Enabling automatic TLS certificate management for %v", domainsForCerts) | ||||
| 			app.logger.Info("enabling automatic TLS certificate management", | ||||
| 				zap.Strings("domains", domainsForCerts), | ||||
| 			) | ||||
| 			err := tlsApp.Manage(domainsForCerts) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("%s: managing certificate for %s: %s", srvName, domains, err) | ||||
| @ -368,7 +392,9 @@ func (app *App) automaticHTTPS() error { | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			log.Printf("[INFO] Enabling automatic HTTP->HTTPS redirects for %v", domains) | ||||
| 			app.logger.Info("enabling automatic HTTP->HTTPS redirects", | ||||
| 				zap.Strings("domains", domains), | ||||
| 			) | ||||
| 
 | ||||
| 			// create HTTP->HTTPS redirects | ||||
| 			for _, addr := range srv.Listen { | ||||
| @ -434,8 +460,10 @@ func (app *App) automaticHTTPS() error { | ||||
| 					// that the redirect runs from; simply append our | ||||
| 					// redirect route to the existing routes, with a | ||||
| 					// caveat that their config might override ours | ||||
| 					log.Printf("[WARNING] Server %s is listening on %s, so automatic HTTP->HTTPS redirects might be overridden by your own configuration", | ||||
| 						srvName, addr) | ||||
| 					app.logger.Warn("server is listening on same interface as redirects, so automatic HTTP->HTTPS redirects might be overridden by your own configuration", | ||||
| 						zap.String("server_name", srvName), | ||||
| 						zap.String("interface", addr), | ||||
| 					) | ||||
| 					srv.Routes = append(srv.Routes, redirRoute) | ||||
| 					continue redirRoutesLoop | ||||
| 				} | ||||
| @ -524,8 +552,8 @@ var emptyHandler HandlerFunc = func(http.ResponseWriter, *http.Request) error { | ||||
| 
 | ||||
| // WeakString is a type that unmarshals any JSON value | ||||
| // as a string literal, with the following exceptions: | ||||
| // 1) actual string values are decoded as strings, and | ||||
| // 2) null is decoded as empty string | ||||
| // 1) actual string values are decoded as strings; and | ||||
| // 2) null is decoded as empty string; | ||||
| // and provides methods for getting the value as various | ||||
| // primitive types. However, using this type removes any | ||||
| // type safety as far as deserializing JSON is concerned. | ||||
|  | ||||
| @ -18,6 +18,7 @@ import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"html/template" | ||||
| 	"io" | ||||
| 	weakrand "math/rand" | ||||
| 	"mime" | ||||
| 	"net/http" | ||||
| @ -191,6 +192,24 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ cadd | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// if this handler exists in an error context (i.e. is | ||||
| 	// part of a handler chain that is supposed to handle | ||||
| 	// a previous error), we have to serve the content | ||||
| 	// manually in order to write the correct status code | ||||
| 	if reqErr, ok := r.Context().Value(caddyhttp.ErrorCtxKey).(error); ok { | ||||
| 		statusCode := http.StatusInternalServerError | ||||
| 		if handlerErr, ok := reqErr.(caddyhttp.HandlerError); ok { | ||||
| 			if handlerErr.StatusCode > 0 { | ||||
| 				statusCode = handlerErr.StatusCode | ||||
| 			} | ||||
| 		} | ||||
| 		w.WriteHeader(statusCode) | ||||
| 		if r.Method != "HEAD" { | ||||
| 			io.Copy(w, file) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	// let the standard library do what it does best; note, however, | ||||
| 	// that errors generated by ServeContent are written immediately | ||||
| 	// to the response, so we cannot handle them (but errors there | ||||
|  | ||||
							
								
								
									
										89
									
								
								modules/caddyhttp/marshalers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								modules/caddyhttp/marshalers.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| // 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 ( | ||||
| 	"crypto/tls" | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"go.uber.org/zap/zapcore" | ||||
| ) | ||||
| 
 | ||||
| // LoggableHTTPRequest makes an HTTP request loggable with zap.Object(). | ||||
| type LoggableHTTPRequest struct{ *http.Request } | ||||
| 
 | ||||
| // MarshalLogObject satisfies the zapcore.ObjectMarshaler interface. | ||||
| func (r LoggableHTTPRequest) MarshalLogObject(enc zapcore.ObjectEncoder) error { | ||||
| 	enc.AddString("method", r.Method) | ||||
| 	enc.AddString("uri", r.RequestURI) | ||||
| 	enc.AddString("proto", r.Proto) | ||||
| 	enc.AddString("remote_addr", r.RemoteAddr) | ||||
| 	enc.AddString("host", r.Host) | ||||
| 	enc.AddObject("headers", LoggableHTTPHeader(r.Header)) | ||||
| 	if r.TLS != nil { | ||||
| 		enc.AddObject("tls", LoggableTLSConnState(*r.TLS)) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // LoggableHTTPHeader makes an HTTP header loggable with zap.Object(). | ||||
| type LoggableHTTPHeader http.Header | ||||
| 
 | ||||
| // MarshalLogObject satisfies the zapcore.ObjectMarshaler interface. | ||||
| func (h LoggableHTTPHeader) MarshalLogObject(enc zapcore.ObjectEncoder) error { | ||||
| 	if h == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	for key, val := range h { | ||||
| 		enc.AddArray(key, LoggableStringArray(val)) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // LoggableStringArray makes a slice of strings marshalable for logging. | ||||
| type LoggableStringArray []string | ||||
| 
 | ||||
| // MarshalLogArray satisfies the zapcore.ArrayMarshaler interface. | ||||
| func (sa LoggableStringArray) MarshalLogArray(enc zapcore.ArrayEncoder) error { | ||||
| 	if sa == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	for _, s := range sa { | ||||
| 		enc.AppendString(s) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // LoggableTLSConnState makes a TLS connection state loggable with zap.Object(). | ||||
| type LoggableTLSConnState tls.ConnectionState | ||||
| 
 | ||||
| // MarshalLogObject satisfies the zapcore.ObjectMarshaler interface. | ||||
| func (t LoggableTLSConnState) MarshalLogObject(enc zapcore.ObjectEncoder) error { | ||||
| 	enc.AddBool("resumed", t.DidResume) | ||||
| 	enc.AddUint16("version", t.Version) | ||||
| 	enc.AddUint16("resumed", t.CipherSuite) | ||||
| 	enc.AddString("proto", t.NegotiatedProtocol) | ||||
| 	enc.AddBool("proto_mutual", t.NegotiatedProtocolIsMutual) | ||||
| 	enc.AddString("server_name", t.ServerName) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Interface guards | ||||
| var ( | ||||
| 	_ zapcore.ObjectMarshaler = (*LoggableHTTPRequest)(nil) | ||||
| 	_ zapcore.ObjectMarshaler = (*LoggableHTTPHeader)(nil) | ||||
| 	_ zapcore.ArrayMarshaler  = (*LoggableStringArray)(nil) | ||||
| 	_ zapcore.ObjectMarshaler = (*LoggableTLSConnState)(nil) | ||||
| ) | ||||
| @ -19,7 +19,6 @@ import ( | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/textproto" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| @ -112,27 +111,30 @@ func addHTTPVarsToReplacer(repl caddy.Replacer, req *http.Request, w http.Respon | ||||
| 				} | ||||
| 				return qs, true | ||||
| 
 | ||||
| 			// original URI, before any internal changes | ||||
| 				// original request, before any internal changes | ||||
| 			case "http.request.orig_method": | ||||
| 				or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) | ||||
| 				return or.Method, true | ||||
| 			case "http.request.orig_uri": | ||||
| 				u, _ := req.Context().Value(OriginalURLCtxKey).(url.URL) | ||||
| 				return u.RequestURI(), true | ||||
| 				or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) | ||||
| 				return or.RequestURI, true | ||||
| 			case "http.request.orig_uri.path": | ||||
| 				u, _ := req.Context().Value(OriginalURLCtxKey).(url.URL) | ||||
| 				return u.Path, true | ||||
| 				or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) | ||||
| 				return or.URL.Path, true | ||||
| 			case "http.request.orig_uri.path.file": | ||||
| 				u, _ := req.Context().Value(OriginalURLCtxKey).(url.URL) | ||||
| 				_, file := path.Split(u.Path) | ||||
| 				or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) | ||||
| 				_, file := path.Split(or.URL.Path) | ||||
| 				return file, true | ||||
| 			case "http.request.orig_uri.path.dir": | ||||
| 				u, _ := req.Context().Value(OriginalURLCtxKey).(url.URL) | ||||
| 				dir, _ := path.Split(u.Path) | ||||
| 				or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) | ||||
| 				dir, _ := path.Split(or.URL.Path) | ||||
| 				return dir, true | ||||
| 			case "http.request.orig_uri.query": | ||||
| 				u, _ := req.Context().Value(OriginalURLCtxKey).(url.URL) | ||||
| 				return u.RawQuery, true | ||||
| 				or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) | ||||
| 				return or.URL.RawQuery, true | ||||
| 			case "http.request.orig_uri.query_string": | ||||
| 				u, _ := req.Context().Value(OriginalURLCtxKey).(url.URL) | ||||
| 				qs := u.Query().Encode() | ||||
| 				or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request) | ||||
| 				qs := or.URL.Query().Encode() | ||||
| 				if qs != "" { | ||||
| 					qs = "?" + qs | ||||
| 				} | ||||
| @ -183,7 +185,7 @@ func addHTTPVarsToReplacer(repl caddy.Replacer, req *http.Request, w http.Respon | ||||
| 			// middleware variables | ||||
| 			if strings.HasPrefix(key, varsReplPrefix) { | ||||
| 				varName := key[len(varsReplPrefix):] | ||||
| 				tbl := req.Context().Value(VarCtxKey).(map[string]interface{}) | ||||
| 				tbl := req.Context().Value(VarsCtxKey).(map[string]interface{}) | ||||
| 				raw, ok := tbl[varName] | ||||
| 				if !ok { | ||||
| 					// variables can be dynamic, so always return true | ||||
|  | ||||
| @ -19,7 +19,6 @@ import ( | ||||
| 	"crypto/tls" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| @ -53,6 +52,9 @@ type Transport struct { | ||||
| 	// with the value of SplitPath. The first piece will be assumed as the | ||||
| 	// actual resource (CGI script) name, and the second piece will be set to | ||||
| 	// PATH_INFO for the CGI script to use. | ||||
| 	// Future enhancements should be careful to avoid CVE-2019-11043, | ||||
| 	// which can be mitigated with use of a try_files-like behavior | ||||
| 	// that 404's if the fastcgi path info is not found. | ||||
| 	SplitPath string `json:"split_path,omitempty"` | ||||
| 
 | ||||
| 	// Extra environment variables | ||||
| @ -191,12 +193,13 @@ func (t Transport) buildEnv(r *http.Request) (map[string]string, error) { | ||||
| 	// original URI in as the value of REQUEST_URI (the user can overwrite this | ||||
| 	// if desired). Most PHP apps seem to want the original URI. Besides, this is | ||||
| 	// how nginx defaults: http://stackoverflow.com/a/12485156/1048862 | ||||
| 	reqURL, ok := r.Context().Value(caddyhttp.OriginalURLCtxKey).(url.URL) | ||||
| 	origReq, ok := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request) | ||||
| 	if !ok { | ||||
| 		// some requests, like active health checks, don't add this to | ||||
| 		// the request context, so we can just use the current URL | ||||
| 		reqURL = *r.URL | ||||
| 		origReq = *r | ||||
| 	} | ||||
| 	reqURL := origReq.URL | ||||
| 
 | ||||
| 	requestScheme := "http" | ||||
| 	if r.TLS != nil { | ||||
|  | ||||
| @ -441,6 +441,15 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO: there should be an option to return an error if the response | ||||
| 	// matches some criteria; would solve https://github.com/caddyserver/caddy/issues/1447 | ||||
| 	// by allowing the backend to determine whether this server should treat | ||||
| 	// a 400+ status code as an error -- but we might need to be careful that | ||||
| 	// we do not affect the health status of the backend... still looking into | ||||
| 	// that; if we need to avoid that, we should return a particular error type | ||||
| 	// that the caller of this function checks for and only applies health | ||||
| 	// status changes if the error is not this special type | ||||
| 
 | ||||
| 	rw.WriteHeader(res.StatusCode) | ||||
| 
 | ||||
| 	err = h.copyResponse(rw, res.Body, h.flushInterval(req, res)) | ||||
|  | ||||
| @ -23,6 +23,7 @@ import ( | ||||
| 
 | ||||
| 	"github.com/caddyserver/caddy/v2" | ||||
| 	"github.com/caddyserver/caddy/v2/modules/caddyhttp" | ||||
| 	"go.uber.org/zap" | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| @ -39,6 +40,8 @@ type Rewrite struct { | ||||
| 
 | ||||
| 	HTTPRedirect caddyhttp.WeakString `json:"http_redirect,omitempty"` | ||||
| 	Rehandle     bool                 `json:"rehandle,omitempty"` | ||||
| 
 | ||||
| 	logger *zap.Logger | ||||
| } | ||||
| 
 | ||||
| // CaddyModule returns the Caddy module information. | ||||
| @ -49,6 +52,12 @@ func (Rewrite) CaddyModule() caddy.ModuleInfo { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Provision sets up rewr. | ||||
| func (rewr *Rewrite) Provision(ctx caddy.Context) error { | ||||
| 	rewr.logger = ctx.Logger(rewr) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Validate ensures rewr's configuration is valid. | ||||
| func (rewr Rewrite) Validate() error { | ||||
| 	if rewr.HTTPRedirect != "" && rewr.Rehandle { | ||||
| @ -61,6 +70,10 @@ func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy | ||||
| 	repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) | ||||
| 	var changed bool | ||||
| 
 | ||||
| 	logger := rewr.logger.With( | ||||
| 		zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: r}), | ||||
| 	) | ||||
| 
 | ||||
| 	// rewrite the method | ||||
| 	if rewr.Method != "" { | ||||
| 		method := r.Method | ||||
| @ -114,11 +127,15 @@ func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy | ||||
| 		r.RequestURI = newURI | ||||
| 	} | ||||
| 
 | ||||
| 	if changed && rewr.Rehandle { | ||||
| 	if changed { | ||||
| 		logger.Debug("rewrote request", | ||||
| 			zap.String("method", r.Method), | ||||
| 			zap.String("uri", r.RequestURI), | ||||
| 		) | ||||
| 		if rewr.Rehandle { | ||||
| 			return caddyhttp.ErrRehandle | ||||
| 		} | ||||
| 
 | ||||
| 	if changed && rewr.HTTPRedirect != "" { | ||||
| 		if rewr.HTTPRedirect != "" { | ||||
| 			statusCode, err := strconv.Atoi(repl.ReplaceAll(rewr.HTTPRedirect.String(), "")) | ||||
| 			if err != nil { | ||||
| 				return caddyhttp.Error(http.StatusInternalServerError, err) | ||||
| @ -127,6 +144,7 @@ func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy | ||||
| 			w.WriteHeader(statusCode) | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return next.ServeHTTP(w, r) | ||||
| } | ||||
|  | ||||
| @ -17,16 +17,18 @@ package caddyhttp | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/caddyserver/caddy/v2" | ||||
| 	"github.com/caddyserver/caddy/v2/modules/caddytls" | ||||
| 	"github.com/lucas-clemente/quic-go/http3" | ||||
| 	"go.uber.org/zap" | ||||
| 	"go.uber.org/zap/zapcore" | ||||
| ) | ||||
| 
 | ||||
| // Server is an HTTP server. | ||||
| @ -43,11 +45,15 @@ type Server struct { | ||||
| 	AutoHTTPS         *AutoHTTPSConfig            `json:"automatic_https,omitempty"` | ||||
| 	MaxRehandles      *int                        `json:"max_rehandles,omitempty"` | ||||
| 	StrictSNIHost     *bool                       `json:"strict_sni_host,omitempty"` | ||||
| 	Logs              *ServerLogConfig            `json:"logs,omitempty"` | ||||
| 
 | ||||
| 	// This field is not subject to compatibility promises | ||||
| 	ExperimentalHTTP3 bool `json:"experimental_http3,omitempty"` | ||||
| 
 | ||||
| 	tlsApp       *caddytls.TLS | ||||
| 	logger       *zap.Logger | ||||
| 	accessLogger *zap.Logger | ||||
| 	errorLogger  *zap.Logger | ||||
| 
 | ||||
| 	h3server *http3.Server | ||||
| } | ||||
| @ -59,58 +65,106 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	if s.h3server != nil { | ||||
| 		err := s.h3server.SetQuicHeaders(w.Header()) | ||||
| 		if err != nil { | ||||
| 			log.Printf("[ERROR] Setting HTTP/3 Alt-Svc header: %v", err) | ||||
| 			s.logger.Error("setting HTTP/3 Alt-Svc header", zap.Error(err)) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if s.tlsApp.HandleHTTPChallenge(w, r) { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// set up the context for the request | ||||
| 	repl := caddy.NewReplacer() | ||||
| 	ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, repl) | ||||
| 	ctx = context.WithValue(ctx, ServerCtxKey, s) | ||||
| 	ctx = context.WithValue(ctx, VarCtxKey, make(map[string]interface{})) | ||||
| 	ctx = context.WithValue(ctx, OriginalURLCtxKey, cloneURL(r.URL)) | ||||
| 	ctx = context.WithValue(ctx, VarsCtxKey, make(map[string]interface{})) | ||||
| 	var url2 url.URL // avoid letting this escape to the heap | ||||
| 	ctx = context.WithValue(ctx, OriginalRequestCtxKey, originalRequest(r, &url2)) | ||||
| 	r = r.WithContext(ctx) | ||||
| 
 | ||||
| 	// once the pointer to the request won't change | ||||
| 	// anymore, finish setting up the replacer | ||||
| 	addHTTPVarsToReplacer(repl, r, w) | ||||
| 
 | ||||
| 	// build and execute the main handler chain | ||||
| 	loggableReq := LoggableHTTPRequest{r} | ||||
| 	errLog := s.errorLogger.With( | ||||
| 		// encode the request for logging purposes before | ||||
| 		// it enters any handler chain; this is necessary | ||||
| 		// to capture the original request in case it gets | ||||
| 		// modified during handling | ||||
| 		zap.Object("request", loggableReq), | ||||
| 	) | ||||
| 
 | ||||
| 	if s.Logs != nil { | ||||
| 		wrec := NewResponseRecorder(w, nil, nil) | ||||
| 		w = wrec | ||||
| 		accLog := s.accessLogger.With( | ||||
| 			// capture the original version of the request | ||||
| 			zap.Object("request", loggableReq), | ||||
| 		) | ||||
| 		start := time.Now() | ||||
| 		defer func() { | ||||
| 			latency := time.Since(start) | ||||
| 
 | ||||
| 			repl.Set("http.response.status", strconv.Itoa(wrec.Status())) | ||||
| 			repl.Set("http.response.size", strconv.Itoa(wrec.Size())) | ||||
| 			repl.Set("http.response.latency", latency.String()) | ||||
| 
 | ||||
| 			logger := accLog | ||||
| 			if s.Logs.LoggerNames != nil { | ||||
| 				logger = logger.Named(s.Logs.LoggerNames[r.Host]) | ||||
| 			} | ||||
| 
 | ||||
| 			log := logger.Info | ||||
| 			if wrec.Status() >= 400 { | ||||
| 				log = logger.Error | ||||
| 			} | ||||
| 
 | ||||
| 			log("request", | ||||
| 				zap.String("common_log", repl.ReplaceAll(CommonLogFormat, "-")), | ||||
| 				zap.Duration("latency", latency), | ||||
| 				zap.Int("size", wrec.Size()), | ||||
| 				zap.Int("status", wrec.Status()), | ||||
| 			) | ||||
| 		}() | ||||
| 	} | ||||
| 
 | ||||
| 	// guarantee ACME HTTP challenges; handle them | ||||
| 	// separately from any user-defined handlers | ||||
| 	if s.tlsApp.HandleHTTPChallenge(w, r) { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// build and execute the primary handler chain | ||||
| 	err := s.executeCompositeRoute(w, r, s.Routes) | ||||
| 	if err != nil { | ||||
| 		// add the raw error value to the request context | ||||
| 		// so it can be accessed by error handlers | ||||
| 		c := context.WithValue(r.Context(), ErrorCtxKey, err) | ||||
| 		r = r.WithContext(c) | ||||
| 
 | ||||
| 		// add error values to the replacer | ||||
| 		repl.Set("http.error", err.Error()) | ||||
| 		if handlerErr, ok := err.(HandlerError); ok { | ||||
| 			repl.Set("http.error.status_code", strconv.Itoa(handlerErr.StatusCode)) | ||||
| 			repl.Set("http.error.status_text", http.StatusText(handlerErr.StatusCode)) | ||||
| 			repl.Set("http.error.message", handlerErr.Message) | ||||
| 			repl.Set("http.error.trace", handlerErr.Trace) | ||||
| 			repl.Set("http.error.id", handlerErr.ID) | ||||
| 		// prepare the error log | ||||
| 		logger := errLog | ||||
| 		if s.Logs.LoggerNames != nil { | ||||
| 			logger = logger.Named(s.Logs.LoggerNames[r.Host]) | ||||
| 		} | ||||
| 
 | ||||
| 		// get the values that will be used to log the error | ||||
| 		errStatus, errMsg, errFields := errLogValues(err) | ||||
| 
 | ||||
| 		// add HTTP error information to request context | ||||
| 		r = s.Errors.WithError(r, err) | ||||
| 
 | ||||
| 		if s.Errors != nil && len(s.Errors.Routes) > 0 { | ||||
| 			err := s.executeCompositeRoute(w, r, s.Errors.Routes) | ||||
| 			if err != nil { | ||||
| 				// TODO: what should we do if the error handler has an error? | ||||
| 				log.Printf("[ERROR] [%s %s] handling error: %v", r.Method, r.RequestURI, err) | ||||
| 			// execute user-defined error handling route | ||||
| 			err2 := s.executeCompositeRoute(w, r, s.Errors.Routes) | ||||
| 			if err2 == nil { | ||||
| 				// user's error route handled the error response | ||||
| 				// successfully, so now just log the error | ||||
| 				logger.Error(errMsg, errFields...) | ||||
| 			} else { | ||||
| 				// well... this is awkward | ||||
| 				errFields = append([]zapcore.Field{ | ||||
| 					zap.String("error", err2.Error()), | ||||
| 					zap.Namespace("first_error"), | ||||
| 					zap.String("msg", errMsg), | ||||
| 				}, errFields...) | ||||
| 				logger.Error("error handling handler error", errFields...) | ||||
| 			} | ||||
| 		} else { | ||||
| 			// TODO: polish the default error handling | ||||
| 			log.Printf("[ERROR] [%s %s] %v", r.Method, r.RequestURI, err) | ||||
| 			if handlerErr, ok := err.(HandlerError); ok { | ||||
| 				w.WriteHeader(handlerErr.StatusCode) | ||||
| 			} else { | ||||
| 				w.WriteHeader(http.StatusInternalServerError) | ||||
| 			} | ||||
| 			logger.Error(errMsg, errFields...) | ||||
| 			w.WriteHeader(errStatus) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -278,26 +332,101 @@ type HTTPErrorConfig struct { | ||||
| 	Routes RouteList `json:"routes,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // WithError makes a shallow copy of r to add the error to its | ||||
| // context, and sets placeholders on the request's replacer | ||||
| // related to err. It returns the modified request which has | ||||
| // the error information in its context and replacer. It | ||||
| // overwrites any existing error values that are stored. | ||||
| func (*HTTPErrorConfig) WithError(r *http.Request, err error) *http.Request { | ||||
| 	// add the raw error value to the request context | ||||
| 	// so it can be accessed by error handlers | ||||
| 	c := context.WithValue(r.Context(), ErrorCtxKey, err) | ||||
| 	r = r.WithContext(c) | ||||
| 
 | ||||
| 	// add error values to the replacer | ||||
| 	repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) | ||||
| 	repl.Set("http.error", err.Error()) | ||||
| 	if handlerErr, ok := err.(HandlerError); ok { | ||||
| 		repl.Set("http.error.status_code", strconv.Itoa(handlerErr.StatusCode)) | ||||
| 		repl.Set("http.error.status_text", http.StatusText(handlerErr.StatusCode)) | ||||
| 		repl.Set("http.error.message", handlerErr.Message) | ||||
| 		repl.Set("http.error.trace", handlerErr.Trace) | ||||
| 		repl.Set("http.error.id", handlerErr.ID) | ||||
| 	} | ||||
| 
 | ||||
| 	return r | ||||
| } | ||||
| 
 | ||||
| // ServerLogConfig describes a server's logging configuration. | ||||
| type ServerLogConfig struct { | ||||
| 	LoggerNames map[string]string `json:"logger_names,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // 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 []zapcore.Field) { | ||||
| 	if handlerErr, ok := err.(HandlerError); ok { | ||||
| 		status = handlerErr.StatusCode | ||||
| 		msg = handlerErr.Err.Error() | ||||
| 		fields = []zapcore.Field{ | ||||
| 			zap.Int("status", handlerErr.StatusCode), | ||||
| 			zap.String("err_id", handlerErr.ID), | ||||
| 			zap.String("err_trace", handlerErr.Trace), | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	status = http.StatusInternalServerError | ||||
| 	msg = err.Error() | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // originalRequest returns a partial, shallow copy of | ||||
| // req, including: req.Method, deep copy of req.URL | ||||
| // (into the urlCopy parameter, which should be on the | ||||
| // stack), and req.RequestURI. Notably, headers are not | ||||
| // copied. This function is designed to be very fast | ||||
| // and efficient, and useful primarly for read-only | ||||
| // logging purposes. | ||||
| func originalRequest(req *http.Request, urlCopy *url.URL) http.Request { | ||||
| 	urlCopy = cloneURL(req.URL) | ||||
| 	return http.Request{ | ||||
| 		Method:     req.Method, | ||||
| 		RequestURI: req.RequestURI, | ||||
| 		URL:        urlCopy, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // cloneURL makes a copy of r.URL and returns a | ||||
| // new value that doesn't reference the original. | ||||
| func cloneURL(u *url.URL) url.URL { | ||||
| func cloneURL(u *url.URL) *url.URL { | ||||
| 	urlCopy := *u | ||||
| 	if u.User != nil { | ||||
| 		userInfo := new(url.Userinfo) | ||||
| 		*userInfo = *u.User | ||||
| 		urlCopy.User = userInfo | ||||
| 	} | ||||
| 	return urlCopy | ||||
| 	return &urlCopy | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	// CommonLogFormat is the common log format. https://en.wikipedia.org/wiki/Common_Log_Format | ||||
| 	CommonLogFormat = `{http.request.remote.host} ` + CommonLogEmptyValue + ` {http.handlers.authentication.user.id} [{time.now.common_log}] "{http.request.orig_method} {http.request.orig_uri} {http.request.proto}" {http.response.status} {http.response.size}` | ||||
| 
 | ||||
| 	// CommonLogEmptyValue is the common empty log value. | ||||
| 	CommonLogEmptyValue = "-" | ||||
| ) | ||||
| 
 | ||||
| // Context keys for HTTP request context values. | ||||
| const ( | ||||
| 	// For referencing the server instance | ||||
| 	ServerCtxKey caddy.CtxKey = "server" | ||||
| 
 | ||||
| 	// For the request's variable table | ||||
| 	VarCtxKey caddy.CtxKey = "vars" | ||||
| 	VarsCtxKey caddy.CtxKey = "vars" | ||||
| 
 | ||||
| 	// For the unmodified URL that originally came in with a request | ||||
| 	OriginalURLCtxKey caddy.CtxKey = "original_url" | ||||
| 	// For a partial copy of the unmodified request that | ||||
| 	// originally came into the server's entry handler | ||||
| 	OriginalRequestCtxKey caddy.CtxKey = "original_request" | ||||
| ) | ||||
|  | ||||
| @ -112,7 +112,7 @@ func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Hand | ||||
| 
 | ||||
| 	// write response body | ||||
| 	if s.Body != "" { | ||||
| 		fmt.Fprint(w, repl.ReplaceAll(s.Body, "")) | ||||
| 		fmt.Fprint(w, repl.ReplaceKnown(s.Body, "")) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
|  | ||||
| @ -30,8 +30,15 @@ func init() { | ||||
| // matchers, or for routes with matchers that must be have deferred | ||||
| // evaluation (e.g. if they depend on placeholders created by other | ||||
| // matchers that need to be evaluated first). | ||||
| // | ||||
| // You can also use subroutes to handle errors from specific handlers. | ||||
| // First the primary Routes will be executed, and if they return an | ||||
| // error, the Errors routes will be executed; in that case, an error | ||||
| // is only returned to the entry point at the server if there is an | ||||
| // additional error returned from the errors routes. | ||||
| type Subroute struct { | ||||
| 	Routes RouteList        `json:"routes,omitempty"` | ||||
| 	Errors *HTTPErrorConfig `json:"errors,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // CaddyModule returns the Caddy module information. | ||||
| @ -47,7 +54,13 @@ func (sr *Subroute) Provision(ctx caddy.Context) error { | ||||
| 	if sr.Routes != nil { | ||||
| 		err := sr.Routes.Provision(ctx) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("setting up routes: %v", err) | ||||
| 			return fmt.Errorf("setting up subroutes: %v", err) | ||||
| 		} | ||||
| 		if sr.Errors != nil { | ||||
| 			err := sr.Errors.Routes.Provision(ctx) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("setting up error subroutes: %v", err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| @ -55,7 +68,13 @@ func (sr *Subroute) Provision(ctx caddy.Context) error { | ||||
| 
 | ||||
| func (sr *Subroute) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error { | ||||
| 	subroute := sr.Routes.BuildCompositeRoute(r) | ||||
| 	return subroute.ServeHTTP(w, r) | ||||
| 	err := subroute.ServeHTTP(w, r) | ||||
| 	if err != nil && sr.Errors != nil { | ||||
| 		r = sr.Errors.WithError(r, err) | ||||
| 		errRoute := sr.Errors.Routes.BuildCompositeRoute(r) | ||||
| 		return errRoute.ServeHTTP(w, r) | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // Interface guards | ||||
|  | ||||
| @ -38,7 +38,7 @@ func (VarsMiddleware) CaddyModule() caddy.ModuleInfo { | ||||
| } | ||||
| 
 | ||||
| func (t VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error { | ||||
| 	vars := r.Context().Value(VarCtxKey).(map[string]interface{}) | ||||
| 	vars := r.Context().Value(VarsCtxKey).(map[string]interface{}) | ||||
| 	repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) | ||||
| 	for k, v := range t { | ||||
| 		keyExpanded := repl.ReplaceAll(k, "") | ||||
| @ -62,7 +62,7 @@ func (VarsMatcher) CaddyModule() caddy.ModuleInfo { | ||||
| 
 | ||||
| // Match matches a request based on variables in the context. | ||||
| func (m VarsMatcher) Match(r *http.Request) bool { | ||||
| 	vars := r.Context().Value(VarCtxKey).(map[string]string) | ||||
| 	vars := r.Context().Value(VarsCtxKey).(map[string]string) | ||||
| 	repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) | ||||
| 	for k, v := range m { | ||||
| 		keyExpanded := repl.ReplaceAll(k, "") | ||||
|  | ||||
| @ -18,7 +18,6 @@ import ( | ||||
| 	"crypto/tls" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| @ -26,6 +25,7 @@ import ( | ||||
| 	"github.com/caddyserver/caddy/v2" | ||||
| 	"github.com/go-acme/lego/v3/challenge" | ||||
| 	"github.com/mholt/certmagic" | ||||
| 	"go.uber.org/zap" | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| @ -43,6 +43,7 @@ type TLS struct { | ||||
| 	ctx                caddy.Context | ||||
| 	storageCleanTicker *time.Ticker | ||||
| 	storageCleanStop   chan struct{} | ||||
| 	logger             *zap.Logger | ||||
| } | ||||
| 
 | ||||
| // CaddyModule returns the Caddy module information. | ||||
| @ -56,6 +57,7 @@ func (TLS) CaddyModule() caddy.ModuleInfo { | ||||
| // Provision sets up the configuration for the TLS app. | ||||
| func (t *TLS) Provision(ctx caddy.Context) error { | ||||
| 	t.ctx = ctx | ||||
| 	t.logger = ctx.Logger(t) | ||||
| 
 | ||||
| 	// set up a new certificate cache; this (re)loads all certificates | ||||
| 	cacheOpts := certmagic.CacheOptions{ | ||||
| @ -290,7 +292,7 @@ func (t *TLS) cleanStorageUnits() { | ||||
| 
 | ||||
| 	storageClean = time.Now() | ||||
| 
 | ||||
| 	log.Println("[INFO] tls: Cleaned up storage unit(s)") | ||||
| 	t.logger.Info("cleaned up storage units") | ||||
| } | ||||
| 
 | ||||
| // CertificateLoader is a type that can load certificates. | ||||
|  | ||||
							
								
								
									
										268
									
								
								modules/logging/encoders.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								modules/logging/encoders.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,268 @@ | ||||
| // 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 logging | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/caddyserver/caddy/v2" | ||||
| 	zaplogfmt "github.com/jsternberg/zap-logfmt" | ||||
| 	"go.uber.org/zap" | ||||
| 	"go.uber.org/zap/buffer" | ||||
| 	"go.uber.org/zap/zapcore" | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| 	caddy.RegisterModule(ConsoleEncoder{}) | ||||
| 	caddy.RegisterModule(JSONEncoder{}) | ||||
| 	caddy.RegisterModule(LogfmtEncoder{}) | ||||
| 	caddy.RegisterModule(StringEncoder{}) | ||||
| } | ||||
| 
 | ||||
| // ConsoleEncoder encodes log entries that are mostly human-readable. | ||||
| type ConsoleEncoder struct { | ||||
| 	zapcore.Encoder | ||||
| 	LogEncoderConfig | ||||
| } | ||||
| 
 | ||||
| // CaddyModule returns the Caddy module information. | ||||
| func (ConsoleEncoder) CaddyModule() caddy.ModuleInfo { | ||||
| 	return caddy.ModuleInfo{ | ||||
| 		Name: "caddy.logging.encoders.console", | ||||
| 		New:  func() caddy.Module { return new(ConsoleEncoder) }, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Provision sets up the encoder. | ||||
| func (ce *ConsoleEncoder) Provision(_ caddy.Context) error { | ||||
| 	ce.Encoder = zapcore.NewConsoleEncoder(ce.ZapcoreEncoderConfig()) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // JSONEncoder encodes entries as JSON. | ||||
| type JSONEncoder struct { | ||||
| 	zapcore.Encoder | ||||
| 	*LogEncoderConfig | ||||
| } | ||||
| 
 | ||||
| // CaddyModule returns the Caddy module information. | ||||
| func (JSONEncoder) CaddyModule() caddy.ModuleInfo { | ||||
| 	return caddy.ModuleInfo{ | ||||
| 		Name: "caddy.logging.encoders.json", | ||||
| 		New:  func() caddy.Module { return new(JSONEncoder) }, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Provision sets up the encoder. | ||||
| func (je *JSONEncoder) Provision(_ caddy.Context) error { | ||||
| 	je.Encoder = zapcore.NewJSONEncoder(je.ZapcoreEncoderConfig()) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // LogfmtEncoder encodes log entries as logfmt: | ||||
| // https://www.brandur.org/logfmt | ||||
| type LogfmtEncoder struct { | ||||
| 	zapcore.Encoder | ||||
| 	LogEncoderConfig | ||||
| } | ||||
| 
 | ||||
| // CaddyModule returns the Caddy module information. | ||||
| func (LogfmtEncoder) CaddyModule() caddy.ModuleInfo { | ||||
| 	return caddy.ModuleInfo{ | ||||
| 		Name: "caddy.logging.encoders.logfmt", | ||||
| 		New:  func() caddy.Module { return new(LogfmtEncoder) }, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Provision sets up the encoder. | ||||
| func (lfe *LogfmtEncoder) Provision(_ caddy.Context) error { | ||||
| 	lfe.Encoder = zaplogfmt.NewEncoder(lfe.ZapcoreEncoderConfig()) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // StringEncoder writes a log entry that consists entirely | ||||
| // of a single string field in the log entry. This is useful | ||||
| // for custom, self-encoded log entries that consist of a | ||||
| // single field in the structured log entry. | ||||
| type StringEncoder struct { | ||||
| 	zapcore.Encoder | ||||
| 	FieldName   string          `json:"field,omitempty"` | ||||
| 	FallbackRaw json.RawMessage `json:"fallback,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // CaddyModule returns the Caddy module information. | ||||
| func (StringEncoder) CaddyModule() caddy.ModuleInfo { | ||||
| 	return caddy.ModuleInfo{ | ||||
| 		Name: "caddy.logging.encoders.string", | ||||
| 		New:  func() caddy.Module { return new(StringEncoder) }, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Provision sets up the encoder. | ||||
| func (se *StringEncoder) Provision(ctx caddy.Context) error { | ||||
| 	if se.FallbackRaw != nil { | ||||
| 		val, err := ctx.LoadModuleInline("format", "caddy.logging.encoders", se.FallbackRaw) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("loading fallback encoder module: %v", err) | ||||
| 		} | ||||
| 		se.FallbackRaw = nil // allow GC to deallocate | ||||
| 		se.Encoder = val.(zapcore.Encoder) | ||||
| 	} | ||||
| 	if se.Encoder == nil { | ||||
| 		se.Encoder = nopEncoder{} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Clone wraps the underlying encoder's Clone. This is | ||||
| // necessary because we implement our own EncodeEntry, | ||||
| // and if we simply let the embedded encoder's Clone | ||||
| // be promoted, it would return a clone of that, and | ||||
| // we'd lose our StringEncoder's EncodeEntry. | ||||
| func (se StringEncoder) Clone() zapcore.Encoder { | ||||
| 	return StringEncoder{ | ||||
| 		Encoder:   se.Encoder.Clone(), | ||||
| 		FieldName: se.FieldName, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // EncodeEntry partially implements the zapcore.Encoder interface. | ||||
| func (se StringEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { | ||||
| 	for _, f := range fields { | ||||
| 		if f.Key == se.FieldName { | ||||
| 			buf := bufferpool.Get() | ||||
| 			buf.AppendString(f.String) | ||||
| 			if !strings.HasSuffix(f.String, "\n") { | ||||
| 				buf.AppendByte('\n') | ||||
| 			} | ||||
| 			return buf, nil | ||||
| 		} | ||||
| 	} | ||||
| 	if se.Encoder == nil { | ||||
| 		return nil, fmt.Errorf("no fallback encoder defined") | ||||
| 	} | ||||
| 	return se.Encoder.EncodeEntry(ent, fields) | ||||
| } | ||||
| 
 | ||||
| // LogEncoderConfig holds configuration common to most encoders. | ||||
| type LogEncoderConfig struct { | ||||
| 	MessageKey     *string `json:"message_key,omitempty"` | ||||
| 	LevelKey       *string `json:"level_key,omitempty"` | ||||
| 	TimeKey        *string `json:"time_key,omitempty"` | ||||
| 	NameKey        *string `json:"name_key,omitempty"` | ||||
| 	CallerKey      *string `json:"caller_key,omitempty"` | ||||
| 	StacktraceKey  *string `json:"stacktrace_key,omitempty"` | ||||
| 	LineEnding     *string `json:"line_ending,omitempty"` | ||||
| 	TimeFormat     string  `json:"time_format,omitempty"` | ||||
| 	DurationFormat string  `json:"duration_format,omitempty"` | ||||
| 	LevelFormat    string  `json:"level_format,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // ZapcoreEncoderConfig returns the equivalent zapcore.EncoderConfig. | ||||
| // If lec is nil, zap.NewProductionEncoderConfig() is returned. | ||||
| func (lec *LogEncoderConfig) ZapcoreEncoderConfig() zapcore.EncoderConfig { | ||||
| 	cfg := zap.NewProductionEncoderConfig() | ||||
| 	if lec == nil { | ||||
| 		lec = new(LogEncoderConfig) | ||||
| 	} | ||||
| 	if lec.MessageKey != nil { | ||||
| 		cfg.MessageKey = *lec.MessageKey | ||||
| 	} | ||||
| 	if lec.TimeKey != nil { | ||||
| 		cfg.TimeKey = *lec.TimeKey | ||||
| 	} | ||||
| 	if lec.NameKey != nil { | ||||
| 		cfg.NameKey = *lec.NameKey | ||||
| 	} | ||||
| 	if lec.CallerKey != nil { | ||||
| 		cfg.CallerKey = *lec.CallerKey | ||||
| 	} | ||||
| 	if lec.StacktraceKey != nil { | ||||
| 		cfg.StacktraceKey = *lec.StacktraceKey | ||||
| 	} | ||||
| 	if lec.LineEnding != nil { | ||||
| 		cfg.LineEnding = *lec.LineEnding | ||||
| 	} | ||||
| 
 | ||||
| 	// time format | ||||
| 	var timeFormatter zapcore.TimeEncoder | ||||
| 	switch lec.TimeFormat { | ||||
| 	case "", "unix_seconds_float": | ||||
| 		timeFormatter = zapcore.EpochTimeEncoder | ||||
| 	case "unix_milli_float": | ||||
| 		timeFormatter = zapcore.EpochMillisTimeEncoder | ||||
| 	case "unix_nano": | ||||
| 		timeFormatter = zapcore.EpochNanosTimeEncoder | ||||
| 	case "iso8601": | ||||
| 		timeFormatter = zapcore.ISO8601TimeEncoder | ||||
| 	default: | ||||
| 		timeFormat := lec.TimeFormat | ||||
| 		switch lec.TimeFormat { | ||||
| 		case "rfc3339": | ||||
| 			timeFormat = time.RFC3339 | ||||
| 		case "rfc3339_nano": | ||||
| 			timeFormat = time.RFC3339Nano | ||||
| 		case "wall": | ||||
| 			timeFormat = "2006/01/02 15:04:05" | ||||
| 		case "wall_milli": | ||||
| 			timeFormat = "2006/01/02 15:04:05.000" | ||||
| 		case "wall_nano": | ||||
| 			timeFormat = "2006/01/02 15:04:05.000000000" | ||||
| 		} | ||||
| 		timeFormatter = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) { | ||||
| 			encoder.AppendString(ts.UTC().Format(timeFormat)) | ||||
| 		} | ||||
| 	} | ||||
| 	cfg.EncodeTime = timeFormatter | ||||
| 
 | ||||
| 	// duration format | ||||
| 	var durFormatter zapcore.DurationEncoder | ||||
| 	switch lec.DurationFormat { | ||||
| 	case "", "seconds": | ||||
| 		durFormatter = zapcore.SecondsDurationEncoder | ||||
| 	case "nano": | ||||
| 		durFormatter = zapcore.NanosDurationEncoder | ||||
| 	case "string": | ||||
| 		durFormatter = zapcore.StringDurationEncoder | ||||
| 	} | ||||
| 	cfg.EncodeDuration = durFormatter | ||||
| 
 | ||||
| 	// level format | ||||
| 	var levelFormatter zapcore.LevelEncoder | ||||
| 	switch lec.LevelFormat { | ||||
| 	case "", "lower": | ||||
| 		levelFormatter = zapcore.LowercaseLevelEncoder | ||||
| 	case "upper": | ||||
| 		levelFormatter = zapcore.CapitalLevelEncoder | ||||
| 	case "color": | ||||
| 		levelFormatter = zapcore.CapitalColorLevelEncoder | ||||
| 	} | ||||
| 	cfg.EncodeLevel = levelFormatter | ||||
| 
 | ||||
| 	return cfg | ||||
| } | ||||
| 
 | ||||
| var bufferpool = buffer.NewPool() | ||||
| 
 | ||||
| // Interface guards | ||||
| var ( | ||||
| 	_ zapcore.Encoder = (*ConsoleEncoder)(nil) | ||||
| 	_ zapcore.Encoder = (*JSONEncoder)(nil) | ||||
| 	_ zapcore.Encoder = (*LogfmtEncoder)(nil) | ||||
| 	_ zapcore.Encoder = (*StringEncoder)(nil) | ||||
| ) | ||||
							
								
								
									
										91
									
								
								modules/logging/filewriter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								modules/logging/filewriter.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | ||||
| // 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 logging | ||||
| 
 | ||||
| import ( | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 
 | ||||
| 	"github.com/caddyserver/caddy/v2" | ||||
| 	"gopkg.in/natefinch/lumberjack.v2" | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| 	caddy.RegisterModule(FileWriter{}) | ||||
| } | ||||
| 
 | ||||
| // FileWriter can write logs to files. | ||||
| type FileWriter struct { | ||||
| 	Filename      string `json:"filename,omitempty"` | ||||
| 	Roll          *bool  `json:"roll,omitempty"` | ||||
| 	RollSizeMB    int    `json:"roll_size_mb,omitempty"` | ||||
| 	RollCompress  *bool  `json:"roll_gzip,omitempty"` | ||||
| 	RollLocalTime bool   `json:"roll_local_time,omitempty"` | ||||
| 	RollKeep      int    `json:"roll_keep,omitempty"` | ||||
| 	RollKeepDays  int    `json:"roll_keep_days,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // CaddyModule returns the Caddy module information. | ||||
| func (FileWriter) CaddyModule() caddy.ModuleInfo { | ||||
| 	return caddy.ModuleInfo{ | ||||
| 		Name: "caddy.logging.writers.file", | ||||
| 		New:  func() caddy.Module { return new(FileWriter) }, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (fw FileWriter) String() string { | ||||
| 	fpath, err := filepath.Abs(fw.Filename) | ||||
| 	if err == nil { | ||||
| 		return fpath | ||||
| 	} | ||||
| 	return fw.Filename | ||||
| } | ||||
| 
 | ||||
| // WriterKey returns a unique key representing this fw. | ||||
| func (fw FileWriter) WriterKey() string { | ||||
| 	return "file:" + fw.Filename | ||||
| } | ||||
| 
 | ||||
| // OpenWriter opens a new file writer. | ||||
| func (fw FileWriter) OpenWriter() (io.WriteCloser, error) { | ||||
| 	// roll log files by default | ||||
| 	if fw.Roll == nil || *fw.Roll == true { | ||||
| 		if fw.RollSizeMB == 0 { | ||||
| 			fw.RollSizeMB = 100 | ||||
| 		} | ||||
| 		if fw.RollCompress == nil { | ||||
| 			compress := true | ||||
| 			fw.RollCompress = &compress | ||||
| 		} | ||||
| 		if fw.RollKeep == 0 { | ||||
| 			fw.RollKeep = 10 | ||||
| 		} | ||||
| 		if fw.RollKeepDays == 0 { | ||||
| 			fw.RollKeepDays = 90 | ||||
| 		} | ||||
| 		return &lumberjack.Logger{ | ||||
| 			Filename:   fw.Filename, | ||||
| 			MaxSize:    fw.RollSizeMB, | ||||
| 			MaxAge:     fw.RollKeepDays, | ||||
| 			MaxBackups: fw.RollKeep, | ||||
| 			LocalTime:  fw.RollLocalTime, | ||||
| 			Compress:   *fw.RollCompress, | ||||
| 		}, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// otherwise just open a regular file | ||||
| 	return os.OpenFile(fw.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666) | ||||
| } | ||||
							
								
								
									
										321
									
								
								modules/logging/filterencoder.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										321
									
								
								modules/logging/filterencoder.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,321 @@ | ||||
| // 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 logging | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/caddyserver/caddy/v2" | ||||
| 	"go.uber.org/zap" | ||||
| 	"go.uber.org/zap/buffer" | ||||
| 	"go.uber.org/zap/zapcore" | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| 	caddy.RegisterModule(FilterEncoder{}) | ||||
| } | ||||
| 
 | ||||
| // FilterEncoder wraps an underlying encoder. It does | ||||
| // not do any encoding itself, but it can manipulate | ||||
| // (filter) fields before they are actually encoded. | ||||
| // A wrapped encoder is required. | ||||
| type FilterEncoder struct { | ||||
| 	WrappedRaw json.RawMessage            `json:"wrap,omitempty"` | ||||
| 	FieldsRaw  map[string]json.RawMessage `json:"fields,omitempty"` | ||||
| 
 | ||||
| 	wrapped zapcore.Encoder | ||||
| 	Fields  map[string]LogFieldFilter `json:"-"` | ||||
| 
 | ||||
| 	// used to keep keys unique across nested objects | ||||
| 	keyPrefix string | ||||
| } | ||||
| 
 | ||||
| // CaddyModule returns the Caddy module information. | ||||
| func (FilterEncoder) CaddyModule() caddy.ModuleInfo { | ||||
| 	return caddy.ModuleInfo{ | ||||
| 		Name: "caddy.logging.encoders.filter", | ||||
| 		New:  func() caddy.Module { return new(FilterEncoder) }, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Provision sets up the encoder. | ||||
| func (fe *FilterEncoder) Provision(ctx caddy.Context) error { | ||||
| 	if fe.WrappedRaw == nil { | ||||
| 		return fmt.Errorf("missing \"wrap\" (must specify an underlying encoder)") | ||||
| 	} | ||||
| 
 | ||||
| 	// set up wrapped encoder (required) | ||||
| 	val, err := ctx.LoadModuleInline("format", "caddy.logging.encoders", fe.WrappedRaw) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("loading fallback encoder module: %v", err) | ||||
| 	} | ||||
| 	fe.WrappedRaw = nil // allow GC to deallocate | ||||
| 	fe.wrapped = val.(zapcore.Encoder) | ||||
| 
 | ||||
| 	// set up each field filter | ||||
| 	if fe.Fields == nil { | ||||
| 		fe.Fields = make(map[string]LogFieldFilter) | ||||
| 	} | ||||
| 	for field, filterRaw := range fe.FieldsRaw { | ||||
| 		if filterRaw == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		val, err := ctx.LoadModuleInline("filter", "caddy.logging.encoders.filter", filterRaw) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("loading log filter module: %v", err) | ||||
| 		} | ||||
| 		fe.Fields[field] = val.(LogFieldFilter) | ||||
| 	} | ||||
| 	fe.FieldsRaw = nil // allow GC to deallocate | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // AddArray is part of the zapcore.ObjectEncoder interface. | ||||
| // Array elements do not get filtered. | ||||
| func (fe FilterEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error { | ||||
| 	if filter, ok := fe.Fields[fe.keyPrefix+key]; ok { | ||||
| 		filter.Filter(zap.Array(key, marshaler)).AddTo(fe.wrapped) | ||||
| 		return nil | ||||
| 	} | ||||
| 	return fe.wrapped.AddArray(key, marshaler) | ||||
| } | ||||
| 
 | ||||
| // AddObject is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error { | ||||
| 	fe.keyPrefix += key + ">" | ||||
| 	return fe.wrapped.AddObject(key, logObjectMarshalerWrapper{ | ||||
| 		enc:   fe, | ||||
| 		marsh: marshaler, | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // AddBinary is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddBinary(key string, value []byte) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddBinary(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddByteString is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddByteString(key string, value []byte) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddByteString(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddBool is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddBool(key string, value bool) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddBool(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddComplex128 is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddComplex128(key string, value complex128) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddComplex128(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddComplex64 is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddComplex64(key string, value complex64) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddComplex64(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddDuration is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddDuration(key string, value time.Duration) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddDuration(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddFloat64 is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddFloat64(key string, value float64) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddFloat64(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddFloat32 is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddFloat32(key string, value float32) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddFloat32(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddInt is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddInt(key string, value int) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddInt(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddInt64 is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddInt64(key string, value int64) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddInt64(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddInt32 is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddInt32(key string, value int32) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddInt32(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddInt16 is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddInt16(key string, value int16) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddInt16(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddInt8 is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddInt8(key string, value int8) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddInt8(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddString is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddString(key, value string) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddString(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddTime is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddTime(key string, value time.Time) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddTime(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddUint is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddUint(key string, value uint) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddUint(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddUint64 is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddUint64(key string, value uint64) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddUint64(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddUint32 is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddUint32(key string, value uint32) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddUint32(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddUint16 is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddUint16(key string, value uint16) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddUint16(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddUint8 is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddUint8(key string, value uint8) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddUint8(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddUintptr is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddUintptr(key string, value uintptr) { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		fe.wrapped.AddUintptr(key, value) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddReflected is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) AddReflected(key string, value interface{}) error { | ||||
| 	if !fe.filtered(key, value) { | ||||
| 		return fe.wrapped.AddReflected(key, value) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // OpenNamespace is part of the zapcore.ObjectEncoder interface. | ||||
| func (fe FilterEncoder) OpenNamespace(key string) { | ||||
| 	fe.wrapped.OpenNamespace(key) | ||||
| } | ||||
| 
 | ||||
| // Clone is part of the zapcore.ObjectEncoder interface. | ||||
| // We don't use it as of Oct 2019 (v2 beta 7), I'm not | ||||
| // really sure what it'd be useful for in our case. | ||||
| func (fe FilterEncoder) Clone() zapcore.Encoder { | ||||
| 	return FilterEncoder{ | ||||
| 		Fields:    fe.Fields, | ||||
| 		wrapped:   fe.wrapped.Clone(), | ||||
| 		keyPrefix: fe.keyPrefix, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // EncodeEntry partially implements the zapcore.Encoder interface. | ||||
| func (fe FilterEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { | ||||
| 	// without this clone and storing it to fe.wrapped, fields | ||||
| 	// from subsequent log entries get appended to previous | ||||
| 	// ones, and I'm not 100% sure why; see end of | ||||
| 	// https://github.com/uber-go/zap/issues/750 | ||||
| 	fe.wrapped = fe.wrapped.Clone() | ||||
| 	for _, field := range fields { | ||||
| 		field.AddTo(fe) | ||||
| 	} | ||||
| 	return fe.wrapped.EncodeEntry(ent, nil) | ||||
| } | ||||
| 
 | ||||
| // filtered returns true if the field was filtered. | ||||
| // If true is returned, the field was filtered and | ||||
| // added to the underlying encoder (so do not do | ||||
| // that again). If false was returned, the field has | ||||
| // not yet been added to the underlying encoder. | ||||
| func (fe FilterEncoder) filtered(key string, value interface{}) bool { | ||||
| 	filter, ok := fe.Fields[fe.keyPrefix+key] | ||||
| 	if !ok { | ||||
| 		return false | ||||
| 	} | ||||
| 	filter.Filter(zap.Any(key, value)).AddTo(fe.wrapped) | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // logObjectMarshalerWrapper allows us to recursively | ||||
| // filter fields of objects as they get encoded. | ||||
| type logObjectMarshalerWrapper struct { | ||||
| 	enc   FilterEncoder | ||||
| 	marsh zapcore.ObjectMarshaler | ||||
| } | ||||
| 
 | ||||
| // MarshalLogObject implements the zapcore.ObjectMarshaler interface. | ||||
| func (mom logObjectMarshalerWrapper) MarshalLogObject(_ zapcore.ObjectEncoder) error { | ||||
| 	return mom.marsh.MarshalLogObject(mom.enc) | ||||
| } | ||||
| 
 | ||||
| // Interface guards | ||||
| var ( | ||||
| 	_ zapcore.Encoder         = (*FilterEncoder)(nil) | ||||
| 	_ zapcore.ObjectMarshaler = (*logObjectMarshalerWrapper)(nil) | ||||
| ) | ||||
							
								
								
									
										94
									
								
								modules/logging/filters.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								modules/logging/filters.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,94 @@ | ||||
| // 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 logging | ||||
| 
 | ||||
| import ( | ||||
| 	"net" | ||||
| 
 | ||||
| 	"github.com/caddyserver/caddy/v2" | ||||
| 	"go.uber.org/zap/zapcore" | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| 	caddy.RegisterModule(DeleteFilter{}) | ||||
| 	caddy.RegisterModule(IPMaskFilter{}) | ||||
| } | ||||
| 
 | ||||
| // LogFieldFilter can filter (or manipulate) | ||||
| // a field in a log entry. If delete is true, | ||||
| // out will be ignored and the field will be | ||||
| // removed from the output. | ||||
| type LogFieldFilter interface { | ||||
| 	Filter(zapcore.Field) zapcore.Field | ||||
| } | ||||
| 
 | ||||
| // DeleteFilter is a Caddy log field filter that | ||||
| // deletes the field. | ||||
| type DeleteFilter struct{} | ||||
| 
 | ||||
| // CaddyModule returns the Caddy module information. | ||||
| func (DeleteFilter) CaddyModule() caddy.ModuleInfo { | ||||
| 	return caddy.ModuleInfo{ | ||||
| 		Name: "caddy.logging.encoders.filter.delete", | ||||
| 		New:  func() caddy.Module { return new(DeleteFilter) }, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Filter filters the input field. | ||||
| func (DeleteFilter) Filter(in zapcore.Field) zapcore.Field { | ||||
| 	in.Type = zapcore.SkipType | ||||
| 	return in | ||||
| } | ||||
| 
 | ||||
| // IPMaskFilter is a Caddy log field filter that | ||||
| // masks IP addresses. | ||||
| type IPMaskFilter struct { | ||||
| 	IPv4CIDR int `json:"ipv4_cidr,omitempty"` | ||||
| 	IPv6CIDR int `json:"ipv6_cidr,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // CaddyModule returns the Caddy module information. | ||||
| func (IPMaskFilter) CaddyModule() caddy.ModuleInfo { | ||||
| 	return caddy.ModuleInfo{ | ||||
| 		Name: "caddy.logging.encoders.filter.ip_mask", | ||||
| 		New:  func() caddy.Module { return new(IPMaskFilter) }, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Filter filters the input field. | ||||
| func (m IPMaskFilter) Filter(in zapcore.Field) zapcore.Field { | ||||
| 	host, port, err := net.SplitHostPort(in.String) | ||||
| 	if err != nil { | ||||
| 		host = in.String // assume whole thing was IP address | ||||
| 	} | ||||
| 	ipAddr := net.ParseIP(host) | ||||
| 	if ipAddr == nil { | ||||
| 		return in | ||||
| 	} | ||||
| 	bitLen := 32 | ||||
| 	cidrPrefix := m.IPv4CIDR | ||||
| 	if ipAddr.To16() != nil { | ||||
| 		bitLen = 128 | ||||
| 		cidrPrefix = m.IPv6CIDR | ||||
| 	} | ||||
| 	mask := net.CIDRMask(cidrPrefix, bitLen) | ||||
| 	masked := ipAddr.Mask(mask) | ||||
| 	if port == "" { | ||||
| 		in.String = masked.String() | ||||
| 	} else { | ||||
| 		in.String = net.JoinHostPort(masked.String(), port) | ||||
| 	} | ||||
| 	return in | ||||
| } | ||||
							
								
								
									
										114
									
								
								modules/logging/nopencoder.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								modules/logging/nopencoder.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,114 @@ | ||||
| // 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 logging | ||||
| 
 | ||||
| import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	"go.uber.org/zap/buffer" | ||||
| 	"go.uber.org/zap/zapcore" | ||||
| ) | ||||
| 
 | ||||
| // nopEncoder is a zapcore.Encoder that does nothing. | ||||
| type nopEncoder struct{} | ||||
| 
 | ||||
| // AddArray is part of the zapcore.ObjectEncoder interface. | ||||
| // Array elements do not get filtered. | ||||
| func (nopEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error { return nil } | ||||
| 
 | ||||
| // AddObject is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddObject(key string, marshaler zapcore.ObjectMarshaler) error { return nil } | ||||
| 
 | ||||
| // AddBinary is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddBinary(key string, value []byte) {} | ||||
| 
 | ||||
| // AddByteString is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddByteString(key string, value []byte) {} | ||||
| 
 | ||||
| // AddBool is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddBool(key string, value bool) {} | ||||
| 
 | ||||
| // AddComplex128 is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddComplex128(key string, value complex128) {} | ||||
| 
 | ||||
| // AddComplex64 is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddComplex64(key string, value complex64) {} | ||||
| 
 | ||||
| // AddDuration is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddDuration(key string, value time.Duration) {} | ||||
| 
 | ||||
| // AddFloat64 is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddFloat64(key string, value float64) {} | ||||
| 
 | ||||
| // AddFloat32 is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddFloat32(key string, value float32) {} | ||||
| 
 | ||||
| // AddInt is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddInt(key string, value int) {} | ||||
| 
 | ||||
| // AddInt64 is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddInt64(key string, value int64) {} | ||||
| 
 | ||||
| // AddInt32 is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddInt32(key string, value int32) {} | ||||
| 
 | ||||
| // AddInt16 is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddInt16(key string, value int16) {} | ||||
| 
 | ||||
| // AddInt8 is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddInt8(key string, value int8) {} | ||||
| 
 | ||||
| // AddString is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddString(key, value string) {} | ||||
| 
 | ||||
| // AddTime is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddTime(key string, value time.Time) {} | ||||
| 
 | ||||
| // AddUint is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddUint(key string, value uint) {} | ||||
| 
 | ||||
| // AddUint64 is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddUint64(key string, value uint64) {} | ||||
| 
 | ||||
| // AddUint32 is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddUint32(key string, value uint32) {} | ||||
| 
 | ||||
| // AddUint16 is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddUint16(key string, value uint16) {} | ||||
| 
 | ||||
| // AddUint8 is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddUint8(key string, value uint8) {} | ||||
| 
 | ||||
| // AddUintptr is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddUintptr(key string, value uintptr) {} | ||||
| 
 | ||||
| // AddReflected is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) AddReflected(key string, value interface{}) error { return nil } | ||||
| 
 | ||||
| // OpenNamespace is part of the zapcore.ObjectEncoder interface. | ||||
| func (nopEncoder) OpenNamespace(key string) {} | ||||
| 
 | ||||
| // Clone is part of the zapcore.ObjectEncoder interface. | ||||
| // We don't use it as of Oct 2019 (v2 beta 7), I'm not | ||||
| // really sure what it'd be useful for in our case. | ||||
| func (ne nopEncoder) Clone() zapcore.Encoder { return ne } | ||||
| 
 | ||||
| // EncodeEntry partially implements the zapcore.Encoder interface. | ||||
| func (nopEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { | ||||
| 	return bufferpool.Get(), nil | ||||
| } | ||||
| 
 | ||||
| // Interface guard | ||||
| var _ zapcore.Encoder = (*nopEncoder)(nil) | ||||
							
								
								
									
										61
									
								
								replacer.go
									
									
									
									
									
								
							
							
						
						
									
										61
									
								
								replacer.go
									
									
									
									
									
								
							| @ -15,6 +15,7 @@ | ||||
| package caddy | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"runtime" | ||||
| @ -28,6 +29,8 @@ type Replacer interface { | ||||
| 	Delete(variable string) | ||||
| 	Map(ReplacementFunc) | ||||
| 	ReplaceAll(input, empty string) string | ||||
| 	ReplaceKnown(input, empty string) string | ||||
| 	ReplaceOrErr(input string, errOnEmpty, errOnUnknown bool) (string, error) | ||||
| } | ||||
| 
 | ||||
| // NewReplacer returns a new Replacer. | ||||
| @ -70,12 +73,34 @@ func (r *replacer) fromStatic(key string) (val string, ok bool) { | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // ReplaceOrErr is like ReplaceAll, but any placeholders | ||||
| // that are empty or not recognized will cause an error to | ||||
| // be returned. | ||||
| func (r *replacer) ReplaceOrErr(input string, errOnEmpty, errOnUnknown bool) (string, error) { | ||||
| 	return r.replace(input, "", false, errOnEmpty, errOnUnknown) | ||||
| } | ||||
| 
 | ||||
| // ReplaceKnown is like ReplaceAll but only replaces | ||||
| // placeholders that are known (recognized). Unrecognized | ||||
| // placeholders will remain in the output. | ||||
| func (r *replacer) ReplaceKnown(input, empty string) string { | ||||
| 	out, _ := r.replace(input, empty, false, false, false) | ||||
| 	return out | ||||
| } | ||||
| 
 | ||||
| // ReplaceAll efficiently replaces placeholders in input with | ||||
| // their values. Unrecognized placeholders will not be replaced. | ||||
| // Values that are empty string will be substituted with empty. | ||||
| // their values. All placeholders are replaced in the output | ||||
| // whether they are recognized or not. Values that are empty | ||||
| // string will be substituted with empty. | ||||
| func (r *replacer) ReplaceAll(input, empty string) string { | ||||
| 	out, _ := r.replace(input, empty, true, false, false) | ||||
| 	return out | ||||
| } | ||||
| 
 | ||||
| func (r *replacer) replace(input, empty string, | ||||
| 	treatUnknownAsEmpty, errOnEmpty, errOnUnknown bool) (string, error) { | ||||
| 	if !strings.Contains(input, string(phOpen)) { | ||||
| 		return input | ||||
| 		return input, nil | ||||
| 	} | ||||
| 
 | ||||
| 	var sb strings.Builder | ||||
| @ -100,25 +125,40 @@ func (r *replacer) ReplaceAll(input, empty string) string { | ||||
| 		// trim opening bracket | ||||
| 		key := input[i+1 : end] | ||||
| 
 | ||||
| 		// try to get a value for this key; if | ||||
| 		// the key is not recognized, do not | ||||
| 		// perform any replacement | ||||
| 		// try to get a value for this key, | ||||
| 		// handle empty values accordingly | ||||
| 		var found bool | ||||
| 		for _, mapFunc := range r.providers { | ||||
| 			if val, ok := mapFunc(key); ok { | ||||
| 				found = true | ||||
| 				if val != "" { | ||||
| 					sb.WriteString(val) | ||||
| 				if val == "" { | ||||
| 					if errOnEmpty { | ||||
| 						return "", fmt.Errorf("evaluated placeholder %s%s%s is empty", | ||||
| 							string(phOpen), key, string(phClose)) | ||||
| 					} else if empty != "" { | ||||
| 						sb.WriteString(empty) | ||||
| 					} | ||||
| 				} else { | ||||
| 					sb.WriteString(val) | ||||
| 				} | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if !found { | ||||
| 			// placeholder is unknown (unrecognized), handle accordingly | ||||
| 			switch { | ||||
| 			case errOnUnknown: | ||||
| 				return "", fmt.Errorf("unrecognized placeholder %s%s%s", | ||||
| 					string(phOpen), key, string(phClose)) | ||||
| 			case treatUnknownAsEmpty: | ||||
| 				if empty != "" { | ||||
| 					sb.WriteString(empty) | ||||
| 				} | ||||
| 			default: | ||||
| 				lastWriteCursor = i | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// advance cursor to end of placeholder | ||||
| 		i = end | ||||
| @ -128,7 +168,7 @@ func (r *replacer) ReplaceAll(input, empty string) string { | ||||
| 	// flush any unwritten remainder | ||||
| 	sb.WriteString(input[lastWriteCursor:]) | ||||
| 
 | ||||
| 	return sb.String() | ||||
| 	return sb.String(), nil | ||||
| } | ||||
| 
 | ||||
| // ReplacementFunc is a function that returns a replacement | ||||
| @ -142,8 +182,7 @@ func globalDefaultReplacements(key string) (string, bool) { | ||||
| 	// check environment variable | ||||
| 	const envPrefix = "env." | ||||
| 	if strings.HasPrefix(key, envPrefix) { | ||||
| 		val := os.Getenv(key[len(envPrefix):]) | ||||
| 		return val, val != "" | ||||
| 		return os.Getenv(key[len(envPrefix):]), true | ||||
| 	} | ||||
| 
 | ||||
| 	switch key { | ||||
|  | ||||
| @ -85,7 +85,7 @@ func TestReplacerSet(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestReplacerReplaceAll(t *testing.T) { | ||||
| func TestReplacerReplaceKnown(t *testing.T) { | ||||
| 	rep := replacer{ | ||||
| 		providers: []ReplacementFunc{ | ||||
| 			// split our possible vars to two functions (to test if both functions are called) | ||||
| @ -148,7 +148,7 @@ func TestReplacerReplaceAll(t *testing.T) { | ||||
| 			expected:  "val1 {nope} test-123 ", | ||||
| 		}, | ||||
| 	} { | ||||
| 		actual := rep.ReplaceAll(tc.testInput, "EMPTY") | ||||
| 		actual := rep.ReplaceKnown(tc.testInput, "EMPTY") | ||||
| 
 | ||||
| 		// test if all are replaced as expected | ||||
| 		if actual != tc.expected { | ||||
|  | ||||
							
								
								
									
										14
									
								
								sigtrap.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								sigtrap.go
									
									
									
									
									
								
							| @ -15,9 +15,10 @@ | ||||
| package caddy | ||||
| 
 | ||||
| import ( | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 
 | ||||
| 	"go.uber.org/zap" | ||||
| ) | ||||
| 
 | ||||
| // TrapSignals create signal/interrupt handlers as best it can for the | ||||
| @ -41,11 +42,11 @@ func trapSignalsCrossPlatform() { | ||||
| 			<-shutdown | ||||
| 
 | ||||
| 			if i > 0 { | ||||
| 				log.Println("[INFO] SIGINT: Force quit") | ||||
| 				Log().Warn("force quit", zap.String("signal", "SIGINT")) | ||||
| 				os.Exit(ExitCodeForceQuit) | ||||
| 			} | ||||
| 
 | ||||
| 			log.Println("[INFO] SIGINT: Shutting down") | ||||
| 			Log().Info("shutting down", zap.String("signal", "SIGINT")) | ||||
| 			go gracefulStop("SIGINT") | ||||
| 		} | ||||
| 	}() | ||||
| @ -57,11 +58,14 @@ func gracefulStop(sigName string) { | ||||
| 
 | ||||
| 	err := stopAndCleanup() | ||||
| 	if err != nil { | ||||
| 		log.Printf("[ERROR] %s stop: %v", sigName, err) | ||||
| 		Log().Error("stopping", | ||||
| 			zap.String("signal", sigName), | ||||
| 			zap.Error(err), | ||||
| 		) | ||||
| 		exitCode = ExitCodeFailedQuit | ||||
| 	} | ||||
| 
 | ||||
| 	log.Printf("[INFO] %s: Shutdown done", sigName) | ||||
| 	Log().Info("shutdown done", zap.String("signal", sigName)) | ||||
| 	os.Exit(exitCode) | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -17,12 +17,12 @@ | ||||
| package caddy | ||||
| 
 | ||||
| import ( | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"github.com/mholt/certmagic" | ||||
| 	"go.uber.org/zap" | ||||
| ) | ||||
| 
 | ||||
| // trapSignalsPosix captures POSIX-only signals. | ||||
| @ -34,23 +34,23 @@ func trapSignalsPosix() { | ||||
| 		for sig := range sigchan { | ||||
| 			switch sig { | ||||
| 			case syscall.SIGQUIT: | ||||
| 				log.Println("[INFO] SIGQUIT: Quitting process immediately") | ||||
| 				Log().Info("quitting process immediately", zap.String("signal", "SIGQUIT")) | ||||
| 				certmagic.CleanUpOwnLocks() // try to clean up locks anyway, it's important | ||||
| 				os.Exit(ExitCodeForceQuit) | ||||
| 
 | ||||
| 			case syscall.SIGTERM: | ||||
| 				log.Println("[INFO] SIGTERM: Shutting down apps then terminating") | ||||
| 				Log().Info("shutting down apps then terminating", zap.String("signal", "SIGTERM")) | ||||
| 				gracefulStop("SIGTERM") | ||||
| 
 | ||||
| 			case syscall.SIGUSR1: | ||||
| 				log.Println("[INFO] SIGUSR1: Not implemented") | ||||
| 				Log().Info("not implemented", zap.String("signal", "SIGUSR1")) | ||||
| 
 | ||||
| 			case syscall.SIGUSR2: | ||||
| 				log.Println("[INFO] SIGUSR2: Not implemented") | ||||
| 				Log().Info("not implemented", zap.String("signal", "SIGUSR2")) | ||||
| 
 | ||||
| 			case syscall.SIGHUP: | ||||
| 				// ignore; this signal is sometimes sent outside of the user's control | ||||
| 				log.Println("[INFO] SIGHUP: Not implemented") | ||||
| 				Log().Info("not implemented", zap.String("signal", "SIGHUP")) | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
							
								
								
									
										200
									
								
								usagepool.go
									
									
									
									
									
								
							
							
						
						
									
										200
									
								
								usagepool.go
									
									
									
									
									
								
							| @ -21,23 +21,136 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| // UsagePool is a thread-safe map that pools values | ||||
| // based on usage; a LoadOrStore operation increments | ||||
| // the usage, and a Delete decrements from the usage. | ||||
| // If the usage count reaches 0, the value will be | ||||
| // removed from the map. There is no way to overwrite | ||||
| // existing keys in the pool without first deleting | ||||
| // it as many times as it was stored. Deleting too | ||||
| // many times will panic. | ||||
| // based on usage (reference counting). Values are | ||||
| // only inserted if they do not already exist. There | ||||
| // are two ways to add values to the pool: | ||||
| // | ||||
| // 1) LoadOrStore will increment usage and store the | ||||
| //    value immediately if it does not already exist | ||||
| // 2) LoadOrNew will increment usage and construct the | ||||
| //    value immediately if it does not already exist, | ||||
| //    then store that value in the pool. When the | ||||
| //    constructed value is finally deleted from the | ||||
| //    pool (after its usage reaches 0), it will be | ||||
| //    cleaned up by calling its Destruct method. | ||||
| // | ||||
| // The use of LoadOrNew allows values to be created | ||||
| // and reused and finally cleaned up only once, even | ||||
| // though they may have many references throughout | ||||
| // their lifespan. This is helpful, for example, when | ||||
| // sharing thread-safe io.Writers that you only want | ||||
| // to open and close once. | ||||
| // | ||||
| // There is no way to overwrite existing keys in the | ||||
| // pool without first deleting it as many times as it | ||||
| // was stored. Deleting too many times will panic. | ||||
| // | ||||
| // The implementation does not use a sync.Pool because | ||||
| // UsagePool needs additional atomicity to run the | ||||
| // constructor functions when creating a new value when | ||||
| // LoadOrNew is used. (We could probably use sync.Pool | ||||
| // but we'd still have to layer our own additional locks | ||||
| // on top.) | ||||
| // | ||||
| // An empty UsagePool is NOT safe to use; always call | ||||
| // NewUsagePool() to make a new value. | ||||
| // NewUsagePool() to make a new one. | ||||
| type UsagePool struct { | ||||
| 	pool *sync.Map | ||||
| 	sync.RWMutex | ||||
| 	pool map[interface{}]*usagePoolVal | ||||
| } | ||||
| 
 | ||||
| // NewUsagePool returns a new usage pool. | ||||
| // NewUsagePool returns a new usage pool that is ready to use. | ||||
| func NewUsagePool() *UsagePool { | ||||
| 	return &UsagePool{pool: new(sync.Map)} | ||||
| 	return &UsagePool{ | ||||
| 		pool: make(map[interface{}]*usagePoolVal), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // LoadOrNew loads the value associated with key from the pool if it | ||||
| // already exists. If the key doesn't exist, it will call construct | ||||
| // to create a new value and then stores that in the pool. An error | ||||
| // is only returned if the constructor returns an error. The loaded | ||||
| // or constructed value is returned. The loaded return value is true | ||||
| // if the value already existed and was loaded, or false if it was | ||||
| // newly constructed. | ||||
| func (up *UsagePool) LoadOrNew(key interface{}, construct Constructor) (value interface{}, loaded bool, err error) { | ||||
| 	var upv *usagePoolVal | ||||
| 	up.Lock() | ||||
| 	upv, loaded = up.pool[key] | ||||
| 	if loaded { | ||||
| 		atomic.AddInt32(&upv.refs, 1) | ||||
| 		up.Unlock() | ||||
| 		upv.RLock() | ||||
| 		value = upv.value | ||||
| 		err = upv.err | ||||
| 		upv.RUnlock() | ||||
| 	} else { | ||||
| 		upv = &usagePoolVal{refs: 1} | ||||
| 		upv.Lock() | ||||
| 		up.pool[key] = upv | ||||
| 		up.Unlock() | ||||
| 		value, err = construct() | ||||
| 		if err == nil { | ||||
| 			upv.value = value | ||||
| 		} else { | ||||
| 			// TODO: remove error'ed entries from map | ||||
| 			upv.err = err | ||||
| 		} | ||||
| 		upv.Unlock() | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // LoadOrStore loads the value associated with key from the pool if it | ||||
| // already exists, or stores it if it does not exist. It returns the | ||||
| // value that was either loaded or stored, and true if the value already | ||||
| // existed and was | ||||
| func (up *UsagePool) LoadOrStore(key, val interface{}) (value interface{}, loaded bool) { | ||||
| 	var upv *usagePoolVal | ||||
| 	up.Lock() | ||||
| 	upv, loaded = up.pool[key] | ||||
| 	if loaded { | ||||
| 		atomic.AddInt32(&upv.refs, 1) | ||||
| 		up.Unlock() | ||||
| 		upv.Lock() | ||||
| 		if upv.err == nil { | ||||
| 			value = upv.value | ||||
| 		} else { | ||||
| 			upv.value = val | ||||
| 			upv.err = nil | ||||
| 		} | ||||
| 		upv.Unlock() | ||||
| 	} else { | ||||
| 		upv = &usagePoolVal{refs: 1, value: val} | ||||
| 		up.pool[key] = upv | ||||
| 		up.Unlock() | ||||
| 		value = val | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Range iterates the pool similarly to how sync.Map.Range() does: | ||||
| // it calls f for every key in the pool, and if f returns false, | ||||
| // iteration is stopped. Ranging does not affect usage counts. | ||||
| // | ||||
| // This method is somewhat naive and acquires a read lock on the | ||||
| // entire pool during iteration, so do your best to make f() really | ||||
| // fast, m'kay? | ||||
| func (up *UsagePool) Range(f func(key, value interface{}) bool) { | ||||
| 	up.RLock() | ||||
| 	defer up.RUnlock() | ||||
| 	for key, upv := range up.pool { | ||||
| 		upv.RLock() | ||||
| 		if upv.err != nil { | ||||
| 			upv.RUnlock() | ||||
| 			continue | ||||
| 		} | ||||
| 		val := upv.value | ||||
| 		upv.RUnlock() | ||||
| 		if !f(key, val) { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Delete decrements the usage count for key and removes the | ||||
| @ -45,50 +158,47 @@ func NewUsagePool() *UsagePool { | ||||
| // true if the usage count reached 0 and the value was deleted. | ||||
| // It panics if the usage count drops below 0; always call | ||||
| // Delete precisely as many times as LoadOrStore. | ||||
| func (up *UsagePool) Delete(key interface{}) (deleted bool) { | ||||
| 	usageVal, ok := up.pool.Load(key) | ||||
| func (up *UsagePool) Delete(key interface{}) (deleted bool, err error) { | ||||
| 	up.Lock() | ||||
| 	upv, ok := up.pool[key] | ||||
| 	if !ok { | ||||
| 		return false | ||||
| 		up.Unlock() | ||||
| 		return false, nil | ||||
| 	} | ||||
| 	upv := usageVal.(*usagePoolVal) | ||||
| 	newUsage := atomic.AddInt32(&upv.usage, -1) | ||||
| 	if newUsage == 0 { | ||||
| 		up.pool.Delete(key) | ||||
| 		return true | ||||
| 	} else if newUsage < 0 { | ||||
| 	refs := atomic.AddInt32(&upv.refs, -1) | ||||
| 	if refs == 0 { | ||||
| 		delete(up.pool, key) | ||||
| 		up.Unlock() | ||||
| 		upv.RLock() | ||||
| 		val := upv.value | ||||
| 		upv.RUnlock() | ||||
| 		if destructor, ok := val.(Destructor); ok { | ||||
| 			err = destructor.Destruct() | ||||
| 		} | ||||
| 		deleted = true | ||||
| 	} else { | ||||
| 		up.Unlock() | ||||
| 		if refs < 0 { | ||||
| 			panic(fmt.Sprintf("deleted more than stored: %#v (usage: %d)", | ||||
| 			upv.value, upv.usage)) | ||||
| 				upv.value, upv.refs)) | ||||
| 		} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| // LoadOrStore puts val in the pool and returns false if key does | ||||
| // not already exist; otherwise if the key exists, it loads the | ||||
| // existing value, increments the usage for that value, and returns | ||||
| // the value along with true. | ||||
| func (up *UsagePool) LoadOrStore(key, val interface{}) (actual interface{}, loaded bool) { | ||||
| 	usageVal := &usagePoolVal{ | ||||
| 		usage: 1, | ||||
| 		value: val, | ||||
| 	} | ||||
| 	actual, loaded = up.pool.LoadOrStore(key, usageVal) | ||||
| 	if loaded { | ||||
| 		upv := actual.(*usagePoolVal) | ||||
| 		actual = upv.value | ||||
| 		atomic.AddInt32(&upv.usage, 1) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Range iterates the pool the same way sync.Map.Range does. | ||||
| // This does not affect usage counts. | ||||
| func (up *UsagePool) Range(f func(key, value interface{}) bool) { | ||||
| 	up.pool.Range(func(key, value interface{}) bool { | ||||
| 		return f(key, value.(*usagePoolVal).value) | ||||
| 	}) | ||||
| // Constructor is a function that returns a new value | ||||
| // that can destruct itself when it is no longer needed. | ||||
| type Constructor func() (Destructor, error) | ||||
| 
 | ||||
| // Destructor is a value that can clean itself up when | ||||
| // it is deallocated. | ||||
| type Destructor interface { | ||||
| 	Destruct() error | ||||
| } | ||||
| 
 | ||||
| type usagePoolVal struct { | ||||
| 	usage int32 // accessed atomically; must be 64-bit aligned for 32-bit systems | ||||
| 	refs  int32 // accessed atomically; must be 64-bit aligned for 32-bit systems | ||||
| 	value interface{} | ||||
| 	err   error | ||||
| 	sync.RWMutex | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user