Prechádzať zdrojové kódy

Allow to rotate logs on demand

Log file can be rotated sending a SIGUSR1 signal on Unix based systems and
using "sftpgo service rotatelogs" on Windows

Fixes #133
Nicola Murino 5 rokov pred
rodič
commit
0056984d4b

+ 9 - 6
.github/workflows/development.yml

@@ -16,7 +16,8 @@ jobs:
         with:
           version: v1.27
 
-  test-deploy-unix:
+  tests-upload-unix:
+    name: Run tests and upload build artifacts on Linux and macOS
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
@@ -40,7 +41,7 @@ jobs:
       - name: Initialize data provider
         run: ./sftpgo initprovider
 
-      - name: Run tests using SQLite provider
+      - name: Run test cases using SQLite as data provider
         run: go test -v ./... -coverprofile=coverage.txt -covermode=atomic
 
       - name: Upload to Codecov
@@ -63,7 +64,7 @@ jobs:
           name: sftpgo-${{ matrix.os }}-go${{ matrix.go }}
           path: output
 
-      - name: Run tests using bolt provider
+      - name: Run test cases using bolt as data provider
         if: ${{ matrix.os == 'ubuntu-latest' && matrix.go == '1.14' }}
         run: |
           rm -f sftpgo.db
@@ -73,14 +74,15 @@ jobs:
         env:
           SFTPGO_DATA_PROVIDER__DRIVER: bolt
 
-      - name: Run tests using memory provider
+      - name: Run test cases using the memory data provider
         if: ${{ matrix.os == 'ubuntu-latest' && matrix.go == '1.14' }}
         run: go test -v ./... -covermode=atomic
         env:
             SFTPGO_DATA_PROVIDER__DRIVER: memory
             SFTPGO_DATA_PROVIDER__NAME:
 
-  test-deploy-windows:
+  tests-upload-windows:
+    name: Run tests and upload build artifact on Windows
     runs-on: windows-latest
     steps:
       - uses: actions/checkout@v2
@@ -116,7 +118,8 @@ jobs:
           name: sftpgo-windows
           path: output
 
-  test-postgresql:
+  tests-postgresql:
+    name: Run test cases using PostgreSQL as data provider
     runs-on: ubuntu-latest
     env:
       SFTPGO_DATA_PROVIDER__DRIVER: postgresql

+ 2 - 0
README.md

@@ -62,6 +62,8 @@ Some Linux distro packages are available:
   - [sftpgo-bin](https://aur.archlinux.org/packages/sftpgo-bin/). This package follows stable releases downloading the prebuilt linux binary from GitHub. It does not require `git`, `gcc` and `go` to build.
   - [sftpgo-git](https://aur.archlinux.org/packages/sftpgo-git/). This package builds and installs the latest git master. It requires `git`, `gcc` and `go` to build.
 
+You can easily test new features selecting a commit from the [Actions](./actions) page and downloading the matching build artifacts for Linux, macOS or Windows. GitHub stores artifacts for 90 days.
+
 Alternately, you can [build from source](./docs/build-from-source.md).
 
 ## Configuration

+ 2 - 2
cmd/reload_windows.go

@@ -21,10 +21,10 @@ var (
 			}
 			err := s.Reload()
 			if err != nil {
-				fmt.Printf("Error reloading service: %v\r\n", err)
+				fmt.Printf("Error sending reload signal: %v\r\n", err)
 				os.Exit(1)
 			} else {
-				fmt.Printf("Service reloaded!\r\n")
+				fmt.Printf("Reload signal sent!\r\n")
 			}
 		},
 	}

+ 35 - 0
cmd/rotatelogs_windows.go

@@ -0,0 +1,35 @@
+package cmd
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/spf13/cobra"
+
+	"github.com/drakkan/sftpgo/service"
+)
+
+var (
+	rotateLogCmd = &cobra.Command{
+		Use:   "rotatelogs",
+		Short: "Signal to the running service to close the existing log file and immediately create a new one",
+		Run: func(cmd *cobra.Command, args []string) {
+			s := service.WindowsService{
+				Service: service.Service{
+					Shutdown: make(chan bool),
+				},
+			}
+			err := s.RotateLogFile()
+			if err != nil {
+				fmt.Printf("Error sending rotate log file signal to the service: %v\r\n", err)
+				os.Exit(1)
+			} else {
+				fmt.Printf("Rotate log file signal sent!\r\n")
+			}
+		},
+	}
+)
+
+func init() {
+	serviceCmd.AddCommand(rotateLogCmd)
+}

+ 1 - 1
docs/account.md

@@ -13,7 +13,7 @@ For each account, the following properties can be configured:
 - `max_sessions` maximum concurrent sessions. 0 means unlimited.
 - `quota_size` maximum size allowed as bytes. 0 means unlimited.
 - `quota_files` maximum number of files allowed. 0 means unlimited.
-- `permissions` the following per directory permissions are supported:
+- `permissions` for SFTP paths. The following per directory permissions are supported:
   - `*` all permissions are granted
   - `list` list items is allowed
   - `download` download files is allowed

+ 2 - 0
docs/full-configuration.md

@@ -32,6 +32,8 @@ The `serve` command supports the following flags:
 - `--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`).
 
+Log file can be rotated on demand sending a `SIGUSR1` signal on Unix based systems and using `sftpgo service rotatelogs` on Windows.
+
 If you don't configure any private host key, the daemon will use `id_rsa` and `id_ecdsa` in the configuration directory. If these files don't exist, the daemon will attempt to autogenerate them (if the user that executes SFTPGo has write access to the `config-dir`). The server supports any private key format supported by [`crypto/ssh`](https://github.com/golang/crypto/blob/master/ssh/keys.go#L33).
 
 ## Configuration file

+ 2 - 1
docs/service.md

@@ -86,7 +86,8 @@ Usage:
 
 Available Commands:
   install     Install SFTPGo as Windows Service
-  reload      Reload the SFTPGo Windows Service sending a `paramchange` request
+  reload      Reload the SFTPGo Windows Service sending a "paramchange" request
+  rotatelogs  Signal to the running service to close the existing log file and immediately create a new one
   start       Start SFTPGo Windows Service
   status      Retrieve the status for the SFTPGo Windows Service
   stop        Stop SFTPGo Windows Service

+ 1 - 1
init/sftpgo.service

@@ -1,5 +1,5 @@
 [Unit]
-Description=SFTPGo sftp server
+Description=SFTPGo SFTP Server
 After=network.target
 
 [Service]

+ 14 - 2
logger/logger.go

@@ -9,6 +9,7 @@
 package logger
 
 import (
+	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -37,6 +38,7 @@ const (
 var (
 	logger        zerolog.Logger
 	consoleLogger zerolog.Logger
+	rollingLogger *lumberjack.Logger
 )
 
 // GetLogger get the configured logger instance
@@ -48,13 +50,14 @@ func GetLogger() *zerolog.Logger {
 func InitLogger(logFilePath string, logMaxSize int, logMaxBackups int, logMaxAge int, logCompress bool, level zerolog.Level) {
 	zerolog.TimeFieldFormat = dateFormat
 	if isLogFilePathValid(logFilePath) {
-		logger = zerolog.New(&lumberjack.Logger{
+		rollingLogger = &lumberjack.Logger{
 			Filename:   logFilePath,
 			MaxSize:    logMaxSize,
 			MaxBackups: logMaxBackups,
 			MaxAge:     logMaxAge,
 			Compress:   logCompress,
-		})
+		}
+		logger = zerolog.New(rollingLogger)
 		EnableConsoleLogger(level)
 	} else {
 		logger = zerolog.New(logSyncWrapper{
@@ -69,6 +72,7 @@ func InitLogger(logFilePath string, logMaxSize int, logMaxBackups int, logMaxAge
 // ConsoleLogger will not be affected
 func DisableLogger() {
 	logger = zerolog.Nop()
+	rollingLogger = nil
 }
 
 // EnableConsoleLogger enables the console logger
@@ -81,6 +85,14 @@ func EnableConsoleLogger(level zerolog.Level) {
 	consoleLogger = zerolog.New(consoleOutput).With().Timestamp().Logger().Level(level)
 }
 
+// RotateLogFile closes the existing log file and immediately create a new one
+func RotateLogFile() error {
+	if rollingLogger != nil {
+		return rollingLogger.Rotate()
+	}
+	return errors.New("logging to file is disabled")
+}
+
 // Log logs at the specified level for the specified sender
 func Log(level LogLevel, sender string, connectionID string, format string, v ...interface{}) {
 	switch level {

+ 1 - 0
service/service.go

@@ -124,6 +124,7 @@ func (s *Service) Start() error {
 func (s *Service) Wait() {
 	if s.PortableMode != 1 {
 		registerSigHup()
+		registerSigUSR1()
 	}
 	<-s.Shutdown
 }

+ 29 - 3
service/service_windows.go

@@ -17,8 +17,10 @@ import (
 )
 
 const (
-	serviceName = "SFTPGo"
-	serviceDesc = "Full featured and highly configurable SFTP server"
+	serviceName     = "SFTPGo"
+	serviceDesc     = "Full featured and highly configurable SFTP server"
+	rotateLogCmd    = svc.Cmd(128)
+	acceptRotateLog = svc.Accepted(rotateLogCmd)
 )
 
 // Status defines service status
@@ -63,7 +65,7 @@ func (s Status) String() string {
 }
 
 func (s *WindowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
-	const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptParamChange
+	const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptParamChange | acceptRotateLog
 	changes <- svc.Status{State: svc.StartPending}
 	if err := s.Service.Start(); err != nil {
 		return true, 1
@@ -91,6 +93,12 @@ loop:
 			if err != nil {
 				logger.Warn(logSender, "", "error reloading TLS certificate: %v", err)
 			}
+		case rotateLogCmd:
+			logger.Debug(logSender, "", "Received log file rotation request")
+			err := logger.RotateLogFile()
+			if err != nil {
+				logger.Warn(logSender, "", "error rotating log file: %v", err)
+			}
 		default:
 			continue loop
 		}
@@ -157,6 +165,24 @@ func (s *WindowsService) Reload() error {
 	return nil
 }
 
+func (s *WindowsService) RotateLogFile() error {
+	m, err := mgr.Connect()
+	if err != nil {
+		return err
+	}
+	defer m.Disconnect()
+	service, err := m.OpenService(serviceName)
+	if err != nil {
+		return fmt.Errorf("could not access service: %v", err)
+	}
+	defer service.Close()
+	_, err = service.Control(rotateLogCmd)
+	if err != nil {
+		return fmt.Errorf("could not send control=%d: %v", rotateLogCmd, err)
+	}
+	return nil
+}
+
 func (s *WindowsService) Install(args ...string) error {
 	exePath, err := s.getExePath()
 	if err != nil {

+ 1 - 2
service/sighup_windows.go

@@ -1,4 +1,3 @@
 package service
 
-func registerSigHup() {
-}
+func registerSigHup() {}

+ 25 - 0
service/sigusr1_unix.go

@@ -0,0 +1,25 @@
+// +build !windows
+
+package service
+
+import (
+	"os"
+	"os/signal"
+	"syscall"
+
+	"github.com/drakkan/sftpgo/logger"
+)
+
+func registerSigUSR1() {
+	sig := make(chan os.Signal, 1)
+	signal.Notify(sig, syscall.SIGUSR1)
+	go func() {
+		for range sig {
+			logger.Debug(logSender, "", "Received log file rotation request")
+			err := logger.RotateLogFile()
+			if err != nil {
+				logger.Warn(logSender, "", "error rotating log file: %v", err)
+			}
+		}
+	}()
+}

+ 3 - 0
service/sigusr1_windows.go

@@ -0,0 +1,3 @@
+package service
+
+func registerSigUSR1() {}