diff --git a/cmd/s3-gw/app.go b/cmd/s3-gw/app.go index d8ec9774d..f1e5ec619 100644 --- a/cmd/s3-gw/app.go +++ b/cmd/s3-gw/app.go @@ -6,7 +6,10 @@ import ( "fmt" "net" "net/http" + "os" + "os/signal" "strconv" + "syscall" "time" "github.com/gorilla/mux" @@ -42,6 +45,17 @@ type ( webDone chan struct{} wrkDone chan struct{} + + settings *appSettings + } + + appSettings struct { + LogLevel zap.AtomicLevel + } + + Logger struct { + logger *zap.Logger + lvl zap.AtomicLevel } tlsConfig struct { @@ -54,7 +68,9 @@ type ( } ) -func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App { +func newApp(ctx context.Context, log *Logger, v *viper.Viper) *App { + l := log.logger + var ( key *keys.PrivateKey err error @@ -245,8 +261,8 @@ func (a *App) Wait() { a.log.Info("application finished") } -// Server runs HTTP server to handle S3 API requests. -func (a *App) Server(ctx context.Context) { +// Serve runs HTTP server to handle S3 API requests. +func (a *App) Serve(ctx context.Context) { var ( err error lis net.Listener @@ -299,7 +315,18 @@ func (a *App) Server(ctx context.Context) { } }() - <-ctx.Done() + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGHUP) + +LOOP: + for { + select { + case <-ctx.Done(): + break LOOP + case <-sigs: + a.configReload() + } + } ctx, cancel := context.WithTimeout(context.Background(), defaultShutdownTimeout) defer cancel() @@ -312,6 +339,31 @@ func (a *App) Server(ctx context.Context) { close(a.webDone) } +func (a *App) configReload() { + a.log.Info("SIGHUP config reload started") + + if !a.cfg.IsSet(cmdConfig) { + a.log.Warn("failed to reload config because it's missed") + return + } + if err := readConfig(a.cfg); err != nil { + a.log.Warn("failed to reload config", zap.Error(err)) + return + } + + a.updateSettings() + + a.log.Info("SIGHUP config reload completed") +} + +func (a *App) updateSettings() { + if lvl, err := getLogLevel(a.cfg); err != nil { + a.log.Warn("log level won't be updated", zap.Error(err)) + } else { + a.settings.LogLevel.SetLevel(lvl) + } +} + func getNotificationsOptions(v *viper.Viper, l *zap.Logger) *notifications.Options { cfg := notifications.Options{} cfg.URL = v.GetString(cfgNATSEndpoint) diff --git a/cmd/s3-gw/app_settings.go b/cmd/s3-gw/app_settings.go index eaa12a05a..0e6a39fc9 100644 --- a/cmd/s3-gw/app_settings.go +++ b/cmd/s3-gw/app_settings.go @@ -15,6 +15,7 @@ import ( "github.com/spf13/pflag" "github.com/spf13/viper" "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) const ( @@ -183,7 +184,7 @@ func newSettings() *viper.Viper { flags.StringP(cmdWallet, "w", "", `path to the wallet`) flags.String(cmdAddress, "", `address of wallet account`) - config := flags.String(cmdConfig, "", "config path") + flags.String(cmdConfig, "", "config path") flags.Duration(cfgHealthcheckTimeout, defaultHealthcheckTimeout, "set timeout to check node health during rebalance") flags.Duration(cfgConnectTimeout, defaultConnectTimeout, "set timeout to connect to NeoFS nodes") @@ -291,12 +292,79 @@ func newSettings() *viper.Viper { } if v.IsSet(cmdConfig) { - if cfgFile, err := os.Open(*config); err != nil { - panic(err) - } else if err := v.ReadConfig(cfgFile); err != nil { + if err := readConfig(v); err != nil { panic(err) } } return v } + +func readConfig(v *viper.Viper) error { + cfgFileName := v.GetString(cmdConfig) + cfgFile, err := os.Open(cfgFileName) + if err != nil { + return err + } + if err = v.ReadConfig(cfgFile); err != nil { + return err + } + + return cfgFile.Close() +} + +// newLogger constructs a Logger instance for the current application. +// Panics on failure. +// +// Logger contains a logger is built from zap's production logging configuration with: +// - parameterized level (debug by default) +// - console encoding +// - ISO8601 time encoding +// +// and atomic log level to dynamically change it. +// +// Logger records a stack trace for all messages at or above fatal level. +// +// See also zapcore.Level, zap.NewProductionConfig, zap.AddStacktrace. +func newLogger(v *viper.Viper) *Logger { + lvl, err := getLogLevel(v) + if err != nil { + panic(err) + } + + c := zap.NewProductionConfig() + c.Level = zap.NewAtomicLevelAt(lvl) + c.Encoding = "console" + c.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + + l, err := c.Build( + zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)), + ) + if err != nil { + panic(fmt.Sprintf("build zap logger instance: %v", err)) + } + + return &Logger{ + logger: l, + lvl: c.Level, + } +} + +func getLogLevel(v *viper.Viper) (zapcore.Level, error) { + var lvl zapcore.Level + lvlStr := v.GetString(cfgLoggerLevel) + err := lvl.UnmarshalText([]byte(lvlStr)) + if err != nil { + return lvl, fmt.Errorf("incorrect logger level configuration %s (%v), "+ + "value should be one of %v", lvlStr, err, [...]zapcore.Level{ + zapcore.DebugLevel, + zapcore.InfoLevel, + zapcore.WarnLevel, + zapcore.ErrorLevel, + zapcore.DPanicLevel, + zapcore.PanicLevel, + zapcore.FatalLevel, + }) + } + return lvl, nil +} diff --git a/cmd/s3-gw/main.go b/cmd/s3-gw/main.go index 3407339d3..5a1957aeb 100644 --- a/cmd/s3-gw/main.go +++ b/cmd/s3-gw/main.go @@ -2,68 +2,19 @@ package main import ( "context" - "fmt" "os/signal" "syscall" - - "github.com/spf13/viper" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" ) -// newLogger constructs a zap.Logger instance for the current application. -// Panics on failure. -// -// Logger is built from zap's production logging configuration with: -// - parameterized level (debug by default) -// - console encoding -// - ISO8601 time encoding -// -// Logger records a stack trace for all messages at or above fatal level. -// -// See also zapcore.Level, zap.NewProductionConfig, zap.AddStacktrace. -func newLogger(v *viper.Viper) *zap.Logger { - var lvl zapcore.Level - lvlStr := v.GetString(cfgLoggerLevel) - - err := lvl.UnmarshalText([]byte(lvlStr)) - if err != nil { - panic(fmt.Sprintf("incorrect logger level configuration %s (%v), "+ - "value should be one of %v", lvlStr, err, [...]zapcore.Level{ - zapcore.DebugLevel, - zapcore.InfoLevel, - zapcore.WarnLevel, - zapcore.ErrorLevel, - zapcore.DPanicLevel, - zapcore.PanicLevel, - zapcore.FatalLevel, - })) - } - - c := zap.NewProductionConfig() - c.Level = zap.NewAtomicLevelAt(lvl) - c.Encoding = "console" - c.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder - - l, err := c.Build( - zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)), - ) - if err != nil { - panic(fmt.Sprintf("build zap logger instance: %v", err)) - } - - return l -} - func main() { - var ( - v = newSettings() - l = newLogger(v) - g, _ = signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) - a = newApp(g, l, v) - ) + g, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) - go a.Server(g) + v := newSettings() + l := newLogger(v) + + a := newApp(g, l, v) + + go a.Serve(g) a.Wait() }