diff --git a/cmd/root.go b/cmd/root.go index 63dac3ce..ceb7fe61 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -28,6 +28,8 @@ const ( logCompressKey = "log_compress" logVerboseFlag = "log-verbose" logVerboseKey = "log_verbose" + logUTCTimeFlag = "log-utc-time" + logUTCTimeKey = "log_utc_time" loadDataFromFlag = "loaddata-from" loadDataFromKey = "loaddata_from" loadDataModeFlag = "loaddata-mode" @@ -44,6 +46,7 @@ const ( defaultLogMaxAge = 28 defaultLogCompress = false defaultLogVerbose = true + defaultLogUTCTime = false defaultLoadDataFrom = "" defaultLoadDataMode = 1 defaultLoadDataQuotaScan = 0 @@ -59,6 +62,7 @@ var ( logMaxAge int logCompress bool logVerbose bool + logUTCTime bool loadDataFrom string loadDataMode int loadDataQuotaScan int @@ -180,6 +184,14 @@ using SFTPGO_LOG_VERBOSE env var too. `) viper.BindPFlag(logVerboseKey, cmd.Flags().Lookup(logVerboseFlag)) //nolint:errcheck + viper.SetDefault(logUTCTimeKey, defaultLogUTCTime) + viper.BindEnv(logUTCTimeKey, "SFTPGO_LOG_UTC_TIME") //nolint:errcheck + cmd.Flags().BoolVar(&logUTCTime, logUTCTimeFlag, viper.GetBool(logUTCTimeKey), + `Use UTC time for logging. This flag can be set +using SFTPGO_LOG_UTC_TIME env var too. +`) + viper.BindPFlag(logUTCTimeKey, cmd.Flags().Lookup(logUTCTimeFlag)) //nolint:errcheck + viper.SetDefault(loadDataFromKey, defaultLoadDataFrom) viper.BindEnv(loadDataFromKey, "SFTPGO_LOADDATA_FROM") //nolint:errcheck cmd.Flags().StringVar(&loadDataFrom, loadDataFromFlag, viper.GetString(loadDataFromKey), diff --git a/cmd/serve.go b/cmd/serve.go index ae9de414..b6e4517f 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -29,6 +29,7 @@ Please take a look at the usage below to customize the startup options`, LogMaxAge: logMaxAge, LogCompress: logCompress, LogVerbose: logVerbose, + LogUTCTime: logUTCTime, LoadDataFrom: loadDataFrom, LoadDataMode: loadDataMode, LoadDataQuotaScan: loadDataQuotaScan, diff --git a/cmd/startsubsys.go b/cmd/startsubsys.go index 8f288ea9..28199d84 100644 --- a/cmd/startsubsys.go +++ b/cmd/startsubsys.go @@ -44,6 +44,7 @@ Command-line flags should be specified in the Subsystem declaration. if !logVerbose { logLevel = zerolog.InfoLevel } + logger.SetLogTime(logUTCTime) if logJournalD { logger.InitJournalDLogger(logLevel) } else { @@ -180,5 +181,13 @@ using SFTPGO_LOG_VERBOSE env var too. `) viper.BindPFlag(logVerboseKey, subsystemCmd.Flags().Lookup(logVerboseFlag)) //nolint:errcheck + viper.SetDefault(logUTCTimeKey, defaultLogUTCTime) + viper.BindEnv(logUTCTimeKey, "SFTPGO_LOG_UTC_TIME") //nolint:errcheck + subsystemCmd.Flags().BoolVar(&logUTCTime, logUTCTimeFlag, viper.GetBool(logUTCTimeKey), + `Use UTC time for logging. This flag can be set +using SFTPGO_LOG_UTC_TIME env var too. +`) + viper.BindPFlag(logUTCTimeKey, subsystemCmd.Flags().Lookup(logUTCTimeFlag)) //nolint:errcheck + rootCmd.AddCommand(subsystemCmd) } diff --git a/common/protocol_test.go b/common/protocol_test.go index 3a8ce9f8..6ce07c26 100644 --- a/common/protocol_test.go +++ b/common/protocol_test.go @@ -63,7 +63,7 @@ var ( func TestMain(m *testing.M) { homeBasePath = os.TempDir() logFilePath := filepath.Join(configDir, "common_test.log") - logger.InitLogger(logFilePath, 5, 1, 28, false, zerolog.DebugLevel) + logger.InitLogger(logFilePath, 5, 1, 28, false, false, zerolog.DebugLevel) os.Setenv("SFTPGO_DATA_PROVIDER__CREATE_DEFAULT_ADMIN", "1") os.Setenv("SFTPGO_DEFAULT_ADMIN_USERNAME", "admin") diff --git a/dataprovider/bolt.go b/dataprovider/bolt.go index 4191c42b..31f1cd17 100644 --- a/dataprovider/bolt.go +++ b/dataprovider/bolt.go @@ -1185,15 +1185,18 @@ func (p *BoltProvider) updateShare(share *Share) error { if err != nil { return err } - var a []byte + var s []byte - if a = bucket.Get([]byte(share.ShareID)); a == nil { + if s = bucket.Get([]byte(share.ShareID)); s == nil { return util.NewRecordNotFoundError(fmt.Sprintf("Share %v does not exist", share.ShareID)) } var oldObject Share - if err = json.Unmarshal(a, &oldObject); err != nil { + if err = json.Unmarshal(s, &oldObject); err != nil { return err } + if oldObject.Username != share.Username { + return util.NewRecordNotFoundError(fmt.Sprintf("Share %v does not exist", share.ShareID)) + } share.ID = oldObject.ID share.ShareID = oldObject.ShareID @@ -1219,7 +1222,16 @@ func (p *BoltProvider) deleteShare(share *Share) error { return err } - if bucket.Get([]byte(share.ShareID)) == nil { + var s []byte + + if s = bucket.Get([]byte(share.ShareID)); s == nil { + return util.NewRecordNotFoundError(fmt.Sprintf("Share %v does not exist", share.ShareID)) + } + var oldObject Share + if err = json.Unmarshal(s, &oldObject); err != nil { + return err + } + if oldObject.Username != share.Username { return util.NewRecordNotFoundError(fmt.Sprintf("Share %v does not exist", share.ShareID)) } diff --git a/docs/full-configuration.md b/docs/full-configuration.md index 913bcff5..1fba6d55 100644 --- a/docs/full-configuration.md +++ b/docs/full-configuration.md @@ -39,7 +39,7 @@ The `serve` command supports the following flags: - `--log-max-backups` int. Maximum number of old log files to retain. Default 5 or the value of `SFTPGO_LOG_MAX_BACKUPS` environment variable. It is unused if `log-file-path` is empty. - `--log-max-size` int. Maximum size in megabytes of the log file before it gets rotated. Default 10 or the value of `SFTPGO_LOG_MAX_SIZE` environment variable. It is unused if `log-file-path` is empty. - `--log-verbose` boolean. Enable verbose logs. Default `true` or the value of `SFTPGO_LOG_VERBOSE` environment variable (1 or `true`, 0 or `false`). -- `--profiler` boolean. Enable the built-in profiler. The profiler will be accessible via HTTP/HTTPS using the base URL "/debug/pprof/". Default `false` or the value of `SFTPGO_PROFILER` environment variable (1 or `true`, 0 or `false`). +- `--log-utc-time` boolean. Enable UTC time for logging. Default `false` or the value of `SFTPGO_LOG_UTC_TIME` environment variable (1 or `true`, 0 or `false`) Log file can be rotated on demand sending a `SIGUSR1` signal on Unix based systems and using the command `sftpgo service rotatelogs` on Windows. diff --git a/docs/sftp-subsystem.md b/docs/sftp-subsystem.md index 641a143c..465a4e58 100644 --- a/docs/sftp-subsystem.md +++ b/docs/sftp-subsystem.md @@ -20,27 +20,32 @@ Flags: starting directory. The home directory for a new user will be: - / + [base-home-dir]/[username] base-home-dir must be an absolute path. - -c, --config-dir string Location for SFTPGo config dir. This directory - should contain the "sftpgo" configuration file - or the configured config-file and it is used as - the base for files with a relative path (eg. the - private keys for the SFTP server, the SQLite - database if you use SQLite as data provider). + -c, --config-dir string Location for the config dir. This directory + is used as the base for files with a relative + path, eg. the private keys for the SFTP + server or the SQLite database if you use + SQLite as data provider. + The configuration file, if not explicitly set, + is looked for in this dir. We support reading + from JSON, TOML, YAML, HCL, envfile and Java + properties config files. The default config + file name is "sftpgo" and therefore + "sftpgo.json", "sftpgo.yaml" and so on are + searched. This flag can be set using SFTPGO_CONFIG_DIR env var too. (default ".") - -f, --config-file string Name for SFTPGo configuration file. It must be - the name of a file stored in config-dir not the - absolute path to the configuration file. The - specified file name must have no extension we - automatically load JSON, YAML, TOML, HCL and - Java properties. Therefore if you set "sftpgo" - then "sftpgo.json", "sftpgo.yaml" and so on - are searched. + --config-file string Path to SFTPGo configuration file. + This flag explicitly defines the path, name + and extension of the config file. If must be + an absolute path or a path relative to the + configuration directory. The specified file + name must have a supported extension (JSON, + YAML, TOML, HCL or Java properties). This flag can be set using SFTPGO_CONFIG_FILE - env var too. (default "sftpgo") + env var too. -h, --help help for startsubsys -j, --log-to-journald Send logs to journald. Only available on Linux. Use: @@ -50,6 +55,9 @@ Flags: To see full logs. If not set, the logs will be sent to the standard error + --log-utc-time Use UTC time for logging. This flag can be set + using SFTPGO_LOG_UTC_TIME env var too. + (default true) -v, --log-verbose Enable verbose logs. This flag can be set using SFTPGO_LOG_VERBOSE env var too. (default true) diff --git a/ftpd/ftpd_test.go b/ftpd/ftpd_test.go index 6bf1614b..61d65fa2 100644 --- a/ftpd/ftpd_test.go +++ b/ftpd/ftpd_test.go @@ -247,7 +247,7 @@ func TestMain(m *testing.M) { logFilePath = filepath.Join(configDir, "sftpgo_ftpd_test.log") bannerFileName := "banner_file" bannerFile := filepath.Join(configDir, bannerFileName) - logger.InitLogger(logFilePath, 5, 1, 28, false, zerolog.DebugLevel) + logger.InitLogger(logFilePath, 5, 1, 28, false, false, zerolog.DebugLevel) err := os.WriteFile(bannerFile, []byte("SFTPGo test ready\nsimple banner line\n"), os.ModePerm) if err != nil { logger.ErrorToConsole("error creating banner file: %v", err) diff --git a/httpd/httpd_test.go b/httpd/httpd_test.go index 6c2cb01d..189975c5 100644 --- a/httpd/httpd_test.go +++ b/httpd/httpd_test.go @@ -245,7 +245,7 @@ type recoveryCode struct { func TestMain(m *testing.M) { homeBasePath = os.TempDir() logfilePath := filepath.Join(configDir, "sftpgo_api_test.log") - logger.InitLogger(logfilePath, 5, 1, 28, false, zerolog.DebugLevel) + logger.InitLogger(logfilePath, 5, 1, 28, false, false, zerolog.DebugLevel) os.Setenv("SFTPGO_COMMON__UPLOAD_MODE", "2") os.Setenv("SFTPGO_DATA_PROVIDER__CREATE_DEFAULT_ADMIN", "1") os.Setenv("SFTPGO_DEFAULT_ADMIN_USERNAME", "admin") diff --git a/logger/logger.go b/logger/logger.go index 50631479..d831e350 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -14,6 +14,7 @@ import ( "os" "path/filepath" "runtime" + "time" ftpserverlog "github.com/fclairamb/go-log" "github.com/rs/zerolog" @@ -136,9 +137,21 @@ func GetLogger() *zerolog.Logger { return &logger } -// InitLogger configures the logger using the given parameters -func InitLogger(logFilePath string, logMaxSize int, logMaxBackups int, logMaxAge int, logCompress bool, level zerolog.Level) { +// SetLogTime sets logging time related setting +func SetLogTime(utc bool) { zerolog.TimeFieldFormat = dateFormat + if utc { + zerolog.TimestampFunc = time.Now().UTC + } else { + zerolog.TimestampFunc = time.Now + } +} + +// InitLogger configures the logger using the given parameters +func InitLogger(logFilePath string, logMaxSize int, logMaxBackups int, logMaxAge int, logCompress, logUTCTime bool, + level zerolog.Level, +) { + SetLogTime(logUTCTime) if isLogFilePathValid(logFilePath) { logDir := filepath.Dir(logFilePath) if _, err := os.Stat(logDir); os.IsNotExist(err) { @@ -153,6 +166,7 @@ func InitLogger(logFilePath string, logMaxSize int, logMaxBackups int, logMaxAge MaxBackups: logMaxBackups, MaxAge: logMaxAge, Compress: logCompress, + LocalTime: !logUTCTime, } logger = zerolog.New(rollingLogger) EnableConsoleLogger(level) diff --git a/service/service.go b/service/service.go index 2936813b..ba1bfa00 100644 --- a/service/service.go +++ b/service/service.go @@ -39,6 +39,7 @@ type Service struct { PortableUser dataprovider.User LogCompress bool LogVerbose bool + LogUTCTime bool LoadDataClean bool LoadDataFrom string LoadDataMode int @@ -55,7 +56,7 @@ func (s *Service) initLogger() { if !filepath.IsAbs(s.LogFilePath) && util.IsFileInputValid(s.LogFilePath) { s.LogFilePath = filepath.Join(s.ConfigDir, s.LogFilePath) } - logger.InitLogger(s.LogFilePath, s.LogMaxSize, s.LogMaxBackups, s.LogMaxAge, s.LogCompress, logLevel) + logger.InitLogger(s.LogFilePath, s.LogMaxSize, s.LogMaxBackups, s.LogMaxAge, s.LogCompress, s.LogUTCTime, logLevel) if s.PortableMode == 1 { logger.EnableConsoleLogger(logLevel) if s.LogFilePath == "" { diff --git a/sftpd/sftpd_test.go b/sftpd/sftpd_test.go index 3583a95e..579bc77e 100644 --- a/sftpd/sftpd_test.go +++ b/sftpd/sftpd_test.go @@ -144,7 +144,7 @@ func TestMain(m *testing.M) { logFilePath = filepath.Join(configDir, "sftpgo_sftpd_test.log") loginBannerFileName := "login_banner" loginBannerFile := filepath.Join(configDir, loginBannerFileName) - logger.InitLogger(logFilePath, 5, 1, 28, false, zerolog.DebugLevel) + logger.InitLogger(logFilePath, 5, 1, 28, false, false, zerolog.DebugLevel) err := os.WriteFile(loginBannerFile, []byte("simple login banner\n"), os.ModePerm) if err != nil { logger.ErrorToConsole("error creating login banner: %v", err) diff --git a/webdavd/webdavd_test.go b/webdavd/webdavd_test.go index 33663759..b8d5fa1b 100644 --- a/webdavd/webdavd_test.go +++ b/webdavd/webdavd_test.go @@ -248,7 +248,7 @@ var ( func TestMain(m *testing.M) { logFilePath = filepath.Join(configDir, "sftpgo_webdavd_test.log") - logger.InitLogger(logFilePath, 5, 1, 28, false, zerolog.DebugLevel) + logger.InitLogger(logFilePath, 5, 1, 28, false, false, zerolog.DebugLevel) os.Setenv("SFTPGO_DATA_PROVIDER__CREATE_DEFAULT_ADMIN", "1") os.Setenv("SFTPGO_DEFAULT_ADMIN_USERNAME", "admin") os.Setenv("SFTPGO_DEFAULT_ADMIN_PASSWORD", "password")