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:
parent
44fb276464
commit
0056984d4b
14 changed files with 127 additions and 18 deletions
15
.github/workflows/development.yml
vendored
15
.github/workflows/development.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
35
cmd/rotatelogs_windows.go
Normal 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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,5 +1,5 @@
|
|||
[Unit]
|
||||
Description=SFTPGo sftp server
|
||||
Description=SFTPGo SFTP Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -124,6 +124,7 @@ func (s *Service) Start() error {
|
|||
func (s *Service) Wait() {
|
||||
if s.PortableMode != 1 {
|
||||
registerSigHup()
|
||||
registerSigUSR1()
|
||||
}
|
||||
<-s.Shutdown
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
package service
|
||||
|
||||
func registerSigHup() {
|
||||
}
|
||||
func registerSigHup() {}
|
||||
|
|
25
service/sigusr1_unix.go
Normal file
25
service/sigusr1_unix.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
3
service/sigusr1_windows.go
Normal file
3
service/sigusr1_windows.go
Normal file
|
@ -0,0 +1,3 @@
|
|||
package service
|
||||
|
||||
func registerSigUSR1() {}
|
Loading…
Reference in a new issue