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
This commit is contained in:
Nicola Murino 2020-06-22 19:11:53 +02:00
parent 44fb276464
commit 0056984d4b
14 changed files with 127 additions and 18 deletions

View file

@ -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

View file

@ -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

View file

@ -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
cmd/rotatelogs_windows.go Normal file
View file

@ -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)
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

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

View file

@ -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 {

View file

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

View file

@ -19,6 +19,8 @@ import (
const (
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 {

View file

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

25
service/sigusr1_unix.go Normal file
View file

@ -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)
}
}
}()
}

View file

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