mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-03 19:17:29 -05: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/caddyserver/caddy/v2/caddyconfig"
 | 
				
			||||||
	"github.com/mholt/certmagic"
 | 
						"github.com/mholt/certmagic"
 | 
				
			||||||
	"github.com/rs/cors"
 | 
						"github.com/rs/cors"
 | 
				
			||||||
 | 
						"go.uber.org/zap"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
@ -52,6 +53,8 @@ var DefaultAdminConfig = &AdminConfig{
 | 
				
			|||||||
	Listen: DefaultAdminListen,
 | 
						Listen: DefaultAdminListen,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: holy smokes, the admin endpoint might not have to live in caddy's core.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// StartAdmin starts Caddy's administration endpoint,
 | 
					// StartAdmin starts Caddy's administration endpoint,
 | 
				
			||||||
// bootstrapping it with an optional configuration
 | 
					// bootstrapping it with an optional configuration
 | 
				
			||||||
// in the format of JSON bytes. It opens a listener
 | 
					// 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{
 | 
						cfgEndptSrv = &http.Server{
 | 
				
			||||||
		Handler:           handler,
 | 
							Handler:           handler,
 | 
				
			||||||
@ -125,14 +136,14 @@ func StartAdmin(initialConfigJSON []byte) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	go cfgEndptSrv.Serve(ln)
 | 
						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 {
 | 
						if len(initialConfigJSON) > 0 {
 | 
				
			||||||
		err := Load(bytes.NewReader(initialConfigJSON))
 | 
							err := Load(bytes.NewReader(initialConfigJSON))
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return fmt.Errorf("loading initial config: %v", err)
 | 
								return fmt.Errorf("loading initial config: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		log.Println("Caddy 2 serving initial configuration")
 | 
							fmt.Println("Caddy 2 serving initial configuration")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										41
									
								
								caddy.go
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								caddy.go
									
									
									
									
									
								
							@ -30,15 +30,12 @@ import (
 | 
				
			|||||||
// Config represents a Caddy configuration.
 | 
					// Config represents a Caddy configuration.
 | 
				
			||||||
type Config struct {
 | 
					type Config struct {
 | 
				
			||||||
	Admin      *AdminConfig               `json:"admin,omitempty"`
 | 
						Admin      *AdminConfig               `json:"admin,omitempty"`
 | 
				
			||||||
 | 
						Logging    *Logging                   `json:"logging,omitempty"`
 | 
				
			||||||
	StorageRaw json.RawMessage            `json:"storage,omitempty"`
 | 
						StorageRaw json.RawMessage            `json:"storage,omitempty"`
 | 
				
			||||||
	storage    certmagic.Storage
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	AppsRaw    map[string]json.RawMessage `json:"apps,omitempty"`
 | 
						AppsRaw    map[string]json.RawMessage `json:"apps,omitempty"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// apps stores the decoded Apps values,
 | 
					 | 
				
			||||||
	// keyed by module name.
 | 
					 | 
				
			||||||
	apps    map[string]App
 | 
						apps    map[string]App
 | 
				
			||||||
 | 
						storage certmagic.Storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cancelFunc context.CancelFunc
 | 
						cancelFunc context.CancelFunc
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -75,7 +72,11 @@ func Run(newCfg *Config) error {
 | 
				
			|||||||
// is performed if any modules were provisioned;
 | 
					// is performed if any modules were provisioned;
 | 
				
			||||||
// apps that were started already will be stopped,
 | 
					// apps that were started already will be stopped,
 | 
				
			||||||
// so this function should not leak resources if
 | 
					// 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 {
 | 
					func run(newCfg *Config, start bool) error {
 | 
				
			||||||
	if newCfg == nil {
 | 
						if newCfg == nil {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
@ -102,12 +103,30 @@ func run(newCfg *Config, start bool) error {
 | 
				
			|||||||
	ctx, cancel := NewContext(Context{Context: context.Background(), cfg: newCfg})
 | 
						ctx, cancel := NewContext(Context{Context: context.Background(), cfg: newCfg})
 | 
				
			||||||
	defer func() {
 | 
						defer func() {
 | 
				
			||||||
		if err != nil {
 | 
							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
 | 
						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 {
 | 
						err = func() error {
 | 
				
			||||||
		if newCfg.StorageRaw != nil {
 | 
							if newCfg.StorageRaw != nil {
 | 
				
			||||||
			val, err := ctx.LoadModuleInline("module", "caddy.storage", newCfg.StorageRaw)
 | 
								val, err := ctx.LoadModuleInline("module", "caddy.storage", newCfg.StorageRaw)
 | 
				
			||||||
@ -214,7 +233,11 @@ func unsyncedStop(cfg *Config) {
 | 
				
			|||||||
// Validate loads, provisions, and validates
 | 
					// Validate loads, provisions, and validates
 | 
				
			||||||
// cfg, but does not start running it.
 | 
					// cfg, but does not start running it.
 | 
				
			||||||
func Validate(cfg *Config) error {
 | 
					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.
 | 
					// 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"
 | 
				
			||||||
	_ "github.com/caddyserver/caddy/v2/modules/caddytls/standardstek"
 | 
						_ "github.com/caddyserver/caddy/v2/modules/caddytls/standardstek"
 | 
				
			||||||
	_ "github.com/caddyserver/caddy/v2/modules/filestorage"
 | 
						_ "github.com/caddyserver/caddy/v2/modules/filestorage"
 | 
				
			||||||
 | 
						_ "github.com/caddyserver/caddy/v2/modules/logging"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
 | 
				
			|||||||
@ -387,7 +387,7 @@ func cmdAdaptConfig(fl Flags) (int, error) {
 | 
				
			|||||||
		if warn.Directive != "" {
 | 
							if warn.Directive != "" {
 | 
				
			||||||
			msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message)
 | 
								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
 | 
						// print result to stdout
 | 
				
			||||||
@ -436,7 +436,7 @@ func cmdValidateConfig(fl Flags) (int, error) {
 | 
				
			|||||||
			if warn.Directive != "" {
 | 
								if warn.Directive != "" {
 | 
				
			||||||
				msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message)
 | 
									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
 | 
							input = adaptedConfig
 | 
				
			||||||
 | 
				
			|||||||
@ -130,9 +130,9 @@ not quit after printing, and can be useful for troubleshooting.`,
 | 
				
			|||||||
	RegisterCommand(Command{
 | 
						RegisterCommand(Command{
 | 
				
			||||||
		Name:  "stop",
 | 
							Name:  "stop",
 | 
				
			||||||
		Func:  cmdStop,
 | 
							Func:  cmdStop,
 | 
				
			||||||
		Short: "Gracefully stops the running Caddy process",
 | 
							Short: "Gracefully stops a started Caddy process",
 | 
				
			||||||
		Long: `
 | 
							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
 | 
					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
 | 
					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"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"log"
 | 
					 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
@ -38,7 +37,7 @@ func Main() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	switch len(os.Args) {
 | 
						switch len(os.Args) {
 | 
				
			||||||
	case 0:
 | 
						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)
 | 
							os.Exit(caddy.ExitCodeFailedStartup)
 | 
				
			||||||
	case 1:
 | 
						case 1:
 | 
				
			||||||
		os.Args = append(os.Args, "help")
 | 
							os.Args = append(os.Args, "help")
 | 
				
			||||||
@ -49,9 +48,9 @@ func Main() {
 | 
				
			|||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		if strings.HasPrefix(os.Args[1], "-") {
 | 
							if strings.HasPrefix(os.Args[1], "-") {
 | 
				
			||||||
			// user probably forgot to type the subcommand
 | 
								// 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 {
 | 
							} 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)
 | 
							os.Exit(caddy.ExitCodeFailedStartup)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -63,13 +62,13 @@ func Main() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	err := fs.Parse(os.Args[2:])
 | 
						err := fs.Parse(os.Args[2:])
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Println(err)
 | 
							fmt.Println(err)
 | 
				
			||||||
		os.Exit(caddy.ExitCodeFailedStartup)
 | 
							os.Exit(caddy.ExitCodeFailedStartup)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	exitCode, err := subcommand.Func(Flags{fs})
 | 
						exitCode, err := subcommand.Func(Flags{fs})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Printf("%s: %v", subcommand.Name, err)
 | 
							fmt.Printf("%s: %v\n", subcommand.Name, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	os.Exit(exitCode)
 | 
						os.Exit(exitCode)
 | 
				
			||||||
@ -149,7 +148,7 @@ func loadConfig(configFile, adapterName string) ([]byte, error) {
 | 
				
			|||||||
			if warn.Directive != "" {
 | 
								if warn.Directive != "" {
 | 
				
			||||||
				msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message)
 | 
									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
 | 
							config = adaptedConfig
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -24,7 +24,7 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func gracefullyStopProcess(pid int) error {
 | 
					func gracefullyStopProcess(pid int) error {
 | 
				
			||||||
	fmt.Printf("Graceful stop...")
 | 
						fmt.Printf("Graceful stop...\n")
 | 
				
			||||||
	err := syscall.Kill(pid, syscall.SIGINT)
 | 
						err := syscall.Kill(pid, syscall.SIGINT)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fmt.Errorf("kill: %v", err)
 | 
							return fmt.Errorf("kill: %v", err)
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func gracefullyStopProcess(pid int) error {
 | 
					func gracefullyStopProcess(pid int) error {
 | 
				
			||||||
	fmt.Printf("Forceful Stop...")
 | 
						fmt.Printf("Forceful Stop...\n")
 | 
				
			||||||
	// process on windows will not stop unless forced with /f
 | 
						// process on windows will not stop unless forced with /f
 | 
				
			||||||
	cmd := exec.Command("taskkill", "/pid", strconv.Itoa(pid), "/f")
 | 
						cmd := exec.Command("taskkill", "/pid", strconv.Itoa(pid), "/f")
 | 
				
			||||||
	if err := cmd.Run(); err != nil {
 | 
						if err := cmd.Run(); err != nil {
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,7 @@ import (
 | 
				
			|||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/mholt/certmagic"
 | 
						"github.com/mholt/certmagic"
 | 
				
			||||||
 | 
						"go.uber.org/zap"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Context is a type which defines the lifetime of modules that
 | 
					// 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 {
 | 
					func (ctx Context) Storage() certmagic.Storage {
 | 
				
			||||||
	return ctx.cfg.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/golang/groupcache v0.0.0-20191002201903-404acd9df4cc
 | 
				
			||||||
	github.com/ilibs/json5 v1.0.1
 | 
						github.com/ilibs/json5 v1.0.1
 | 
				
			||||||
	github.com/imdario/mergo v0.3.8 // indirect
 | 
						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/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19
 | 
				
			||||||
	github.com/klauspost/compress v1.8.6
 | 
						github.com/klauspost/compress v1.8.6
 | 
				
			||||||
	github.com/klauspost/cpuid v1.2.1
 | 
						github.com/klauspost/cpuid v1.2.1
 | 
				
			||||||
	github.com/lucas-clemente/quic-go v0.12.1
 | 
						github.com/lucas-clemente/quic-go v0.12.1
 | 
				
			||||||
	github.com/mholt/certmagic v0.8.3
 | 
						github.com/mholt/certmagic v0.8.3
 | 
				
			||||||
	github.com/muhammadmuzzammil1998/jsonc v0.0.0-20190906142622-1265e9b150c6
 | 
						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/rs/cors v1.7.0
 | 
				
			||||||
	github.com/russross/blackfriday/v2 v2.0.1
 | 
						github.com/russross/blackfriday/v2 v2.0.1
 | 
				
			||||||
	github.com/starlight-go/starlight v0.0.0-20181207205707-b06f321544f3
 | 
						github.com/starlight-go/starlight v0.0.0-20181207205707-b06f321544f3
 | 
				
			||||||
	github.com/vulcand/oxy v1.0.0
 | 
						github.com/vulcand/oxy v1.0.0
 | 
				
			||||||
	go.starlark.net v0.0.0-20190919145610-979af19b165c
 | 
						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/crypto v0.0.0-20191010185427-af544f31c8ac
 | 
				
			||||||
	golang.org/x/net v0.0.0-20191009170851-d66e71096ffb
 | 
						golang.org/x/net v0.0.0-20191009170851-d66e71096ffb
 | 
				
			||||||
	golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0
 | 
						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/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/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/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/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 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
 | 
				
			||||||
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
 | 
					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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 | 
				
			||||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
					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/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/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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 | 
				
			||||||
github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19 h1:WjT3fLi9n8YWh/Ih8Q1LHAPsTqGddPcHqscN+PJ3i68=
 | 
					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.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 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
 | 
				
			||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
					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 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
 | 
				
			||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 | 
					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/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/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/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/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.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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
					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.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 h1:WR7X1xgXJlXhQBdorVc9Db3RhwG+J/kp6bLuMyJjfVw=
 | 
				
			||||||
go.starlark.net v0.0.0-20190919145610-979af19b165c/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg=
 | 
					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/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/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-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-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/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/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.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 | 
				
			||||||
gopkg.in/ini.v1 v1.44.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/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.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc=
 | 
				
			||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 | 
					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"
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"log"
 | 
					 | 
				
			||||||
	weakrand "math/rand"
 | 
						weakrand "math/rand"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
@ -33,6 +32,7 @@ import (
 | 
				
			|||||||
	"github.com/caddyserver/caddy/v2/modules/caddytls"
 | 
						"github.com/caddyserver/caddy/v2/modules/caddytls"
 | 
				
			||||||
	"github.com/lucas-clemente/quic-go/http3"
 | 
						"github.com/lucas-clemente/quic-go/http3"
 | 
				
			||||||
	"github.com/mholt/certmagic"
 | 
						"github.com/mholt/certmagic"
 | 
				
			||||||
 | 
						"go.uber.org/zap"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
@ -40,7 +40,7 @@ func init() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	err := caddy.RegisterModule(App{})
 | 
						err := caddy.RegisterModule(App{})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Fatal(err)
 | 
							caddy.Log().Fatal(err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -56,6 +56,7 @@ type App struct {
 | 
				
			|||||||
	h3listeners []net.PacketConn
 | 
						h3listeners []net.PacketConn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx    caddy.Context
 | 
						ctx    caddy.Context
 | 
				
			||||||
 | 
						logger *zap.Logger
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CaddyModule returns the Caddy module information.
 | 
					// CaddyModule returns the Caddy module information.
 | 
				
			||||||
@ -69,10 +70,15 @@ func (App) CaddyModule() caddy.ModuleInfo {
 | 
				
			|||||||
// Provision sets up the app.
 | 
					// Provision sets up the app.
 | 
				
			||||||
func (app *App) Provision(ctx caddy.Context) error {
 | 
					func (app *App) Provision(ctx caddy.Context) error {
 | 
				
			||||||
	app.ctx = ctx
 | 
						app.ctx = ctx
 | 
				
			||||||
 | 
						app.logger = ctx.Logger(app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	repl := caddy.NewReplacer()
 | 
						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 {
 | 
							if srv.AutoHTTPS == nil {
 | 
				
			||||||
			// avoid nil pointer dereferences
 | 
								// avoid nil pointer dereferences
 | 
				
			||||||
			srv.AutoHTTPS = new(AutoHTTPSConfig)
 | 
								srv.AutoHTTPS = new(AutoHTTPSConfig)
 | 
				
			||||||
@ -93,20 +99,25 @@ func (app *App) Provision(ctx caddy.Context) error {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for i := range srv.Listen {
 | 
							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 {
 | 
							if srv.Routes != nil {
 | 
				
			||||||
			err := srv.Routes.Provision(ctx)
 | 
								err := srv.Routes.Provision(ctx)
 | 
				
			||||||
			if err != nil {
 | 
								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 {
 | 
							if srv.Errors != nil {
 | 
				
			||||||
			err := srv.Errors.Routes.Provision(ctx)
 | 
								err := srv.Errors.Routes.Provision(ctx)
 | 
				
			||||||
			if err != nil {
 | 
								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
 | 
										// TODO: HTTP/3 support is experimental for now
 | 
				
			||||||
					if srv.ExperimentalHTTP3 {
 | 
										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)
 | 
											h3ln, err := caddy.ListenPacket("udp", addr)
 | 
				
			||||||
						if err != nil {
 | 
											if err != nil {
 | 
				
			||||||
							return fmt.Errorf("getting HTTP/3 UDP listener: %v", err)
 | 
												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
 | 
							// skip if all listeners use the HTTP port
 | 
				
			||||||
		if !srv.listenersUseAnyPortOtherThan(app.httpPort()) {
 | 
							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",
 | 
								app.logger.Info("server is only listening on the HTTP port, so no automatic HTTPS will be applied to this server",
 | 
				
			||||||
				srvName, app.httpPort())
 | 
									zap.String("server_name", srvName),
 | 
				
			||||||
 | 
									zap.Int("http_port", app.httpPort()),
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// find all qualifying domain names, de-duplicated
 | 
							// find all qualifying domain names, de-duplicated
 | 
				
			||||||
		domainSet := make(map[string]struct{})
 | 
							domainSet := make(map[string]struct{})
 | 
				
			||||||
		for _, route := range srv.Routes {
 | 
							for routeIdx, route := range srv.Routes {
 | 
				
			||||||
			for _, matcherSet := range route.MatcherSets {
 | 
								for matcherSetIdx, matcherSet := range route.MatcherSets {
 | 
				
			||||||
				for _, m := range matcherSet {
 | 
									for matcherIdx, m := range matcherSet {
 | 
				
			||||||
					if hm, ok := m.(*MatchHost); ok {
 | 
										if hm, ok := m.(*MatchHost); ok {
 | 
				
			||||||
						for _, d := range *hm {
 | 
											for hostMatcherIdx, d := range *hm {
 | 
				
			||||||
							d = repl.ReplaceAll(d, "")
 | 
												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) &&
 | 
												if certmagic.HostQualifies(d) &&
 | 
				
			||||||
								!srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.Skip) {
 | 
													!srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.Skip) {
 | 
				
			||||||
								domainSet[d] = struct{}{}
 | 
													domainSet[d] = struct{}{}
 | 
				
			||||||
@ -318,7 +337,10 @@ func (app *App) automaticHTTPS() error {
 | 
				
			|||||||
					// supposed to ignore loaded certificates
 | 
										// supposed to ignore loaded certificates
 | 
				
			||||||
					if !srv.AutoHTTPS.IgnoreLoadedCerts &&
 | 
										if !srv.AutoHTTPS.IgnoreLoadedCerts &&
 | 
				
			||||||
						len(tlsApp.AllMatchingCertificates(d)) > 0 {
 | 
											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
 | 
											continue
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					domainsForCerts = append(domainsForCerts, d)
 | 
										domainsForCerts = append(domainsForCerts, d)
 | 
				
			||||||
@ -351,7 +373,9 @@ func (app *App) automaticHTTPS() error {
 | 
				
			|||||||
				})
 | 
									})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// manage their certificates
 | 
								// 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)
 | 
								err := tlsApp.Manage(domainsForCerts)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return fmt.Errorf("%s: managing certificate for %s: %s", srvName, domains, err)
 | 
									return fmt.Errorf("%s: managing certificate for %s: %s", srvName, domains, err)
 | 
				
			||||||
@ -368,7 +392,9 @@ func (app *App) automaticHTTPS() error {
 | 
				
			|||||||
				continue
 | 
									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
 | 
								// create HTTP->HTTPS redirects
 | 
				
			||||||
			for _, addr := range srv.Listen {
 | 
								for _, addr := range srv.Listen {
 | 
				
			||||||
@ -434,8 +460,10 @@ func (app *App) automaticHTTPS() error {
 | 
				
			|||||||
					// that the redirect runs from; simply append our
 | 
										// that the redirect runs from; simply append our
 | 
				
			||||||
					// redirect route to the existing routes, with a
 | 
										// redirect route to the existing routes, with a
 | 
				
			||||||
					// caveat that their config might override ours
 | 
										// 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",
 | 
										app.logger.Warn("server is listening on same interface as redirects, so automatic HTTP->HTTPS redirects might be overridden by your own configuration",
 | 
				
			||||||
						srvName, addr)
 | 
											zap.String("server_name", srvName),
 | 
				
			||||||
 | 
											zap.String("interface", addr),
 | 
				
			||||||
 | 
										)
 | 
				
			||||||
					srv.Routes = append(srv.Routes, redirRoute)
 | 
										srv.Routes = append(srv.Routes, redirRoute)
 | 
				
			||||||
					continue redirRoutesLoop
 | 
										continue redirRoutesLoop
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@ -524,8 +552,8 @@ var emptyHandler HandlerFunc = func(http.ResponseWriter, *http.Request) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// WeakString is a type that unmarshals any JSON value
 | 
					// WeakString is a type that unmarshals any JSON value
 | 
				
			||||||
// as a string literal, with the following exceptions:
 | 
					// as a string literal, with the following exceptions:
 | 
				
			||||||
// 1) actual string values are decoded as strings, and
 | 
					// 1) actual string values are decoded as strings; and
 | 
				
			||||||
// 2) null is decoded as empty string
 | 
					// 2) null is decoded as empty string;
 | 
				
			||||||
// and provides methods for getting the value as various
 | 
					// and provides methods for getting the value as various
 | 
				
			||||||
// primitive types. However, using this type removes any
 | 
					// primitive types. However, using this type removes any
 | 
				
			||||||
// type safety as far as deserializing JSON is concerned.
 | 
					// type safety as far as deserializing JSON is concerned.
 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,7 @@ import (
 | 
				
			|||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"html/template"
 | 
						"html/template"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
	weakrand "math/rand"
 | 
						weakrand "math/rand"
 | 
				
			||||||
	"mime"
 | 
						"mime"
 | 
				
			||||||
	"net/http"
 | 
						"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,
 | 
						// let the standard library do what it does best; note, however,
 | 
				
			||||||
	// that errors generated by ServeContent are written immediately
 | 
						// that errors generated by ServeContent are written immediately
 | 
				
			||||||
	// to the response, so we cannot handle them (but errors there
 | 
						// 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"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/textproto"
 | 
						"net/textproto"
 | 
				
			||||||
	"net/url"
 | 
					 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
@ -112,27 +111,30 @@ func addHTTPVarsToReplacer(repl caddy.Replacer, req *http.Request, w http.Respon
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
				return qs, true
 | 
									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":
 | 
								case "http.request.orig_uri":
 | 
				
			||||||
				u, _ := req.Context().Value(OriginalURLCtxKey).(url.URL)
 | 
									or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request)
 | 
				
			||||||
				return u.RequestURI(), true
 | 
									return or.RequestURI, true
 | 
				
			||||||
			case "http.request.orig_uri.path":
 | 
								case "http.request.orig_uri.path":
 | 
				
			||||||
				u, _ := req.Context().Value(OriginalURLCtxKey).(url.URL)
 | 
									or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request)
 | 
				
			||||||
				return u.Path, true
 | 
									return or.URL.Path, true
 | 
				
			||||||
			case "http.request.orig_uri.path.file":
 | 
								case "http.request.orig_uri.path.file":
 | 
				
			||||||
				u, _ := req.Context().Value(OriginalURLCtxKey).(url.URL)
 | 
									or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request)
 | 
				
			||||||
				_, file := path.Split(u.Path)
 | 
									_, file := path.Split(or.URL.Path)
 | 
				
			||||||
				return file, true
 | 
									return file, true
 | 
				
			||||||
			case "http.request.orig_uri.path.dir":
 | 
								case "http.request.orig_uri.path.dir":
 | 
				
			||||||
				u, _ := req.Context().Value(OriginalURLCtxKey).(url.URL)
 | 
									or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request)
 | 
				
			||||||
				dir, _ := path.Split(u.Path)
 | 
									dir, _ := path.Split(or.URL.Path)
 | 
				
			||||||
				return dir, true
 | 
									return dir, true
 | 
				
			||||||
			case "http.request.orig_uri.query":
 | 
								case "http.request.orig_uri.query":
 | 
				
			||||||
				u, _ := req.Context().Value(OriginalURLCtxKey).(url.URL)
 | 
									or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request)
 | 
				
			||||||
				return u.RawQuery, true
 | 
									return or.URL.RawQuery, true
 | 
				
			||||||
			case "http.request.orig_uri.query_string":
 | 
								case "http.request.orig_uri.query_string":
 | 
				
			||||||
				u, _ := req.Context().Value(OriginalURLCtxKey).(url.URL)
 | 
									or, _ := req.Context().Value(OriginalRequestCtxKey).(http.Request)
 | 
				
			||||||
				qs := u.Query().Encode()
 | 
									qs := or.URL.Query().Encode()
 | 
				
			||||||
				if qs != "" {
 | 
									if qs != "" {
 | 
				
			||||||
					qs = "?" + qs
 | 
										qs = "?" + qs
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@ -183,7 +185,7 @@ func addHTTPVarsToReplacer(repl caddy.Replacer, req *http.Request, w http.Respon
 | 
				
			|||||||
			// middleware variables
 | 
								// middleware variables
 | 
				
			||||||
			if strings.HasPrefix(key, varsReplPrefix) {
 | 
								if strings.HasPrefix(key, varsReplPrefix) {
 | 
				
			||||||
				varName := key[len(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]
 | 
									raw, ok := tbl[varName]
 | 
				
			||||||
				if !ok {
 | 
									if !ok {
 | 
				
			||||||
					// variables can be dynamic, so always return true
 | 
										// variables can be dynamic, so always return true
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,6 @@ import (
 | 
				
			|||||||
	"crypto/tls"
 | 
						"crypto/tls"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
					 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
@ -53,6 +52,9 @@ type Transport struct {
 | 
				
			|||||||
	// with the value of SplitPath. The first piece will be assumed as the
 | 
						// 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
 | 
						// actual resource (CGI script) name, and the second piece will be set to
 | 
				
			||||||
	// PATH_INFO for the CGI script to use.
 | 
						// 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"`
 | 
						SplitPath string `json:"split_path,omitempty"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Extra environment variables
 | 
						// 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
 | 
						// 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
 | 
						// if desired). Most PHP apps seem to want the original URI. Besides, this is
 | 
				
			||||||
	// how nginx defaults: http://stackoverflow.com/a/12485156/1048862
 | 
						// 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 {
 | 
						if !ok {
 | 
				
			||||||
		// some requests, like active health checks, don't add this to
 | 
							// some requests, like active health checks, don't add this to
 | 
				
			||||||
		// the request context, so we can just use the current URL
 | 
							// the request context, so we can just use the current URL
 | 
				
			||||||
		reqURL = *r.URL
 | 
							origReq = *r
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						reqURL := origReq.URL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	requestScheme := "http"
 | 
						requestScheme := "http"
 | 
				
			||||||
	if r.TLS != nil {
 | 
						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)
 | 
						rw.WriteHeader(res.StatusCode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = h.copyResponse(rw, res.Body, h.flushInterval(req, res))
 | 
						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"
 | 
				
			||||||
	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
 | 
						"github.com/caddyserver/caddy/v2/modules/caddyhttp"
 | 
				
			||||||
 | 
						"go.uber.org/zap"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
@ -39,6 +40,8 @@ type Rewrite struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	HTTPRedirect caddyhttp.WeakString `json:"http_redirect,omitempty"`
 | 
						HTTPRedirect caddyhttp.WeakString `json:"http_redirect,omitempty"`
 | 
				
			||||||
	Rehandle     bool                 `json:"rehandle,omitempty"`
 | 
						Rehandle     bool                 `json:"rehandle,omitempty"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						logger *zap.Logger
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CaddyModule returns the Caddy module information.
 | 
					// 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.
 | 
					// Validate ensures rewr's configuration is valid.
 | 
				
			||||||
func (rewr Rewrite) Validate() error {
 | 
					func (rewr Rewrite) Validate() error {
 | 
				
			||||||
	if rewr.HTTPRedirect != "" && rewr.Rehandle {
 | 
						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)
 | 
						repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
 | 
				
			||||||
	var changed bool
 | 
						var changed bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						logger := rewr.logger.With(
 | 
				
			||||||
 | 
							zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: r}),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// rewrite the method
 | 
						// rewrite the method
 | 
				
			||||||
	if rewr.Method != "" {
 | 
						if rewr.Method != "" {
 | 
				
			||||||
		method := r.Method
 | 
							method := r.Method
 | 
				
			||||||
@ -114,11 +127,15 @@ func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy
 | 
				
			|||||||
		r.RequestURI = newURI
 | 
							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
 | 
								return caddyhttp.ErrRehandle
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							if rewr.HTTPRedirect != "" {
 | 
				
			||||||
	if changed && rewr.HTTPRedirect != "" {
 | 
					 | 
				
			||||||
			statusCode, err := strconv.Atoi(repl.ReplaceAll(rewr.HTTPRedirect.String(), ""))
 | 
								statusCode, err := strconv.Atoi(repl.ReplaceAll(rewr.HTTPRedirect.String(), ""))
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return caddyhttp.Error(http.StatusInternalServerError, err)
 | 
									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)
 | 
								w.WriteHeader(statusCode)
 | 
				
			||||||
			return nil
 | 
								return nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return next.ServeHTTP(w, r)
 | 
						return next.ServeHTTP(w, r)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -17,16 +17,18 @@ package caddyhttp
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"log"
 | 
					 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/caddyserver/caddy/v2"
 | 
						"github.com/caddyserver/caddy/v2"
 | 
				
			||||||
	"github.com/caddyserver/caddy/v2/modules/caddytls"
 | 
						"github.com/caddyserver/caddy/v2/modules/caddytls"
 | 
				
			||||||
	"github.com/lucas-clemente/quic-go/http3"
 | 
						"github.com/lucas-clemente/quic-go/http3"
 | 
				
			||||||
 | 
						"go.uber.org/zap"
 | 
				
			||||||
 | 
						"go.uber.org/zap/zapcore"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Server is an HTTP server.
 | 
					// Server is an HTTP server.
 | 
				
			||||||
@ -43,11 +45,15 @@ type Server struct {
 | 
				
			|||||||
	AutoHTTPS         *AutoHTTPSConfig            `json:"automatic_https,omitempty"`
 | 
						AutoHTTPS         *AutoHTTPSConfig            `json:"automatic_https,omitempty"`
 | 
				
			||||||
	MaxRehandles      *int                        `json:"max_rehandles,omitempty"`
 | 
						MaxRehandles      *int                        `json:"max_rehandles,omitempty"`
 | 
				
			||||||
	StrictSNIHost     *bool                       `json:"strict_sni_host,omitempty"`
 | 
						StrictSNIHost     *bool                       `json:"strict_sni_host,omitempty"`
 | 
				
			||||||
 | 
						Logs              *ServerLogConfig            `json:"logs,omitempty"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// This field is not subject to compatibility promises
 | 
						// This field is not subject to compatibility promises
 | 
				
			||||||
	ExperimentalHTTP3 bool `json:"experimental_http3,omitempty"`
 | 
						ExperimentalHTTP3 bool `json:"experimental_http3,omitempty"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tlsApp       *caddytls.TLS
 | 
						tlsApp       *caddytls.TLS
 | 
				
			||||||
 | 
						logger       *zap.Logger
 | 
				
			||||||
 | 
						accessLogger *zap.Logger
 | 
				
			||||||
 | 
						errorLogger  *zap.Logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	h3server *http3.Server
 | 
						h3server *http3.Server
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -59,58 +65,106 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
	if s.h3server != nil {
 | 
						if s.h3server != nil {
 | 
				
			||||||
		err := s.h3server.SetQuicHeaders(w.Header())
 | 
							err := s.h3server.SetQuicHeaders(w.Header())
 | 
				
			||||||
		if err != nil {
 | 
							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
 | 
						// set up the context for the request
 | 
				
			||||||
	repl := caddy.NewReplacer()
 | 
						repl := caddy.NewReplacer()
 | 
				
			||||||
	ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, repl)
 | 
						ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, repl)
 | 
				
			||||||
	ctx = context.WithValue(ctx, ServerCtxKey, s)
 | 
						ctx = context.WithValue(ctx, ServerCtxKey, s)
 | 
				
			||||||
	ctx = context.WithValue(ctx, VarCtxKey, make(map[string]interface{}))
 | 
						ctx = context.WithValue(ctx, VarsCtxKey, make(map[string]interface{}))
 | 
				
			||||||
	ctx = context.WithValue(ctx, OriginalURLCtxKey, cloneURL(r.URL))
 | 
						var url2 url.URL // avoid letting this escape to the heap
 | 
				
			||||||
 | 
						ctx = context.WithValue(ctx, OriginalRequestCtxKey, originalRequest(r, &url2))
 | 
				
			||||||
	r = r.WithContext(ctx)
 | 
						r = r.WithContext(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// once the pointer to the request won't change
 | 
						// once the pointer to the request won't change
 | 
				
			||||||
	// anymore, finish setting up the replacer
 | 
						// anymore, finish setting up the replacer
 | 
				
			||||||
	addHTTPVarsToReplacer(repl, r, w)
 | 
						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)
 | 
						err := s.executeCompositeRoute(w, r, s.Routes)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		// add the raw error value to the request context
 | 
							// prepare the error log
 | 
				
			||||||
		// so it can be accessed by error handlers
 | 
							logger := errLog
 | 
				
			||||||
		c := context.WithValue(r.Context(), ErrorCtxKey, err)
 | 
							if s.Logs.LoggerNames != nil {
 | 
				
			||||||
		r = r.WithContext(c)
 | 
								logger = logger.Named(s.Logs.LoggerNames[r.Host])
 | 
				
			||||||
 | 
					 | 
				
			||||||
		// 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)
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 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 {
 | 
							if s.Errors != nil && len(s.Errors.Routes) > 0 {
 | 
				
			||||||
			err := s.executeCompositeRoute(w, r, s.Errors.Routes)
 | 
								// execute user-defined error handling route
 | 
				
			||||||
			if err != nil {
 | 
								err2 := s.executeCompositeRoute(w, r, s.Errors.Routes)
 | 
				
			||||||
				// TODO: what should we do if the error handler has an error?
 | 
								if err2 == nil {
 | 
				
			||||||
				log.Printf("[ERROR] [%s %s] handling error: %v", r.Method, r.RequestURI, err)
 | 
									// 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 {
 | 
							} else {
 | 
				
			||||||
			// TODO: polish the default error handling
 | 
								logger.Error(errMsg, errFields...)
 | 
				
			||||||
			log.Printf("[ERROR] [%s %s] %v", r.Method, r.RequestURI, err)
 | 
								w.WriteHeader(errStatus)
 | 
				
			||||||
			if handlerErr, ok := err.(HandlerError); ok {
 | 
					 | 
				
			||||||
				w.WriteHeader(handlerErr.StatusCode)
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				w.WriteHeader(http.StatusInternalServerError)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -278,26 +332,101 @@ type HTTPErrorConfig struct {
 | 
				
			|||||||
	Routes RouteList `json:"routes,omitempty"`
 | 
						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
 | 
					// cloneURL makes a copy of r.URL and returns a
 | 
				
			||||||
// new value that doesn't reference the original.
 | 
					// new value that doesn't reference the original.
 | 
				
			||||||
func cloneURL(u *url.URL) url.URL {
 | 
					func cloneURL(u *url.URL) *url.URL {
 | 
				
			||||||
	urlCopy := *u
 | 
						urlCopy := *u
 | 
				
			||||||
	if u.User != nil {
 | 
						if u.User != nil {
 | 
				
			||||||
		userInfo := new(url.Userinfo)
 | 
							userInfo := new(url.Userinfo)
 | 
				
			||||||
		*userInfo = *u.User
 | 
							*userInfo = *u.User
 | 
				
			||||||
		urlCopy.User = userInfo
 | 
							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.
 | 
					// Context keys for HTTP request context values.
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	// For referencing the server instance
 | 
						// For referencing the server instance
 | 
				
			||||||
	ServerCtxKey caddy.CtxKey = "server"
 | 
						ServerCtxKey caddy.CtxKey = "server"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// For the request's variable table
 | 
						// 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
 | 
						// For a partial copy of the unmodified request that
 | 
				
			||||||
	OriginalURLCtxKey caddy.CtxKey = "original_url"
 | 
						// 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
 | 
						// write response body
 | 
				
			||||||
	if s.Body != "" {
 | 
						if s.Body != "" {
 | 
				
			||||||
		fmt.Fprint(w, repl.ReplaceAll(s.Body, ""))
 | 
							fmt.Fprint(w, repl.ReplaceKnown(s.Body, ""))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
 | 
				
			|||||||
@ -30,8 +30,15 @@ func init() {
 | 
				
			|||||||
// matchers, or for routes with matchers that must be have deferred
 | 
					// matchers, or for routes with matchers that must be have deferred
 | 
				
			||||||
// evaluation (e.g. if they depend on placeholders created by other
 | 
					// evaluation (e.g. if they depend on placeholders created by other
 | 
				
			||||||
// matchers that need to be evaluated first).
 | 
					// 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 {
 | 
					type Subroute struct {
 | 
				
			||||||
	Routes RouteList        `json:"routes,omitempty"`
 | 
						Routes RouteList        `json:"routes,omitempty"`
 | 
				
			||||||
 | 
						Errors *HTTPErrorConfig `json:"errors,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CaddyModule returns the Caddy module information.
 | 
					// CaddyModule returns the Caddy module information.
 | 
				
			||||||
@ -47,7 +54,13 @@ func (sr *Subroute) Provision(ctx caddy.Context) error {
 | 
				
			|||||||
	if sr.Routes != nil {
 | 
						if sr.Routes != nil {
 | 
				
			||||||
		err := sr.Routes.Provision(ctx)
 | 
							err := sr.Routes.Provision(ctx)
 | 
				
			||||||
		if err != nil {
 | 
							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
 | 
						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 {
 | 
					func (sr *Subroute) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
 | 
				
			||||||
	subroute := sr.Routes.BuildCompositeRoute(r)
 | 
						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
 | 
					// Interface guards
 | 
				
			||||||
 | 
				
			|||||||
@ -38,7 +38,7 @@ func (VarsMiddleware) CaddyModule() caddy.ModuleInfo {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (t VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error {
 | 
					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)
 | 
						repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
 | 
				
			||||||
	for k, v := range t {
 | 
						for k, v := range t {
 | 
				
			||||||
		keyExpanded := repl.ReplaceAll(k, "")
 | 
							keyExpanded := repl.ReplaceAll(k, "")
 | 
				
			||||||
@ -62,7 +62,7 @@ func (VarsMatcher) CaddyModule() caddy.ModuleInfo {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Match matches a request based on variables in the context.
 | 
					// Match matches a request based on variables in the context.
 | 
				
			||||||
func (m VarsMatcher) Match(r *http.Request) bool {
 | 
					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)
 | 
						repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
 | 
				
			||||||
	for k, v := range m {
 | 
						for k, v := range m {
 | 
				
			||||||
		keyExpanded := repl.ReplaceAll(k, "")
 | 
							keyExpanded := repl.ReplaceAll(k, "")
 | 
				
			||||||
 | 
				
			|||||||
@ -18,7 +18,6 @@ import (
 | 
				
			|||||||
	"crypto/tls"
 | 
						"crypto/tls"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"log"
 | 
					 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@ -26,6 +25,7 @@ import (
 | 
				
			|||||||
	"github.com/caddyserver/caddy/v2"
 | 
						"github.com/caddyserver/caddy/v2"
 | 
				
			||||||
	"github.com/go-acme/lego/v3/challenge"
 | 
						"github.com/go-acme/lego/v3/challenge"
 | 
				
			||||||
	"github.com/mholt/certmagic"
 | 
						"github.com/mholt/certmagic"
 | 
				
			||||||
 | 
						"go.uber.org/zap"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
@ -43,6 +43,7 @@ type TLS struct {
 | 
				
			|||||||
	ctx                caddy.Context
 | 
						ctx                caddy.Context
 | 
				
			||||||
	storageCleanTicker *time.Ticker
 | 
						storageCleanTicker *time.Ticker
 | 
				
			||||||
	storageCleanStop   chan struct{}
 | 
						storageCleanStop   chan struct{}
 | 
				
			||||||
 | 
						logger             *zap.Logger
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CaddyModule returns the Caddy module information.
 | 
					// CaddyModule returns the Caddy module information.
 | 
				
			||||||
@ -56,6 +57,7 @@ func (TLS) CaddyModule() caddy.ModuleInfo {
 | 
				
			|||||||
// Provision sets up the configuration for the TLS app.
 | 
					// Provision sets up the configuration for the TLS app.
 | 
				
			||||||
func (t *TLS) Provision(ctx caddy.Context) error {
 | 
					func (t *TLS) Provision(ctx caddy.Context) error {
 | 
				
			||||||
	t.ctx = ctx
 | 
						t.ctx = ctx
 | 
				
			||||||
 | 
						t.logger = ctx.Logger(t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// set up a new certificate cache; this (re)loads all certificates
 | 
						// set up a new certificate cache; this (re)loads all certificates
 | 
				
			||||||
	cacheOpts := certmagic.CacheOptions{
 | 
						cacheOpts := certmagic.CacheOptions{
 | 
				
			||||||
@ -290,7 +292,7 @@ func (t *TLS) cleanStorageUnits() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	storageClean = time.Now()
 | 
						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.
 | 
					// 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
 | 
					package caddy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"runtime"
 | 
						"runtime"
 | 
				
			||||||
@ -28,6 +29,8 @@ type Replacer interface {
 | 
				
			|||||||
	Delete(variable string)
 | 
						Delete(variable string)
 | 
				
			||||||
	Map(ReplacementFunc)
 | 
						Map(ReplacementFunc)
 | 
				
			||||||
	ReplaceAll(input, empty string) string
 | 
						ReplaceAll(input, empty string) string
 | 
				
			||||||
 | 
						ReplaceKnown(input, empty string) string
 | 
				
			||||||
 | 
						ReplaceOrErr(input string, errOnEmpty, errOnUnknown bool) (string, error)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewReplacer returns a new Replacer.
 | 
					// NewReplacer returns a new Replacer.
 | 
				
			||||||
@ -70,12 +73,34 @@ func (r *replacer) fromStatic(key string) (val string, ok bool) {
 | 
				
			|||||||
	return
 | 
						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
 | 
					// ReplaceAll efficiently replaces placeholders in input with
 | 
				
			||||||
// their values. Unrecognized placeholders will not be replaced.
 | 
					// their values. All placeholders are replaced in the output
 | 
				
			||||||
// Values that are empty string will be substituted with empty.
 | 
					// whether they are recognized or not. Values that are empty
 | 
				
			||||||
 | 
					// string will be substituted with empty.
 | 
				
			||||||
func (r *replacer) ReplaceAll(input, empty string) string {
 | 
					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)) {
 | 
						if !strings.Contains(input, string(phOpen)) {
 | 
				
			||||||
		return input
 | 
							return input, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var sb strings.Builder
 | 
						var sb strings.Builder
 | 
				
			||||||
@ -100,25 +125,40 @@ func (r *replacer) ReplaceAll(input, empty string) string {
 | 
				
			|||||||
		// trim opening bracket
 | 
							// trim opening bracket
 | 
				
			||||||
		key := input[i+1 : end]
 | 
							key := input[i+1 : end]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// try to get a value for this key; if
 | 
							// try to get a value for this key,
 | 
				
			||||||
		// the key is not recognized, do not
 | 
							// handle empty values accordingly
 | 
				
			||||||
		// perform any replacement
 | 
					 | 
				
			||||||
		var found bool
 | 
							var found bool
 | 
				
			||||||
		for _, mapFunc := range r.providers {
 | 
							for _, mapFunc := range r.providers {
 | 
				
			||||||
			if val, ok := mapFunc(key); ok {
 | 
								if val, ok := mapFunc(key); ok {
 | 
				
			||||||
				found = true
 | 
									found = true
 | 
				
			||||||
				if val != "" {
 | 
									if val == "" {
 | 
				
			||||||
					sb.WriteString(val)
 | 
										if errOnEmpty {
 | 
				
			||||||
 | 
											return "", fmt.Errorf("evaluated placeholder %s%s%s is empty",
 | 
				
			||||||
 | 
												string(phOpen), key, string(phClose))
 | 
				
			||||||
					} else if empty != "" {
 | 
										} else if empty != "" {
 | 
				
			||||||
						sb.WriteString(empty)
 | 
											sb.WriteString(empty)
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										sb.WriteString(val)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				break
 | 
									break
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if !found {
 | 
							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
 | 
									lastWriteCursor = i
 | 
				
			||||||
				continue
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// advance cursor to end of placeholder
 | 
							// advance cursor to end of placeholder
 | 
				
			||||||
		i = end
 | 
							i = end
 | 
				
			||||||
@ -128,7 +168,7 @@ func (r *replacer) ReplaceAll(input, empty string) string {
 | 
				
			|||||||
	// flush any unwritten remainder
 | 
						// flush any unwritten remainder
 | 
				
			||||||
	sb.WriteString(input[lastWriteCursor:])
 | 
						sb.WriteString(input[lastWriteCursor:])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return sb.String()
 | 
						return sb.String(), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ReplacementFunc is a function that returns a replacement
 | 
					// ReplacementFunc is a function that returns a replacement
 | 
				
			||||||
@ -142,8 +182,7 @@ func globalDefaultReplacements(key string) (string, bool) {
 | 
				
			|||||||
	// check environment variable
 | 
						// check environment variable
 | 
				
			||||||
	const envPrefix = "env."
 | 
						const envPrefix = "env."
 | 
				
			||||||
	if strings.HasPrefix(key, envPrefix) {
 | 
						if strings.HasPrefix(key, envPrefix) {
 | 
				
			||||||
		val := os.Getenv(key[len(envPrefix):])
 | 
							return os.Getenv(key[len(envPrefix):]), true
 | 
				
			||||||
		return val, val != ""
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch key {
 | 
						switch key {
 | 
				
			||||||
 | 
				
			|||||||
@ -85,7 +85,7 @@ func TestReplacerSet(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestReplacerReplaceAll(t *testing.T) {
 | 
					func TestReplacerReplaceKnown(t *testing.T) {
 | 
				
			||||||
	rep := replacer{
 | 
						rep := replacer{
 | 
				
			||||||
		providers: []ReplacementFunc{
 | 
							providers: []ReplacementFunc{
 | 
				
			||||||
			// split our possible vars to two functions (to test if both functions are called)
 | 
								// 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 ",
 | 
								expected:  "val1 {nope} test-123 ",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	} {
 | 
						} {
 | 
				
			||||||
		actual := rep.ReplaceAll(tc.testInput, "EMPTY")
 | 
							actual := rep.ReplaceKnown(tc.testInput, "EMPTY")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// test if all are replaced as expected
 | 
							// test if all are replaced as expected
 | 
				
			||||||
		if actual != tc.expected {
 | 
							if actual != tc.expected {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										14
									
								
								sigtrap.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								sigtrap.go
									
									
									
									
									
								
							@ -15,9 +15,10 @@
 | 
				
			|||||||
package caddy
 | 
					package caddy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"log"
 | 
					 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"os/signal"
 | 
						"os/signal"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"go.uber.org/zap"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TrapSignals create signal/interrupt handlers as best it can for the
 | 
					// TrapSignals create signal/interrupt handlers as best it can for the
 | 
				
			||||||
@ -41,11 +42,11 @@ func trapSignalsCrossPlatform() {
 | 
				
			|||||||
			<-shutdown
 | 
								<-shutdown
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if i > 0 {
 | 
								if i > 0 {
 | 
				
			||||||
				log.Println("[INFO] SIGINT: Force quit")
 | 
									Log().Warn("force quit", zap.String("signal", "SIGINT"))
 | 
				
			||||||
				os.Exit(ExitCodeForceQuit)
 | 
									os.Exit(ExitCodeForceQuit)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			log.Println("[INFO] SIGINT: Shutting down")
 | 
								Log().Info("shutting down", zap.String("signal", "SIGINT"))
 | 
				
			||||||
			go gracefulStop("SIGINT")
 | 
								go gracefulStop("SIGINT")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
@ -57,11 +58,14 @@ func gracefulStop(sigName string) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	err := stopAndCleanup()
 | 
						err := stopAndCleanup()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Printf("[ERROR] %s stop: %v", sigName, err)
 | 
							Log().Error("stopping",
 | 
				
			||||||
 | 
								zap.String("signal", sigName),
 | 
				
			||||||
 | 
								zap.Error(err),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
		exitCode = ExitCodeFailedQuit
 | 
							exitCode = ExitCodeFailedQuit
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Printf("[INFO] %s: Shutdown done", sigName)
 | 
						Log().Info("shutdown done", zap.String("signal", sigName))
 | 
				
			||||||
	os.Exit(exitCode)
 | 
						os.Exit(exitCode)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -17,12 +17,12 @@
 | 
				
			|||||||
package caddy
 | 
					package caddy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"log"
 | 
					 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"os/signal"
 | 
						"os/signal"
 | 
				
			||||||
	"syscall"
 | 
						"syscall"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/mholt/certmagic"
 | 
						"github.com/mholt/certmagic"
 | 
				
			||||||
 | 
						"go.uber.org/zap"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// trapSignalsPosix captures POSIX-only signals.
 | 
					// trapSignalsPosix captures POSIX-only signals.
 | 
				
			||||||
@ -34,23 +34,23 @@ func trapSignalsPosix() {
 | 
				
			|||||||
		for sig := range sigchan {
 | 
							for sig := range sigchan {
 | 
				
			||||||
			switch sig {
 | 
								switch sig {
 | 
				
			||||||
			case syscall.SIGQUIT:
 | 
								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
 | 
									certmagic.CleanUpOwnLocks() // try to clean up locks anyway, it's important
 | 
				
			||||||
				os.Exit(ExitCodeForceQuit)
 | 
									os.Exit(ExitCodeForceQuit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case syscall.SIGTERM:
 | 
								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")
 | 
									gracefulStop("SIGTERM")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case syscall.SIGUSR1:
 | 
								case syscall.SIGUSR1:
 | 
				
			||||||
				log.Println("[INFO] SIGUSR1: Not implemented")
 | 
									Log().Info("not implemented", zap.String("signal", "SIGUSR1"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case syscall.SIGUSR2:
 | 
								case syscall.SIGUSR2:
 | 
				
			||||||
				log.Println("[INFO] SIGUSR2: Not implemented")
 | 
									Log().Info("not implemented", zap.String("signal", "SIGUSR2"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case syscall.SIGHUP:
 | 
								case syscall.SIGHUP:
 | 
				
			||||||
				// ignore; this signal is sometimes sent outside of the user's control
 | 
									// 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
 | 
					// UsagePool is a thread-safe map that pools values
 | 
				
			||||||
// based on usage; a LoadOrStore operation increments
 | 
					// based on usage (reference counting). Values are
 | 
				
			||||||
// the usage, and a Delete decrements from the usage.
 | 
					// only inserted if they do not already exist. There
 | 
				
			||||||
// If the usage count reaches 0, the value will be
 | 
					// are two ways to add values to the pool:
 | 
				
			||||||
// removed from the map. There is no way to overwrite
 | 
					//
 | 
				
			||||||
// existing keys in the pool without first deleting
 | 
					// 1) LoadOrStore will increment usage and store the
 | 
				
			||||||
// it as many times as it was stored. Deleting too
 | 
					//    value immediately if it does not already exist
 | 
				
			||||||
// many times will panic.
 | 
					// 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
 | 
					// 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 {
 | 
					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 {
 | 
					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
 | 
					// 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.
 | 
					// true if the usage count reached 0 and the value was deleted.
 | 
				
			||||||
// It panics if the usage count drops below 0; always call
 | 
					// It panics if the usage count drops below 0; always call
 | 
				
			||||||
// Delete precisely as many times as LoadOrStore.
 | 
					// Delete precisely as many times as LoadOrStore.
 | 
				
			||||||
func (up *UsagePool) Delete(key interface{}) (deleted bool) {
 | 
					func (up *UsagePool) Delete(key interface{}) (deleted bool, err error) {
 | 
				
			||||||
	usageVal, ok := up.pool.Load(key)
 | 
						up.Lock()
 | 
				
			||||||
 | 
						upv, ok := up.pool[key]
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return false
 | 
							up.Unlock()
 | 
				
			||||||
 | 
							return false, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	upv := usageVal.(*usagePoolVal)
 | 
						refs := atomic.AddInt32(&upv.refs, -1)
 | 
				
			||||||
	newUsage := atomic.AddInt32(&upv.usage, -1)
 | 
						if refs == 0 {
 | 
				
			||||||
	if newUsage == 0 {
 | 
							delete(up.pool, key)
 | 
				
			||||||
		up.pool.Delete(key)
 | 
							up.Unlock()
 | 
				
			||||||
		return true
 | 
							upv.RLock()
 | 
				
			||||||
	} else if newUsage < 0 {
 | 
							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)",
 | 
								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
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Range iterates the pool the same way sync.Map.Range does.
 | 
					// Constructor is a function that returns a new value
 | 
				
			||||||
// This does not affect usage counts.
 | 
					// that can destruct itself when it is no longer needed.
 | 
				
			||||||
func (up *UsagePool) Range(f func(key, value interface{}) bool) {
 | 
					type Constructor func() (Destructor, error)
 | 
				
			||||||
	up.pool.Range(func(key, value interface{}) bool {
 | 
					
 | 
				
			||||||
		return f(key, value.(*usagePoolVal).value)
 | 
					// Destructor is a value that can clean itself up when
 | 
				
			||||||
	})
 | 
					// it is deallocated.
 | 
				
			||||||
 | 
					type Destructor interface {
 | 
				
			||||||
 | 
						Destruct() error
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type usagePoolVal struct {
 | 
					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{}
 | 
						value interface{}
 | 
				
			||||||
 | 
						err   error
 | 
				
			||||||
 | 
						sync.RWMutex
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user