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:
|
with:
|
||||||
version: v1.27
|
version: v1.27
|
||||||
|
|
||||||
test-deploy-unix:
|
tests-upload-unix:
|
||||||
|
name: Run tests and upload build artifacts on Linux and macOS
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -40,7 +41,7 @@ jobs:
|
||||||
- name: Initialize data provider
|
- name: Initialize data provider
|
||||||
run: ./sftpgo initprovider
|
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
|
run: go test -v ./... -coverprofile=coverage.txt -covermode=atomic
|
||||||
|
|
||||||
- name: Upload to Codecov
|
- name: Upload to Codecov
|
||||||
|
@ -63,7 +64,7 @@ jobs:
|
||||||
name: sftpgo-${{ matrix.os }}-go${{ matrix.go }}
|
name: sftpgo-${{ matrix.os }}-go${{ matrix.go }}
|
||||||
path: output
|
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' }}
|
if: ${{ matrix.os == 'ubuntu-latest' && matrix.go == '1.14' }}
|
||||||
run: |
|
run: |
|
||||||
rm -f sftpgo.db
|
rm -f sftpgo.db
|
||||||
|
@ -73,14 +74,15 @@ jobs:
|
||||||
env:
|
env:
|
||||||
SFTPGO_DATA_PROVIDER__DRIVER: bolt
|
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' }}
|
if: ${{ matrix.os == 'ubuntu-latest' && matrix.go == '1.14' }}
|
||||||
run: go test -v ./... -covermode=atomic
|
run: go test -v ./... -covermode=atomic
|
||||||
env:
|
env:
|
||||||
SFTPGO_DATA_PROVIDER__DRIVER: memory
|
SFTPGO_DATA_PROVIDER__DRIVER: memory
|
||||||
SFTPGO_DATA_PROVIDER__NAME:
|
SFTPGO_DATA_PROVIDER__NAME:
|
||||||
|
|
||||||
test-deploy-windows:
|
tests-upload-windows:
|
||||||
|
name: Run tests and upload build artifact on Windows
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
@ -116,7 +118,8 @@ jobs:
|
||||||
name: sftpgo-windows
|
name: sftpgo-windows
|
||||||
path: output
|
path: output
|
||||||
|
|
||||||
test-postgresql:
|
tests-postgresql:
|
||||||
|
name: Run test cases using PostgreSQL as data provider
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
SFTPGO_DATA_PROVIDER__DRIVER: postgresql
|
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-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.
|
- [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).
|
Alternately, you can [build from source](./docs/build-from-source.md).
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
|
@ -21,10 +21,10 @@ var (
|
||||||
}
|
}
|
||||||
err := s.Reload()
|
err := s.Reload()
|
||||||
if err != nil {
|
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)
|
os.Exit(1)
|
||||||
} else {
|
} 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.
|
- `max_sessions` maximum concurrent sessions. 0 means unlimited.
|
||||||
- `quota_size` maximum size allowed as bytes. 0 means unlimited.
|
- `quota_size` maximum size allowed as bytes. 0 means unlimited.
|
||||||
- `quota_files` maximum number of files allowed. 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
|
- `*` all permissions are granted
|
||||||
- `list` list items is allowed
|
- `list` list items is allowed
|
||||||
- `download` download files 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-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-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).
|
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
|
## Configuration file
|
||||||
|
|
|
@ -86,7 +86,8 @@ Usage:
|
||||||
|
|
||||||
Available Commands:
|
Available Commands:
|
||||||
install Install SFTPGo as Windows Service
|
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
|
start Start SFTPGo Windows Service
|
||||||
status Retrieve the status for the SFTPGo Windows Service
|
status Retrieve the status for the SFTPGo Windows Service
|
||||||
stop Stop SFTPGo Windows Service
|
stop Stop SFTPGo Windows Service
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=SFTPGo sftp server
|
Description=SFTPGo SFTP Server
|
||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -37,6 +38,7 @@ const (
|
||||||
var (
|
var (
|
||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
consoleLogger zerolog.Logger
|
consoleLogger zerolog.Logger
|
||||||
|
rollingLogger *lumberjack.Logger
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetLogger get the configured logger instance
|
// 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) {
|
func InitLogger(logFilePath string, logMaxSize int, logMaxBackups int, logMaxAge int, logCompress bool, level zerolog.Level) {
|
||||||
zerolog.TimeFieldFormat = dateFormat
|
zerolog.TimeFieldFormat = dateFormat
|
||||||
if isLogFilePathValid(logFilePath) {
|
if isLogFilePathValid(logFilePath) {
|
||||||
logger = zerolog.New(&lumberjack.Logger{
|
rollingLogger = &lumberjack.Logger{
|
||||||
Filename: logFilePath,
|
Filename: logFilePath,
|
||||||
MaxSize: logMaxSize,
|
MaxSize: logMaxSize,
|
||||||
MaxBackups: logMaxBackups,
|
MaxBackups: logMaxBackups,
|
||||||
MaxAge: logMaxAge,
|
MaxAge: logMaxAge,
|
||||||
Compress: logCompress,
|
Compress: logCompress,
|
||||||
})
|
}
|
||||||
|
logger = zerolog.New(rollingLogger)
|
||||||
EnableConsoleLogger(level)
|
EnableConsoleLogger(level)
|
||||||
} else {
|
} else {
|
||||||
logger = zerolog.New(logSyncWrapper{
|
logger = zerolog.New(logSyncWrapper{
|
||||||
|
@ -69,6 +72,7 @@ func InitLogger(logFilePath string, logMaxSize int, logMaxBackups int, logMaxAge
|
||||||
// ConsoleLogger will not be affected
|
// ConsoleLogger will not be affected
|
||||||
func DisableLogger() {
|
func DisableLogger() {
|
||||||
logger = zerolog.Nop()
|
logger = zerolog.Nop()
|
||||||
|
rollingLogger = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableConsoleLogger enables the console logger
|
// EnableConsoleLogger enables the console logger
|
||||||
|
@ -81,6 +85,14 @@ func EnableConsoleLogger(level zerolog.Level) {
|
||||||
consoleLogger = zerolog.New(consoleOutput).With().Timestamp().Logger().Level(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
|
// Log logs at the specified level for the specified sender
|
||||||
func Log(level LogLevel, sender string, connectionID string, format string, v ...interface{}) {
|
func Log(level LogLevel, sender string, connectionID string, format string, v ...interface{}) {
|
||||||
switch level {
|
switch level {
|
||||||
|
|
|
@ -124,6 +124,7 @@ func (s *Service) Start() error {
|
||||||
func (s *Service) Wait() {
|
func (s *Service) Wait() {
|
||||||
if s.PortableMode != 1 {
|
if s.PortableMode != 1 {
|
||||||
registerSigHup()
|
registerSigHup()
|
||||||
|
registerSigUSR1()
|
||||||
}
|
}
|
||||||
<-s.Shutdown
|
<-s.Shutdown
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ import (
|
||||||
const (
|
const (
|
||||||
serviceName = "SFTPGo"
|
serviceName = "SFTPGo"
|
||||||
serviceDesc = "Full featured and highly configurable SFTP server"
|
serviceDesc = "Full featured and highly configurable SFTP server"
|
||||||
|
rotateLogCmd = svc.Cmd(128)
|
||||||
|
acceptRotateLog = svc.Accepted(rotateLogCmd)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Status defines service status
|
// 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) {
|
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}
|
changes <- svc.Status{State: svc.StartPending}
|
||||||
if err := s.Service.Start(); err != nil {
|
if err := s.Service.Start(); err != nil {
|
||||||
return true, 1
|
return true, 1
|
||||||
|
@ -91,6 +93,12 @@ loop:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn(logSender, "", "error reloading TLS certificate: %v", err)
|
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:
|
default:
|
||||||
continue loop
|
continue loop
|
||||||
}
|
}
|
||||||
|
@ -157,6 +165,24 @@ func (s *WindowsService) Reload() error {
|
||||||
return nil
|
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 {
|
func (s *WindowsService) Install(args ...string) error {
|
||||||
exePath, err := s.getExePath()
|
exePath, err := s.getExePath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
package service
|
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