fs events: add elapsed field to notifications

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2023-02-12 18:56:53 +01:00
parent 7e85356325
commit 157d1db0b1
No known key found for this signature in database
GPG key ID: 935D2952DEC4EECF
14 changed files with 94 additions and 53 deletions

View file

@ -40,6 +40,7 @@ If the `hook` defines a path to an external program, then this program can read
- `SFTPGO_ACTION_VIRTUAL_TARGET`, virtual target path, seen by SFTPGo users - `SFTPGO_ACTION_VIRTUAL_TARGET`, virtual target path, seen by SFTPGo users
- `SFTPGO_ACTION_SSH_CMD`, non-empty for `ssh_cmd` `SFTPGO_ACTION` - `SFTPGO_ACTION_SSH_CMD`, non-empty for `ssh_cmd` `SFTPGO_ACTION`
- `SFTPGO_ACTION_FILE_SIZE`, non-zero for `pre-upload`, `upload`, `download`, `delete`, and `copy` actions if the file size is greater than `0` - `SFTPGO_ACTION_FILE_SIZE`, non-zero for `pre-upload`, `upload`, `download`, `delete`, and `copy` actions if the file size is greater than `0`
- `SFTPGO_ACTION_ELAPSED`, elapsed time as milliseconds
- `SFTPGO_ACTION_FS_PROVIDER`, `0` for local filesystem, `1` for S3 backend, `2` for Google Cloud Storage (GCS) backend, `3` for Azure Blob Storage backend, `4` for local encrypted backend, `5` for SFTP backend - `SFTPGO_ACTION_FS_PROVIDER`, `0` for local filesystem, `1` for S3 backend, `2` for Google Cloud Storage (GCS) backend, `3` for Azure Blob Storage backend, `4` for local encrypted backend, `5` for SFTP backend
- `SFTPGO_ACTION_BUCKET`, non-empty for S3, GCS and Azure backends - `SFTPGO_ACTION_BUCKET`, non-empty for S3, GCS and Azure backends
- `SFTPGO_ACTION_ENDPOINT`, non-empty for S3, SFTP and Azure backend if configured - `SFTPGO_ACTION_ENDPOINT`, non-empty for S3, SFTP and Azure backend if configured
@ -64,6 +65,7 @@ If the `hook` defines an HTTP URL then this URL will be invoked as HTTP POST. Th
- `virtual_target_path`, string, virtual target path, seen by SFTPGo users - `virtual_target_path`, string, virtual target path, seen by SFTPGo users
- `ssh_cmd`, string, included for `ssh_cmd` action - `ssh_cmd`, string, included for `ssh_cmd` action
- `file_size`, int64, included for `pre-upload`, `upload`, `download`, `delete` and `copy` actions if the file size is greater than `0` - `file_size`, int64, included for `pre-upload`, `upload`, `download`, `delete` and `copy` actions if the file size is greater than `0`
- `elapsed`, int64, elapsed size as milliseconds
- `fs_provider`, integer, `0` for local filesystem, `1` for S3 backend, `2` for Google Cloud Storage (GCS) backend, `3` for Azure Blob Storage backend, `4` for local encrypted backend, `5` for SFTP backend, `6` for HTTPFs backend - `fs_provider`, integer, `0` for local filesystem, `1` for S3 backend, `2` for Google Cloud Storage (GCS) backend, `3` for Azure Blob Storage backend, `4` for local encrypted backend, `5` for SFTP backend, `6` for HTTPFs backend
- `bucket`, string, included for S3, GCS and Azure backends - `bucket`, string, included for S3, GCS and Azure backends
- `endpoint`, string, included for S3, SFTP and Azure backend if configured - `endpoint`, string, included for S3, SFTP and Azure backend if configured

View file

@ -39,6 +39,7 @@ The following placeholders are supported:
- `{{TargetName}}`. Target object name for renames. - `{{TargetName}}`. Target object name for renames.
- `{{FsTargetPath}}`. Full filesystem target path for renames. - `{{FsTargetPath}}`. Full filesystem target path for renames.
- `{{FileSize}}`. File size. - `{{FileSize}}`. File size.
- `{{Elapsed}}`. Elapsed time as milliseconds for filesystem events.
- `{{Protocol}}`. Used protocol, for example `SFTP`, `FTP`. - `{{Protocol}}`. Used protocol, for example `SFTP`, `FTP`.
- `{{IP}}`. Client IP address. - `{{IP}}`. Client IP address.
- `{{Role}}`. User or admin role. - `{{Role}}`. User or admin role.

View file

@ -36,6 +36,7 @@ The logs can be divided into the following categories:
- `access_time` datetime as YYYY-MM-DDTHH:MM:SS. Valid for sender `Chtimes` otherwise empty - `access_time` datetime as YYYY-MM-DDTHH:MM:SS. Valid for sender `Chtimes` otherwise empty
- `modification_time` datetime as YYYY-MM-DDTHH:MM:SS. Valid for sender `Chtimes` otherwise empty - `modification_time` datetime as YYYY-MM-DDTHH:MM:SS. Valid for sender `Chtimes` otherwise empty
- `size` int64. Valid for sender `Truncate` otherwise -1 - `size` int64. Valid for sender `Truncate` otherwise -1
- `elapsed`, int64. Elapsed time, as milliseconds
- `ssh_command`, string. Valid for sender `SSHCommand` otherwise empty - `ssh_command`, string. Valid for sender `SSHCommand` otherwise empty
- `connection_id` string. Unique connection identifier - `connection_id` string. Unique connection identifier
- `protocol` string. `SFTP`, `SCP`, `SSH`, `FTP`, `HTTP`, `DAV`, `DataRetention`, `EventAction` - `protocol` string. `SFTP`, `SCP`, `SSH`, `FTP`, `HTTP`, `DAV`, `DataRetention`, `EventAction`

2
go.mod
View file

@ -52,7 +52,7 @@ require (
github.com/rs/cors v1.8.3 github.com/rs/cors v1.8.3
github.com/rs/xid v1.4.0 github.com/rs/xid v1.4.0
github.com/rs/zerolog v1.29.0 github.com/rs/zerolog v1.29.0
github.com/sftpgo/sdk v0.1.3-0.20221217110036-383c1bb50fa0 github.com/sftpgo/sdk v0.1.3-0.20230212154322-556375985d8c
github.com/shirou/gopsutil/v3 v3.23.1 github.com/shirou/gopsutil/v3 v3.23.1
github.com/spf13/afero v1.9.3 github.com/spf13/afero v1.9.3
github.com/spf13/cobra v1.6.1 github.com/spf13/cobra v1.6.1

2
go.sum
View file

@ -1802,6 +1802,8 @@ github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo= github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo=
github.com/sftpgo/sdk v0.1.3-0.20221217110036-383c1bb50fa0 h1:e1OQroqX8SWV06Z270CxG2/v//Wx1026iXKTDRn5J1E= github.com/sftpgo/sdk v0.1.3-0.20221217110036-383c1bb50fa0 h1:e1OQroqX8SWV06Z270CxG2/v//Wx1026iXKTDRn5J1E=
github.com/sftpgo/sdk v0.1.3-0.20221217110036-383c1bb50fa0/go.mod h1:3GpW3Qy8IHH6kex0ny+Y6ayeYb9OJxz8Pxh3IZgAs2E= github.com/sftpgo/sdk v0.1.3-0.20221217110036-383c1bb50fa0/go.mod h1:3GpW3Qy8IHH6kex0ny+Y6ayeYb9OJxz8Pxh3IZgAs2E=
github.com/sftpgo/sdk v0.1.3-0.20230212154322-556375985d8c h1:SiWQZe99SZ/O4QSIsxzL91NgwFJNoo4IJ31cazUrYh4=
github.com/sftpgo/sdk v0.1.3-0.20230212154322-556375985d8c/go.mod h1:B1lPGb05WtvvrX5IuhHrSjWdRT867qBaoxlS2Q9+1bA=
github.com/shirou/gopsutil/v3 v3.23.1 h1:a9KKO+kGLKEvcPIs4W62v0nu3sciVDOOOPUD0Hz7z/4= github.com/shirou/gopsutil/v3 v3.23.1 h1:a9KKO+kGLKEvcPIs4W62v0nu3sciVDOOOPUD0Hz7z/4=
github.com/shirou/gopsutil/v3 v3.23.1/go.mod h1:NN6mnm5/0k8jw4cBfCnJtr5L7ErOTg18tMNpgFkn0hA= github.com/shirou/gopsutil/v3 v3.23.1/go.mod h1:NN6mnm5/0k8jw4cBfCnJtr5L7ErOTg18tMNpgFkn0hA=
github.com/shoenig/test v0.4.3/go.mod h1:xYtyGBC5Q3kzCNyJg/SjgNpfAa2kvmgA0i5+lQso8x0= github.com/shoenig/test v0.4.3/go.mod h1:xYtyGBC5Q3kzCNyJg/SjgNpfAa2kvmgA0i5+lQso8x0=

View file

@ -92,7 +92,7 @@ func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath str
return 0, nil return 0, nil
} }
event = newActionNotification(&conn.User, operation, filePath, virtualPath, "", "", "", event = newActionNotification(&conn.User, operation, filePath, virtualPath, "", "", "",
conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, openFlags, conn.getNotificationStatus(nil)) conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, openFlags, conn.getNotificationStatus(nil), 0)
if hasNotifiersPlugin { if hasNotifiersPlugin {
plugin.Handler.NotifyFsEvent(event) plugin.Handler.NotifyFsEvent(event)
} }
@ -127,7 +127,7 @@ func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath str
// ExecuteActionNotification executes the defined hook, if any, for the specified action // ExecuteActionNotification executes the defined hook, if any, for the specified action
func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtualPath, target, virtualTarget, sshCmd string, func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtualPath, target, virtualTarget, sshCmd string,
fileSize int64, err error, fileSize int64, err error, elapsed int64,
) error { ) error {
hasNotifiersPlugin := plugin.Handler.HasNotifiers() hasNotifiersPlugin := plugin.Handler.HasNotifiers()
hasHook := util.Contains(Config.Actions.ExecuteOn, operation) hasHook := util.Contains(Config.Actions.ExecuteOn, operation)
@ -136,7 +136,7 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
return nil return nil
} }
notification := newActionNotification(&conn.User, operation, filePath, virtualPath, target, virtualTarget, sshCmd, notification := newActionNotification(&conn.User, operation, filePath, virtualPath, target, virtualTarget, sshCmd,
conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, 0, conn.getNotificationStatus(err)) conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, 0, conn.getNotificationStatus(err), elapsed)
if hasNotifiersPlugin { if hasNotifiersPlugin {
plugin.Handler.NotifyFsEvent(notification) plugin.Handler.NotifyFsEvent(notification)
} }
@ -152,6 +152,7 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
FsTargetPath: notification.TargetPath, FsTargetPath: notification.TargetPath,
ObjectName: path.Base(notification.VirtualPath), ObjectName: path.Base(notification.VirtualPath),
FileSize: notification.FileSize, FileSize: notification.FileSize,
Elapsed: notification.Elapsed,
Protocol: notification.Protocol, Protocol: notification.Protocol,
IP: notification.IP, IP: notification.IP,
Role: notification.Role, Role: notification.Role,
@ -190,7 +191,7 @@ func newActionNotification(
user *dataprovider.User, user *dataprovider.User,
operation, filePath, virtualPath, target, virtualTarget, sshCmd, protocol, ip, sessionID string, operation, filePath, virtualPath, target, virtualTarget, sshCmd, protocol, ip, sessionID string,
fileSize int64, fileSize int64,
openFlags, status int, openFlags, status int, elapsed int64,
) *notifier.FsEvent { ) *notifier.FsEvent {
var bucket, endpoint string var bucket, endpoint string
@ -232,6 +233,7 @@ func newActionNotification(
OpenFlags: openFlags, OpenFlags: openFlags,
Role: user.Role, Role: user.Role,
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
Elapsed: elapsed,
} }
} }
@ -321,6 +323,7 @@ func notificationAsEnvVars(event *notifier.FsEvent) []string {
fmt.Sprintf("SFTPGO_ACTION_VIRTUAL_TARGET=%s", event.VirtualTargetPath), fmt.Sprintf("SFTPGO_ACTION_VIRTUAL_TARGET=%s", event.VirtualTargetPath),
fmt.Sprintf("SFTPGO_ACTION_SSH_CMD=%s", event.SSHCmd), fmt.Sprintf("SFTPGO_ACTION_SSH_CMD=%s", event.SSHCmd),
fmt.Sprintf("SFTPGO_ACTION_FILE_SIZE=%d", event.FileSize), fmt.Sprintf("SFTPGO_ACTION_FILE_SIZE=%d", event.FileSize),
fmt.Sprintf("SFTPGO_ACTION_ELAPSED=%d", event.Elapsed),
fmt.Sprintf("SFTPGO_ACTION_FS_PROVIDER=%d", event.FsProvider), fmt.Sprintf("SFTPGO_ACTION_FS_PROVIDER=%d", event.FsProvider),
fmt.Sprintf("SFTPGO_ACTION_BUCKET=%s", event.Bucket), fmt.Sprintf("SFTPGO_ACTION_BUCKET=%s", event.Bucket),
fmt.Sprintf("SFTPGO_ACTION_ENDPOINT=%s", event.Endpoint), fmt.Sprintf("SFTPGO_ACTION_ENDPOINT=%s", event.Endpoint),

View file

@ -71,7 +71,7 @@ func TestNewActionNotification(t *testing.T) {
c := NewBaseConnection("id", ProtocolSSH, "", "", user) c := NewBaseConnection("id", ProtocolSSH, "", "", user)
sessionID := xid.New().String() sessionID := xid.New().String()
a := newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID, a := newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
123, 0, c.getNotificationStatus(errors.New("fake error"))) 123, 0, c.getNotificationStatus(errors.New("fake error")), 0)
assert.Equal(t, user.Username, a.Username) assert.Equal(t, user.Username, a.Username)
assert.Equal(t, 0, len(a.Bucket)) assert.Equal(t, 0, len(a.Bucket))
assert.Equal(t, 0, len(a.Endpoint)) assert.Equal(t, 0, len(a.Endpoint))
@ -79,38 +79,38 @@ func TestNewActionNotification(t *testing.T) {
user.FsConfig.Provider = sdk.S3FilesystemProvider user.FsConfig.Provider = sdk.S3FilesystemProvider
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID, a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID,
123, 0, c.getNotificationStatus(nil)) 123, 0, c.getNotificationStatus(nil), 0)
assert.Equal(t, "s3bucket", a.Bucket) assert.Equal(t, "s3bucket", a.Bucket)
assert.Equal(t, "endpoint", a.Endpoint) assert.Equal(t, "endpoint", a.Endpoint)
assert.Equal(t, 1, a.Status) assert.Equal(t, 1, a.Status)
user.FsConfig.Provider = sdk.GCSFilesystemProvider user.FsConfig.Provider = sdk.GCSFilesystemProvider
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID, a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
123, 0, c.getNotificationStatus(ErrQuotaExceeded)) 123, 0, c.getNotificationStatus(ErrQuotaExceeded), 0)
assert.Equal(t, "gcsbucket", a.Bucket) assert.Equal(t, "gcsbucket", a.Bucket)
assert.Equal(t, 0, len(a.Endpoint)) assert.Equal(t, 0, len(a.Endpoint))
assert.Equal(t, 3, a.Status) assert.Equal(t, 3, a.Status)
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID, a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
123, 0, c.getNotificationStatus(fmt.Errorf("wrapper quota error: %w", ErrQuotaExceeded))) 123, 0, c.getNotificationStatus(fmt.Errorf("wrapper quota error: %w", ErrQuotaExceeded)), 0)
assert.Equal(t, "gcsbucket", a.Bucket) assert.Equal(t, "gcsbucket", a.Bucket)
assert.Equal(t, 0, len(a.Endpoint)) assert.Equal(t, 0, len(a.Endpoint))
assert.Equal(t, 3, a.Status) assert.Equal(t, 3, a.Status)
user.FsConfig.Provider = sdk.HTTPFilesystemProvider user.FsConfig.Provider = sdk.HTTPFilesystemProvider
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID, a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID,
123, 0, c.getNotificationStatus(nil)) 123, 0, c.getNotificationStatus(nil), 0)
assert.Equal(t, "httpendpoint", a.Endpoint) assert.Equal(t, "httpendpoint", a.Endpoint)
assert.Equal(t, 1, a.Status) assert.Equal(t, 1, a.Status)
user.FsConfig.Provider = sdk.AzureBlobFilesystemProvider user.FsConfig.Provider = sdk.AzureBlobFilesystemProvider
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID, a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
123, 0, c.getNotificationStatus(nil)) 123, 0, c.getNotificationStatus(nil), 0)
assert.Equal(t, "azcontainer", a.Bucket) assert.Equal(t, "azcontainer", a.Bucket)
assert.Equal(t, "azendpoint", a.Endpoint) assert.Equal(t, "azendpoint", a.Endpoint)
assert.Equal(t, 1, a.Status) assert.Equal(t, 1, a.Status)
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID, a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
123, os.O_APPEND, c.getNotificationStatus(nil)) 123, os.O_APPEND, c.getNotificationStatus(nil), 0)
assert.Equal(t, "azcontainer", a.Bucket) assert.Equal(t, "azcontainer", a.Bucket)
assert.Equal(t, "azendpoint", a.Endpoint) assert.Equal(t, "azendpoint", a.Endpoint)
assert.Equal(t, 1, a.Status) assert.Equal(t, 1, a.Status)
@ -118,7 +118,7 @@ func TestNewActionNotification(t *testing.T) {
user.FsConfig.Provider = sdk.SFTPFilesystemProvider user.FsConfig.Provider = sdk.SFTPFilesystemProvider
a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID, a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
123, 0, c.getNotificationStatus(nil)) 123, 0, c.getNotificationStatus(nil), 0)
assert.Equal(t, "sftpendpoint", a.Endpoint) assert.Equal(t, "sftpendpoint", a.Endpoint)
} }
@ -135,7 +135,7 @@ func TestActionHTTP(t *testing.T) {
}, },
} }
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "",
xid.New().String(), 123, 0, 1) xid.New().String(), 123, 0, 1, 0)
status, err := actionHandler.Handle(a) status, err := actionHandler.Handle(a)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, status) assert.Equal(t, 1, status)
@ -175,16 +175,16 @@ func TestActionCMD(t *testing.T) {
} }
sessionID := shortuuid.New() sessionID := shortuuid.New()
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID, a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
123, 0, 1) 123, 0, 1, 0)
status, err := actionHandler.Handle(a) status, err := actionHandler.Handle(a)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, status) assert.Equal(t, 1, status)
c := NewBaseConnection("id", ProtocolSFTP, "", "", *user) c := NewBaseConnection("id", ProtocolSFTP, "", "", *user)
err = ExecuteActionNotification(c, OperationSSHCmd, "path", "vpath", "target", "vtarget", "sha1sum", 0, nil) err = ExecuteActionNotification(c, OperationSSHCmd, "path", "vpath", "target", "vtarget", "sha1sum", 0, nil, 0)
assert.NoError(t, err) assert.NoError(t, err)
err = ExecuteActionNotification(c, operationDownload, "path", "vpath", "", "", "", 0, nil) err = ExecuteActionNotification(c, operationDownload, "path", "vpath", "", "", "", 0, nil, 0)
assert.NoError(t, err) assert.NoError(t, err)
Config.Actions = actionsCopy Config.Actions = actionsCopy
@ -208,7 +208,7 @@ func TestWrongActions(t *testing.T) {
} }
a := newActionNotification(user, operationUpload, "", "", "", "", "", ProtocolSFTP, "", xid.New().String(), a := newActionNotification(user, operationUpload, "", "", "", "", "", ProtocolSFTP, "", xid.New().String(),
123, 0, 1) 123, 0, 1, 0)
status, err := actionHandler.Handle(a) status, err := actionHandler.Handle(a)
assert.Error(t, err, "action with bad command must fail") assert.Error(t, err, "action with bad command must fail")
assert.Equal(t, 1, status) assert.Equal(t, 1, status)
@ -307,7 +307,7 @@ func TestUnconfiguredHook(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, status, 0) assert.Equal(t, status, 0)
err = ExecuteActionNotification(c, operationDownload, "", "", "", "", "", 0, nil) err = ExecuteActionNotification(c, operationDownload, "", "", "", "", "", 0, nil, 0)
assert.NoError(t, err) assert.NoError(t, err)
err = plugin.Initialize(nil, "debug") err = plugin.Initialize(nil, "debug")

View file

@ -360,15 +360,17 @@ func (c *BaseConnection) CreateDir(virtualPath string, checkFilePatterns bool) e
if err != nil { if err != nil {
return err return err
} }
startTime := time.Now()
if err := fs.Mkdir(fsPath); err != nil { if err := fs.Mkdir(fsPath); err != nil {
c.Log(logger.LevelError, "error creating dir: %#v error: %+v", fsPath, err) c.Log(logger.LevelError, "error creating dir: %#v error: %+v", fsPath, err)
return c.GetFsError(fs, err) return c.GetFsError(fs, err)
} }
vfs.SetPathPermissions(fs, fsPath, c.User.GetUID(), c.User.GetGID()) vfs.SetPathPermissions(fs, fsPath, c.User.GetUID(), c.User.GetGID())
elapsed := time.Since(startTime).Nanoseconds() / 1000000
logger.CommandLog(mkdirLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", -1, logger.CommandLog(mkdirLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", -1,
c.localAddr, c.remoteAddr) c.localAddr, c.remoteAddr, elapsed)
ExecuteActionNotification(c, operationMkdir, fsPath, virtualPath, "", "", "", 0, nil) //nolint:errcheck ExecuteActionNotification(c, operationMkdir, fsPath, virtualPath, "", "", "", 0, nil, elapsed) //nolint:errcheck
return nil return nil
} }
@ -397,6 +399,7 @@ func (c *BaseConnection) RemoveFile(fs vfs.Fs, fsPath, virtualPath string, info
return c.GetPermissionDeniedError() return c.GetPermissionDeniedError()
} }
updateQuota := true updateQuota := true
startTime := time.Now()
if err := fs.Remove(fsPath, false); err != nil { if err := fs.Remove(fsPath, false); err != nil {
if status > 0 && fs.IsNotExist(err) { if status > 0 && fs.IsNotExist(err) {
// file removed in the pre-action, if the file was deleted from the EventManager the quota is already updated // file removed in the pre-action, if the file was deleted from the EventManager the quota is already updated
@ -407,9 +410,10 @@ func (c *BaseConnection) RemoveFile(fs vfs.Fs, fsPath, virtualPath string, info
return c.GetFsError(fs, err) return c.GetFsError(fs, err)
} }
} }
elapsed := time.Since(startTime).Nanoseconds() / 1000000
logger.CommandLog(removeLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", -1, logger.CommandLog(removeLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", -1,
c.localAddr, c.remoteAddr) c.localAddr, c.remoteAddr, elapsed)
if updateQuota && info.Mode()&os.ModeSymlink == 0 { if updateQuota && info.Mode()&os.ModeSymlink == 0 {
vfolder, err := c.User.GetVirtualFolderForPath(path.Dir(virtualPath)) vfolder, err := c.User.GetVirtualFolderForPath(path.Dir(virtualPath))
if err == nil { if err == nil {
@ -421,7 +425,7 @@ func (c *BaseConnection) RemoveFile(fs vfs.Fs, fsPath, virtualPath string, info
dataprovider.UpdateUserQuota(&c.User, -1, -size, false) //nolint:errcheck dataprovider.UpdateUserQuota(&c.User, -1, -size, false) //nolint:errcheck
} }
} }
ExecuteActionNotification(c, operationDelete, fsPath, virtualPath, "", "", "", size, nil) //nolint:errcheck ExecuteActionNotification(c, operationDelete, fsPath, virtualPath, "", "", "", size, nil, elapsed) //nolint:errcheck
return nil return nil
} }
@ -478,14 +482,16 @@ func (c *BaseConnection) RemoveDir(virtualPath string) error {
return c.GetGenericError(nil) return c.GetGenericError(nil)
} }
startTime := time.Now()
if err := fs.Remove(fsPath, true); err != nil { if err := fs.Remove(fsPath, true); err != nil {
c.Log(logger.LevelError, "failed to remove directory %#v: %+v", fsPath, err) c.Log(logger.LevelError, "failed to remove directory %#v: %+v", fsPath, err)
return c.GetFsError(fs, err) return c.GetFsError(fs, err)
} }
elapsed := time.Since(startTime).Nanoseconds() / 1000000
logger.CommandLog(rmdirLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", -1, logger.CommandLog(rmdirLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", -1,
c.localAddr, c.remoteAddr) c.localAddr, c.remoteAddr, elapsed)
ExecuteActionNotification(c, operationRmdir, fsPath, virtualPath, "", "", "", 0, nil) //nolint:errcheck ExecuteActionNotification(c, operationRmdir, fsPath, virtualPath, "", "", "", 0, nil, elapsed) //nolint:errcheck
return nil return nil
} }
@ -597,8 +603,10 @@ func (c *BaseConnection) copyFile(virtualSourcePath, virtualTargetPath string, s
} }
defer wCancelFn() defer wCancelFn()
startTime := time.Now()
_, err = io.Copy(writer, reader) _, err = io.Copy(writer, reader)
return closeWriterAndUpdateQuota(writer, c, virtualSourcePath, virtualTargetPath, numFiles, truncatedSize, err, operationCopy) return closeWriterAndUpdateQuota(writer, c, virtualSourcePath, virtualTargetPath, numFiles, truncatedSize,
err, operationCopy, startTime)
} }
func (c *BaseConnection) doRecursiveCopy(virtualSourcePath, virtualTargetPath string, srcInfo os.FileInfo, func (c *BaseConnection) doRecursiveCopy(virtualSourcePath, virtualTargetPath string, srcInfo os.FileInfo,
@ -709,6 +717,7 @@ func (c *BaseConnection) renameInternal(virtualSourcePath, virtualTargetPath str
if err != nil { if err != nil {
return err return err
} }
startTime := time.Now()
srcInfo, err := fsSrc.Lstat(fsSourcePath) srcInfo, err := fsSrc.Lstat(fsSourcePath)
if err != nil { if err != nil {
return c.GetFsError(fsSrc, err) return c.GetFsError(fsSrc, err)
@ -756,11 +765,12 @@ func (c *BaseConnection) renameInternal(virtualSourcePath, virtualTargetPath str
return c.GetFsError(fsSrc, err) return c.GetFsError(fsSrc, err)
} }
vfs.SetPathPermissions(fsDst, fsTargetPath, c.User.GetUID(), c.User.GetGID()) vfs.SetPathPermissions(fsDst, fsTargetPath, c.User.GetUID(), c.User.GetGID())
elapsed := time.Since(startTime).Nanoseconds() / 1000000
c.updateQuotaAfterRename(fsDst, virtualSourcePath, virtualTargetPath, fsTargetPath, initialSize, files, size) //nolint:errcheck c.updateQuotaAfterRename(fsDst, virtualSourcePath, virtualTargetPath, fsTargetPath, initialSize, files, size) //nolint:errcheck
logger.CommandLog(renameLogSender, fsSourcePath, fsTargetPath, c.User.Username, "", c.ID, c.protocol, -1, -1, logger.CommandLog(renameLogSender, fsSourcePath, fsTargetPath, c.User.Username, "", c.ID, c.protocol, -1, -1,
"", "", "", -1, c.localAddr, c.remoteAddr) "", "", "", -1, c.localAddr, c.remoteAddr, elapsed)
ExecuteActionNotification(c, operationRename, fsSourcePath, virtualSourcePath, fsTargetPath, //nolint:errcheck ExecuteActionNotification(c, operationRename, fsSourcePath, virtualSourcePath, fsTargetPath, //nolint:errcheck
virtualTargetPath, "", 0, nil) virtualTargetPath, "", 0, nil, elapsed)
return nil return nil
} }
@ -810,12 +820,14 @@ func (c *BaseConnection) CreateSymlink(virtualSourcePath, virtualTargetPath stri
if relativePath != "" { if relativePath != "" {
fsSourcePath = relativePath fsSourcePath = relativePath
} }
startTime := time.Now()
if err := fs.Symlink(fsSourcePath, fsTargetPath); err != nil { if err := fs.Symlink(fsSourcePath, fsTargetPath); err != nil {
c.Log(logger.LevelError, "failed to create symlink %#v -> %#v: %+v", fsSourcePath, fsTargetPath, err) c.Log(logger.LevelError, "failed to create symlink %q -> %q: %+v", fsSourcePath, fsTargetPath, err)
return c.GetFsError(fs, err) return c.GetFsError(fs, err)
} }
elapsed := time.Since(startTime).Nanoseconds() / 1000000
logger.CommandLog(symlinkLogSender, fsSourcePath, fsTargetPath, c.User.Username, "", c.ID, c.protocol, -1, -1, "", logger.CommandLog(symlinkLogSender, fsSourcePath, fsTargetPath, c.User.Username, "", c.ID, c.protocol, -1, -1, "",
"", "", -1, c.localAddr, c.remoteAddr) "", "", -1, c.localAddr, c.remoteAddr, elapsed)
return nil return nil
} }
@ -899,12 +911,14 @@ func (c *BaseConnection) handleChmod(fs vfs.Fs, fsPath, pathForPerms string, att
if c.ignoreSetStat(fs) { if c.ignoreSetStat(fs) {
return nil return nil
} }
startTime := time.Now()
if err := fs.Chmod(c.getRealFsPath(fsPath), attributes.Mode); err != nil { if err := fs.Chmod(c.getRealFsPath(fsPath), attributes.Mode); err != nil {
c.Log(logger.LevelError, "failed to chmod path %#v, mode: %v, err: %+v", fsPath, attributes.Mode.String(), err) c.Log(logger.LevelError, "failed to chmod path %#v, mode: %v, err: %+v", fsPath, attributes.Mode.String(), err)
return c.GetFsError(fs, err) return c.GetFsError(fs, err)
} }
elapsed := time.Since(startTime).Nanoseconds() / 1000000
logger.CommandLog(chmodLogSender, fsPath, "", c.User.Username, attributes.Mode.String(), c.ID, c.protocol, logger.CommandLog(chmodLogSender, fsPath, "", c.User.Username, attributes.Mode.String(), c.ID, c.protocol,
-1, -1, "", "", "", -1, c.localAddr, c.remoteAddr) -1, -1, "", "", "", -1, c.localAddr, c.remoteAddr, elapsed)
return nil return nil
} }
@ -915,13 +929,15 @@ func (c *BaseConnection) handleChown(fs vfs.Fs, fsPath, pathForPerms string, att
if c.ignoreSetStat(fs) { if c.ignoreSetStat(fs) {
return nil return nil
} }
startTime := time.Now()
if err := fs.Chown(c.getRealFsPath(fsPath), attributes.UID, attributes.GID); err != nil { if err := fs.Chown(c.getRealFsPath(fsPath), attributes.UID, attributes.GID); err != nil {
c.Log(logger.LevelError, "failed to chown path %#v, uid: %v, gid: %v, err: %+v", fsPath, attributes.UID, c.Log(logger.LevelError, "failed to chown path %#v, uid: %v, gid: %v, err: %+v", fsPath, attributes.UID,
attributes.GID, err) attributes.GID, err)
return c.GetFsError(fs, err) return c.GetFsError(fs, err)
} }
elapsed := time.Since(startTime).Nanoseconds() / 1000000
logger.CommandLog(chownLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, attributes.UID, attributes.GID, logger.CommandLog(chownLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, attributes.UID, attributes.GID,
"", "", "", -1, c.localAddr, c.remoteAddr) "", "", "", -1, c.localAddr, c.remoteAddr, elapsed)
return nil return nil
} }
@ -932,6 +948,7 @@ func (c *BaseConnection) handleChtimes(fs vfs.Fs, fsPath, pathForPerms string, a
if Config.SetstatMode == 1 { if Config.SetstatMode == 1 {
return nil return nil
} }
startTime := time.Now()
isUploading := c.setTimes(fsPath, attributes.Atime, attributes.Mtime) isUploading := c.setTimes(fsPath, attributes.Atime, attributes.Mtime)
if err := fs.Chtimes(c.getRealFsPath(fsPath), attributes.Atime, attributes.Mtime, isUploading); err != nil { if err := fs.Chtimes(c.getRealFsPath(fsPath), attributes.Atime, attributes.Mtime, isUploading); err != nil {
c.setTimes(fsPath, time.Time{}, time.Time{}) c.setTimes(fsPath, time.Time{}, time.Time{})
@ -942,10 +959,11 @@ func (c *BaseConnection) handleChtimes(fs vfs.Fs, fsPath, pathForPerms string, a
fsPath, attributes.Atime, attributes.Mtime, err) fsPath, attributes.Atime, attributes.Mtime, err)
return c.GetFsError(fs, err) return c.GetFsError(fs, err)
} }
elapsed := time.Since(startTime).Nanoseconds() / 1000000
accessTimeString := attributes.Atime.Format(chtimesFormat) accessTimeString := attributes.Atime.Format(chtimesFormat)
modificationTimeString := attributes.Mtime.Format(chtimesFormat) modificationTimeString := attributes.Mtime.Format(chtimesFormat)
logger.CommandLog(chtimesLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, logger.CommandLog(chtimesLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1,
accessTimeString, modificationTimeString, "", -1, c.localAddr, c.remoteAddr) accessTimeString, modificationTimeString, "", -1, c.localAddr, c.remoteAddr, elapsed)
return nil return nil
} }
@ -982,13 +1000,14 @@ func (c *BaseConnection) SetStat(virtualPath string, attributes *StatAttributes)
if !c.User.HasPerm(dataprovider.PermOverwrite, pathForPerms) { if !c.User.HasPerm(dataprovider.PermOverwrite, pathForPerms) {
return c.GetPermissionDeniedError() return c.GetPermissionDeniedError()
} }
startTime := time.Now()
if err = c.truncateFile(fs, fsPath, virtualPath, attributes.Size); err != nil { if err = c.truncateFile(fs, fsPath, virtualPath, attributes.Size); err != nil {
c.Log(logger.LevelError, "failed to truncate path %#v, size: %v, err: %+v", fsPath, attributes.Size, err) c.Log(logger.LevelError, "failed to truncate path %#v, size: %v, err: %+v", fsPath, attributes.Size, err)
return c.GetFsError(fs, err) return c.GetFsError(fs, err)
} }
elapsed := time.Since(startTime).Nanoseconds() / 1000000
logger.CommandLog(truncateLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", logger.CommandLog(truncateLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "",
"", attributes.Size, c.localAddr, c.remoteAddr) "", attributes.Size, c.localAddr, c.remoteAddr, elapsed)
} }
return nil return nil

View file

@ -447,6 +447,7 @@ type EventParams struct {
ObjectName string ObjectName string
ObjectType string ObjectType string
FileSize int64 FileSize int64
Elapsed int64
Protocol string Protocol string
IP string IP string
Role string Role string
@ -633,6 +634,7 @@ func (p *EventParams) getStringReplacements(addObjectData bool) []string {
"{{ObjectName}}", p.ObjectName, "{{ObjectName}}", p.ObjectName,
"{{ObjectType}}", p.ObjectType, "{{ObjectType}}", p.ObjectType,
"{{FileSize}}", fmt.Sprintf("%d", p.FileSize), "{{FileSize}}", fmt.Sprintf("%d", p.FileSize),
"{{Elapsed}}", fmt.Sprintf("%d", p.Elapsed),
"{{Protocol}}", p.Protocol, "{{Protocol}}", p.Protocol,
"{{IP}}", p.IP, "{{IP}}", p.IP,
"{{Role}}", p.Role, "{{Role}}", p.Role,
@ -685,7 +687,7 @@ func getCSVRetentionReport(results []folderRetentionCheckResult) ([]byte, error)
} }
func closeWriterAndUpdateQuota(w io.WriteCloser, conn *BaseConnection, virtualSourcePath, virtualTargetPath string, func closeWriterAndUpdateQuota(w io.WriteCloser, conn *BaseConnection, virtualSourcePath, virtualTargetPath string,
numFiles int, truncatedSize int64, errTransfer error, operation string, numFiles int, truncatedSize int64, errTransfer error, operation string, startTime time.Time,
) error { ) error {
errWrite := w.Close() errWrite := w.Close()
targetPath := virtualSourcePath targetPath := virtualSourcePath
@ -704,14 +706,15 @@ func closeWriterAndUpdateQuota(w io.WriteCloser, conn *BaseConnection, virtualSo
_, fsDstPath, errDstFs = conn.GetFsAndResolvedPath(virtualTargetPath) _, fsDstPath, errDstFs = conn.GetFsAndResolvedPath(virtualTargetPath)
} }
if errSrcFs == nil && errDstFs == nil { if errSrcFs == nil && errDstFs == nil {
elapsed := time.Since(startTime).Nanoseconds() / 1000000
if errTransfer == nil { if errTransfer == nil {
errTransfer = errWrite errTransfer = errWrite
} }
if operation == operationCopy { if operation == operationCopy {
logger.CommandLog(copyLogSender, fsSrcPath, fsDstPath, conn.User.Username, "", conn.ID, conn.protocol, -1, -1, logger.CommandLog(copyLogSender, fsSrcPath, fsDstPath, conn.User.Username, "", conn.ID, conn.protocol, -1, -1,
"", "", "", info.Size(), conn.localAddr, conn.remoteAddr) "", "", "", info.Size(), conn.localAddr, conn.remoteAddr, elapsed)
} }
ExecuteActionNotification(conn, operation, fsSrcPath, virtualSourcePath, fsDstPath, virtualTargetPath, "", info.Size(), errTransfer) //nolint:errcheck ExecuteActionNotification(conn, operation, fsSrcPath, virtualSourcePath, fsDstPath, virtualTargetPath, "", info.Size(), errTransfer, elapsed) //nolint:errcheck
} }
} else { } else {
eventManagerLog(logger.LevelWarn, "unable to update quota after writing %q: %v", targetPath, err) eventManagerLog(logger.LevelWarn, "unable to update quota after writing %q: %v", targetPath, err)
@ -1735,18 +1738,19 @@ func executeCompressFsActionForUser(c dataprovider.EventActionFsCompress, replac
Writer: zip.NewWriter(writer), Writer: zip.NewWriter(writer),
Entries: make(map[string]bool), Entries: make(map[string]bool),
} }
startTime := time.Now()
for _, item := range paths { for _, item := range paths {
if err := addZipEntry(zipWriter, conn, item, baseDir); err != nil { if err := addZipEntry(zipWriter, conn, item, baseDir); err != nil {
closeWriterAndUpdateQuota(writer, conn, name, "", numFiles, truncatedSize, err, operationUpload) //nolint:errcheck closeWriterAndUpdateQuota(writer, conn, name, "", numFiles, truncatedSize, err, operationUpload, startTime) //nolint:errcheck
return err return err
} }
} }
if err := zipWriter.Writer.Close(); err != nil { if err := zipWriter.Writer.Close(); err != nil {
eventManagerLog(logger.LevelError, "unable to close zip file %q: %v", name, err) eventManagerLog(logger.LevelError, "unable to close zip file %q: %v", name, err)
closeWriterAndUpdateQuota(writer, conn, name, "", numFiles, truncatedSize, err, operationUpload) //nolint:errcheck closeWriterAndUpdateQuota(writer, conn, name, "", numFiles, truncatedSize, err, operationUpload, startTime) //nolint:errcheck
return fmt.Errorf("unable to close zip file %q: %w", name, err) return fmt.Errorf("unable to close zip file %q: %w", name, err)
} }
return closeWriterAndUpdateQuota(writer, conn, name, "", numFiles, truncatedSize, err, operationUpload) return closeWriterAndUpdateQuota(writer, conn, name, "", numFiles, truncatedSize, err, operationUpload, startTime)
} }
func executeExistFsRuleAction(exist []string, replacer *strings.Replacer, conditions dataprovider.ConditionOptions, func executeExistFsRuleAction(exist []string, replacer *strings.Replacer, conditions dataprovider.ConditionOptions,

View file

@ -393,7 +393,7 @@ func (t *BaseTransfer) Close() error {
logger.TransferLog(downloadLogSender, t.fsPath, elapsed, t.BytesSent.Load(), t.Connection.User.Username, logger.TransferLog(downloadLogSender, t.fsPath, elapsed, t.BytesSent.Load(), t.Connection.User.Username,
t.Connection.ID, t.Connection.protocol, t.Connection.localAddr, t.Connection.remoteAddr, t.ftpMode) t.Connection.ID, t.Connection.protocol, t.Connection.localAddr, t.Connection.remoteAddr, t.ftpMode)
ExecuteActionNotification(t.Connection, operationDownload, t.fsPath, t.requestPath, "", "", "", //nolint:errcheck ExecuteActionNotification(t.Connection, operationDownload, t.fsPath, t.requestPath, "", "", "", //nolint:errcheck
t.BytesSent.Load(), t.ErrTransfer) t.BytesSent.Load(), t.ErrTransfer, elapsed)
} else { } else {
statSize, deletedFiles, errStat := t.getUploadFileSize() statSize, deletedFiles, errStat := t.getUploadFileSize()
if errStat == nil { if errStat == nil {
@ -408,7 +408,7 @@ func (t *BaseTransfer) Close() error {
numFiles -= deletedFiles numFiles -= deletedFiles
t.Connection.Log(logger.LevelDebug, "upload file size %d, num files %d, deleted files %d, fs path %q", t.Connection.Log(logger.LevelDebug, "upload file size %d, num files %d, deleted files %d, fs path %q",
uploadFileSize, numFiles, deletedFiles, t.fsPath) uploadFileSize, numFiles, deletedFiles, t.fsPath)
numFiles, uploadFileSize = t.executeUploadHook(numFiles, uploadFileSize) numFiles, uploadFileSize = t.executeUploadHook(numFiles, uploadFileSize, elapsed)
t.updateQuota(numFiles, uploadFileSize) t.updateQuota(numFiles, uploadFileSize)
t.updateTimes() t.updateTimes()
logger.TransferLog(uploadLogSender, t.fsPath, elapsed, t.BytesReceived.Load(), t.Connection.User.Username, logger.TransferLog(uploadLogSender, t.fsPath, elapsed, t.BytesReceived.Load(), t.Connection.User.Username,
@ -420,11 +420,11 @@ func (t *BaseTransfer) Close() error {
err = t.ErrTransfer err = t.ErrTransfer
} }
} }
t.updateTransferTimestamps(uploadFileSize) t.updateTransferTimestamps(uploadFileSize, elapsed)
return err return err
} }
func (t *BaseTransfer) updateTransferTimestamps(uploadFileSize int64) { func (t *BaseTransfer) updateTransferTimestamps(uploadFileSize, elapsed int64) {
if t.ErrTransfer != nil { if t.ErrTransfer != nil {
return return
} }
@ -433,7 +433,7 @@ func (t *BaseTransfer) updateTransferTimestamps(uploadFileSize int64) {
if err := dataprovider.UpdateUserTransferTimestamps(t.Connection.User.Username, true); err == nil { if err := dataprovider.UpdateUserTransferTimestamps(t.Connection.User.Username, true); err == nil {
t.Connection.uploadDone.Store(true) t.Connection.uploadDone.Store(true)
ExecuteActionNotification(t.Connection, operationFirstUpload, t.fsPath, t.requestPath, "", //nolint:errcheck ExecuteActionNotification(t.Connection, operationFirstUpload, t.fsPath, t.requestPath, "", //nolint:errcheck
"", "", uploadFileSize, t.ErrTransfer) "", "", uploadFileSize, t.ErrTransfer, elapsed)
} }
} }
return return
@ -442,14 +442,14 @@ func (t *BaseTransfer) updateTransferTimestamps(uploadFileSize int64) {
if err := dataprovider.UpdateUserTransferTimestamps(t.Connection.User.Username, false); err == nil { if err := dataprovider.UpdateUserTransferTimestamps(t.Connection.User.Username, false); err == nil {
t.Connection.downloadDone.Store(true) t.Connection.downloadDone.Store(true)
ExecuteActionNotification(t.Connection, operationFirstDownload, t.fsPath, t.requestPath, "", //nolint:errcheck ExecuteActionNotification(t.Connection, operationFirstDownload, t.fsPath, t.requestPath, "", //nolint:errcheck
"", "", t.BytesSent.Load(), t.ErrTransfer) "", "", t.BytesSent.Load(), t.ErrTransfer, elapsed)
} }
} }
} }
func (t *BaseTransfer) executeUploadHook(numFiles int, fileSize int64) (int, int64) { func (t *BaseTransfer) executeUploadHook(numFiles int, fileSize, elapsed int64) (int, int64) {
err := ExecuteActionNotification(t.Connection, operationUpload, t.fsPath, t.requestPath, "", "", "", err := ExecuteActionNotification(t.Connection, operationUpload, t.fsPath, t.requestPath, "", "", "",
fileSize, t.ErrTransfer) fileSize, t.ErrTransfer, elapsed)
if err != nil { if err != nil {
if t.ErrTransfer == nil { if t.ErrTransfer == nil {
t.ErrTransfer = err t.ErrTransfer = err

View file

@ -223,7 +223,7 @@ func TransferLog(operation, path string, elapsed int64, size int64, user, connec
// CommandLog logs an SFTP/SCP/SSH command // CommandLog logs an SFTP/SCP/SSH command
func CommandLog(command, path, target, user, fileMode, connectionID, protocol string, uid, gid int, atime, mtime, func CommandLog(command, path, target, user, fileMode, connectionID, protocol string, uid, gid int, atime, mtime,
sshCommand string, size int64, localAddr, remoteAddr string) { sshCommand string, size int64, localAddr, remoteAddr string, elapsed int64) {
logger.Info(). logger.Info().
Timestamp(). Timestamp().
Str("sender", command). Str("sender", command).
@ -238,6 +238,7 @@ func CommandLog(command, path, target, user, fileMode, connectionID, protocol st
Str("access_time", atime). Str("access_time", atime).
Str("modification_time", mtime). Str("modification_time", mtime).
Int64("size", size). Int64("size", size).
Int64("elapsed", elapsed).
Str("ssh_command", sshCommand). Str("ssh_command", sshCommand).
Str("connection_id", connectionID). Str("connection_id", connectionID).
Str("protocol", protocol). Str("protocol", protocol).

View file

@ -29,6 +29,7 @@ import (
"runtime/debug" "runtime/debug"
"strings" "strings"
"sync" "sync"
"time"
"github.com/google/shlex" "github.com/google/shlex"
"github.com/sftpgo/sdk" "github.com/sftpgo/sdk"
@ -55,6 +56,7 @@ type sshCommand struct {
command string command string
args []string args []string
connection *Connection connection *Connection
startTime time.Time
} }
type systemCommand struct { type systemCommand struct {
@ -97,6 +99,7 @@ func processSSHCommand(payload []byte, connection *Connection, enabledSSHCommand
sshCommand: sshCommand{ sshCommand: sshCommand{
command: name, command: name,
connection: connection, connection: connection,
startTime: time.Now(),
args: args}, args: args},
} }
go scpCommand.handle() //nolint:errcheck go scpCommand.handle() //nolint:errcheck
@ -107,6 +110,7 @@ func processSSHCommand(payload []byte, connection *Connection, enabledSSHCommand
sshCommand := sshCommand{ sshCommand := sshCommand{
command: name, command: name,
connection: connection, connection: connection,
startTime: time.Now(),
args: args, args: args,
} }
go sshCommand.handle() //nolint:errcheck go sshCommand.handle() //nolint:errcheck
@ -549,6 +553,7 @@ func (c *sshCommand) sendExitStatus(err error) {
c.connection.channel.Close() c.connection.channel.Close()
// for scp we notify single uploads/downloads // for scp we notify single uploads/downloads
if c.command != scpCmdName { if c.command != scpCmdName {
elapsed := time.Since(c.startTime).Nanoseconds() / 1000000
metric.SSHCommandCompleted(err) metric.SSHCommandCompleted(err)
if vCmdPath != "" { if vCmdPath != "" {
_, p, errFs := c.connection.GetFsAndResolvedPath(vCmdPath) _, p, errFs := c.connection.GetFsAndResolvedPath(vCmdPath)
@ -563,11 +568,11 @@ func (c *sshCommand) sendExitStatus(err error) {
} }
} }
common.ExecuteActionNotification(c.connection.BaseConnection, common.OperationSSHCmd, cmdPath, vCmdPath, //nolint:errcheck common.ExecuteActionNotification(c.connection.BaseConnection, common.OperationSSHCmd, cmdPath, vCmdPath, //nolint:errcheck
targetPath, vTargetPath, c.command, 0, err) targetPath, vTargetPath, c.command, 0, err, elapsed)
if err == nil { if err == nil {
logger.CommandLog(sshCommandLogSender, cmdPath, targetPath, c.connection.User.Username, "", c.connection.ID, logger.CommandLog(sshCommandLogSender, cmdPath, targetPath, c.connection.User.Username, "", c.connection.ID,
common.ProtocolSSH, -1, -1, "", "", c.connection.command, -1, c.connection.GetLocalAddress(), common.ProtocolSSH, -1, -1, "", "", c.connection.command, -1, c.connection.GetLocalAddress(),
c.connection.GetRemoteAddress()) c.connection.GetRemoteAddress(), elapsed)
} }
} }
} }

View file

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
NFPM_VERSION=2.25.0 NFPM_VERSION=2.26.0
NFPM_ARCH=${NFPM_ARCH:-amd64} NFPM_ARCH=${NFPM_ARCH:-amd64}
if [ -z ${SFTPGO_VERSION} ] if [ -z ${SFTPGO_VERSION} ]
then then

View file

@ -760,6 +760,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<p> <p>
<span class="shortcut"><b>{{`{{FileSize}}`}}</b></span> => File size. <span class="shortcut"><b>{{`{{FileSize}}`}}</b></span> => File size.
</p> </p>
<p>
<span class="shortcut"><b>{{`{{Elapsed}}`}}</b></span> => Elapsed time as milliseconds for filesystem events.
</p>
<p> <p>
<span class="shortcut"><b>{{`{{Protocol}}`}}</b></span> => Protocol, for example "SFTP", "FTP". <span class="shortcut"><b>{{`{{Protocol}}`}}</b></span> => Protocol, for example "SFTP", "FTP".
</p> </p>