diff --git a/config/envoyconfig/envoyconfig.go b/config/envoyconfig/envoyconfig.go index 9e78f0a99..942ec9248 100644 --- a/config/envoyconfig/envoyconfig.go +++ b/config/envoyconfig/envoyconfig.go @@ -82,11 +82,11 @@ func buildAccessLogs(options *config.Options) []*envoy_config_accesslog_v3.Acces lvl = options.LogLevel } if lvl == "" { - lvl = "debug" + lvl = config.LogLevelDebug } switch lvl { - case "trace", "debug", "info": + case config.LogLevelTrace, config.LogLevelDebug, config.LogLevelInfo: default: // don't log access requests for levels > info return nil diff --git a/config/log.go b/config/log.go index b988911d8..68bff8c66 100644 --- a/config/log.go +++ b/config/log.go @@ -41,6 +41,6 @@ func (mgr *LogManager) OnConfigChange(_ context.Context, cfg *Config) { } if cfg.Options.LogLevel != "" { - log.SetLevel(cfg.Options.LogLevel) + log.SetLevel(cfg.Options.LogLevel.ToZerolog()) } } diff --git a/config/log_level.go b/config/log_level.go new file mode 100644 index 000000000..761cb3017 --- /dev/null +++ b/config/log_level.go @@ -0,0 +1,107 @@ +package config + +import ( + "fmt" + + "github.com/rs/zerolog" +) + +// A LogLevel represents a logging level. +type LogLevel string + +// Known log levels. +const ( + LogLevelUnset LogLevel = "" // defaults to info + LogLevelTrace LogLevel = "trace" + LogLevelDebug LogLevel = "debug" + LogLevelInfo LogLevel = "info" + LogLevelWarn LogLevel = "warn" + LogLevelWarning LogLevel = "warning" + LogLevelError LogLevel = "error" + LogLevelCritical LogLevel = "critical" + LogLevelFatal LogLevel = "fatal" + LogLevelPanic LogLevel = "panic" + LogLevelOff LogLevel = "off" + LogLevelNone LogLevel = "none" + LogLevelDisabled LogLevel = "disabled" +) + +// AllLogLevels are all of the known log levels. +var AllLogLevels = [...]LogLevel{ + LogLevelUnset, + LogLevelTrace, + LogLevelDebug, + LogLevelInfo, + LogLevelWarn, + LogLevelWarning, + LogLevelError, + LogLevelCritical, + LogLevelFatal, + LogLevelPanic, + LogLevelOff, + LogLevelNone, + LogLevelDisabled, +} + +var logLevelLookup = func() map[LogLevel]struct{} { + m := map[LogLevel]struct{}{} + for _, lvl := range AllLogLevels { + m[lvl] = struct{}{} + } + return m +}() + +// ValidateLogLevel validates that a log level is one of the known log levels. +func ValidateLogLevel(lvl LogLevel) error { + _, ok := logLevelLookup[lvl] + if !ok { + return fmt.Errorf("unknown log level: %s", lvl) + } + return nil +} + +// ToZerolog converts the log level to a level zerolog expects +func (lvl LogLevel) ToZerolog() zerolog.Level { + switch lvl { + case LogLevelTrace: + return zerolog.TraceLevel + case LogLevelDebug: + return zerolog.DebugLevel + case LogLevelInfo, LogLevelUnset: + return zerolog.InfoLevel + case LogLevelWarn, LogLevelWarning: + return zerolog.WarnLevel + case LogLevelError: + return zerolog.ErrorLevel + case LogLevelCritical, LogLevelFatal: + return zerolog.FatalLevel + case LogLevelPanic: + return zerolog.PanicLevel + case LogLevelOff, LogLevelNone, LogLevelDisabled: + return zerolog.Disabled + default: + panic(fmt.Sprintf("unknown log level: %s", lvl)) + } +} + +// ToEnvoy converts the log level to a string envoy expects. +func (lvl LogLevel) ToEnvoy() string { + switch lvl { + case LogLevelTrace: + return "trace" + case LogLevelDebug: + return "debug" + case LogLevelInfo, LogLevelUnset: + return "info" + case LogLevelWarn, LogLevelWarning: + return "warn" + case LogLevelError: + return "error" + case LogLevelCritical, LogLevelFatal, LogLevelPanic: + return "critical" + case LogLevelOff, LogLevelNone, LogLevelDisabled: + return "off" + default: + panic(fmt.Sprintf("unknown log level: %s", lvl)) + } +} diff --git a/config/options.go b/config/options.go index 6abf17af6..bfb8baab8 100644 --- a/config/options.go +++ b/config/options.go @@ -62,11 +62,11 @@ type Options struct { // LogLevel sets the global override for log level. All Loggers will use at least this value. // Possible options are "info","warn","debug" and "error". Defaults to "info". - LogLevel string `mapstructure:"log_level" yaml:"log_level,omitempty"` + LogLevel LogLevel `mapstructure:"log_level" yaml:"log_level,omitempty"` // ProxyLogLevel sets the log level for the proxy service. // Possible options are "info","warn", and "error". Defaults to the value of `LogLevel`. - ProxyLogLevel string `mapstructure:"proxy_log_level" yaml:"proxy_log_level,omitempty"` + ProxyLogLevel LogLevel `mapstructure:"proxy_log_level" yaml:"proxy_log_level,omitempty"` // SharedKey is the shared secret authorization key used to mutually authenticate // requests between services. @@ -302,7 +302,7 @@ type certificateFilePair struct { // DefaultOptions are the default configuration options for pomerium var defaultOptions = Options{ Debug: false, - LogLevel: "info", + LogLevel: LogLevelInfo, Services: "all", CookieHTTPOnly: true, CookieSecure: true, @@ -741,6 +741,14 @@ func (o *Options) Validate() error { return fmt.Errorf("config: invalid cookie_same_site: %w", err) } + if err := ValidateLogLevel(o.LogLevel); err != nil { + return fmt.Errorf("config: invalid log_level: %w", err) + } + + if err := ValidateLogLevel(o.ProxyLogLevel); err != nil { + return fmt.Errorf("config: invalid proxy_log_level: %w", err) + } + return nil } @@ -1320,8 +1328,8 @@ func (o *Options) ApplySettings(ctx context.Context, certsIndex *cryptutil.Certi set(&o.InstallationID, settings.InstallationId) set(&o.Debug, settings.Debug) - set(&o.LogLevel, settings.LogLevel) - set(&o.ProxyLogLevel, settings.ProxyLogLevel) + setLogLevel(&o.LogLevel, settings.LogLevel) + setLogLevel(&o.ProxyLogLevel, settings.ProxyLogLevel) set(&o.SharedKey, settings.SharedSecret) set(&o.Services, settings.Services) set(&o.Addr, settings.Address) @@ -1474,6 +1482,13 @@ func setDuration(dst *time.Duration, src *durationpb.Duration) { *dst = src.AsDuration() } +func setLogLevel(dst *LogLevel, src *string) { + if src == nil { + return + } + *dst = LogLevel(*src) +} + func setOptional[T any](dst **T, src *T) { if src == nil { return diff --git a/internal/log/log.go b/internal/log/log.go index 5bd44280e..8f1740b90 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -64,22 +64,22 @@ func ZapLogger() *zap.Logger { return zapLogger.Load() } -// SetLevel sets the minimum global log level. Options are 'debug' 'info' 'warn' and 'error'. -// Defaults to 'debug' -func SetLevel(level string) { +// SetLevel sets the minimum global log level. +func SetLevel(level zerolog.Level) { + zerolog.SetGlobalLevel(level) switch level { - case "info": - zerolog.SetGlobalLevel(zerolog.InfoLevel) - zapLevel.SetLevel(zapcore.InfoLevel) - case "warn": - zerolog.SetGlobalLevel(zerolog.WarnLevel) - zapLevel.SetLevel(zapcore.WarnLevel) - case "error": - zerolog.SetGlobalLevel(zerolog.ErrorLevel) - zapLevel.SetLevel(zapcore.ErrorLevel) - default: - zerolog.SetGlobalLevel(zerolog.DebugLevel) + case zerolog.DebugLevel, zerolog.TraceLevel: zapLevel.SetLevel(zapcore.DebugLevel) + case zerolog.WarnLevel: + zapLevel.SetLevel(zapcore.WarnLevel) + case zerolog.ErrorLevel: + zapLevel.SetLevel(zapcore.ErrorLevel) + case zerolog.FatalLevel: + zapLevel.SetLevel(zapcore.FatalLevel) + case zerolog.PanicLevel: + zapLevel.SetLevel(zapcore.PanicLevel) + default: + zapLevel.SetLevel(zapcore.InfoLevel) } } diff --git a/internal/log/log_test.go b/internal/log/log_test.go index 40ce4e194..a58bd30a8 100644 --- a/internal/log/log_test.go +++ b/internal/log/log_test.go @@ -134,19 +134,19 @@ func Example() { func ExampleSetLevel() { setup() - log.SetLevel("info") + log.SetLevel(zerolog.InfoLevel) log.Debug(context.Background()).Msg("Debug") log.Info(context.Background()).Msg("Debug or Info") - log.SetLevel("warn") + log.SetLevel(zerolog.WarnLevel) log.Debug(context.Background()).Msg("Debug") log.Info(context.Background()).Msg("Debug or Info") log.Warn(context.Background()).Msg("Debug or Info or Warn") - log.SetLevel("error") + log.SetLevel(zerolog.ErrorLevel) log.Debug(context.Background()).Msg("Debug") log.Info(context.Background()).Msg("Debug or Info") log.Warn(context.Background()).Msg("Debug or Info or Warn") log.Error(context.Background()).Msg("Debug or Info or Warn or Error") - log.SetLevel("default-fall-through") + log.SetLevel(zerolog.DebugLevel) log.Debug(context.Background()).Msg("Debug") // Output: diff --git a/pkg/envoy/envoy.go b/pkg/envoy/envoy.go index 9f99ddd97..3d19dc26e 100644 --- a/pkg/envoy/envoy.go +++ b/pkg/envoy/envoy.go @@ -37,7 +37,7 @@ const ( type serverOptions struct { services string - logLevel string + logLevel config.LogLevel } // A Server is a pomerium proxy implemented via envoy. @@ -113,7 +113,7 @@ func (srv *Server) update(ctx context.Context, cfg *config.Config) { options := serverOptions{ services: cfg.Options.Services, - logLevel: firstNonEmpty(cfg.Options.ProxyLogLevel, cfg.Options.LogLevel, "debug"), + logLevel: firstNonEmpty(cfg.Options.ProxyLogLevel, cfg.Options.LogLevel, config.LogLevelDebug), } if cmp.Equal(srv.options, options, cmp.AllowUnexported(serverOptions{})) { @@ -140,7 +140,7 @@ func (srv *Server) run(ctx context.Context, cfg *config.Config) error { args := []string{ "-c", configFileName, - "--log-level", srv.options.logLevel, + "--log-level", srv.options.logLevel.ToEnvoy(), "--log-format", "[LOG_FORMAT]%l--%n--%v", "--log-format-escaped", } diff --git a/pkg/envoy/misc.go b/pkg/envoy/misc.go index 59de03bef..ebba53ce3 100644 --- a/pkg/envoy/misc.go +++ b/pkg/envoy/misc.go @@ -8,7 +8,7 @@ import ( envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" ) -func firstNonEmpty(args ...string) string { +func firstNonEmpty[T interface{ ~string }](args ...T) T { for _, a := range args { if a != "" { return a