From 7eb5b01169b801a619cbb57f2e53760549d90a18 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Fri, 13 Sep 2019 18:45:36 +0200 Subject: [PATCH] add Prometheus support some basic counters and gauges are now exposed --- README.md | 18 +++ api/api.go | 1 + api/api_test.go | 7 ++ api/router.go | 3 + dataprovider/bolt.go | 5 + dataprovider/dataprovider.go | 47 ++++++-- dataprovider/mysql.go | 4 + dataprovider/pgsql.go | 4 + dataprovider/sqlcommon.go | 4 + dataprovider/sqlite.go | 4 + go.mod | 8 +- go.sum | 58 +++++++++- logger/request_logger.go | 2 + metrics/metrics.go | 212 +++++++++++++++++++++++++++++++++++ sftpd/server.go | 15 ++- sftpd/sftpd.go | 3 + sftpd/transfer.go | 2 + 17 files changed, 377 insertions(+), 20 deletions(-) create mode 100644 metrics/metrics.go diff --git a/README.md b/README.md index eebc206e..ce784186 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Full featured and highly configurable SFTP server - Atomic uploads are configurable. - Optional SCP support. - REST API for users and quota management and real time reports for the active connections with possibility of forcibly closing a connection. +- Prometheus metrics are exposed. - Configuration is a your choice: JSON, TOML, YAML, HCL, envfile are supported. - Log files are accurate and they are saved in the easily parsable JSON format. @@ -289,6 +290,23 @@ A sample CLI client for the REST API can be found inside the source tree [script You can also generate your own REST client, in your preferred programming language or even bash scripts, using an OpenAPI generator such as [swagger-codegen](https://github.com/swagger-api/swagger-codegen) or [OpenAPI Generator](https://openapi-generator.tech/) +## Metrics + +SFTPGo exposes [Prometheus](https://prometheus.io/) metrics at the `/metrics` HTTP endpoint. +Several counters and gauges are available, for example: + +- Total uploads and downloads +- Total uploads and downloads size +- Total uploads and downloads errors +- Number of active connections +- Data provider availability +- Total successful and failed logins using a password or a public key +- Total HTTP requests served and totals for response code +- Go's runtime like details about GC, number of gouroutines and OS threads +- Process information like CPU, memory, file descriptor usage and start time + +Please check the `/metrics` page for more details. + ## Logs Inside the log file each line is a JSON struct, each struct has a `sender` fields that identify the log type. diff --git a/api/api.go b/api/api.go index 2b280edc..1f00616c 100644 --- a/api/api.go +++ b/api/api.go @@ -19,6 +19,7 @@ const ( quotaScanPath = "/api/v1/quota_scan" userPath = "/api/v1/user" versionPath = "/api/v1/version" + metricsPath = "/metrics" ) var ( diff --git a/api/api_test.go b/api/api_test.go index 1a760a45..7cfd3fb1 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -36,6 +36,7 @@ const ( activeConnectionsPath = "/api/v1/connection" quotaScanPath = "/api/v1/quota_scan" versionPath = "/api/v1/version" + metricsPath = "/metrics" ) var ( @@ -710,6 +711,12 @@ func TestMethodNotAllowedMock(t *testing.T) { checkResponseCode(t, http.StatusMethodNotAllowed, rr.Code) } +func TestMetricsMock(t *testing.T) { + req, _ := http.NewRequest(http.MethodGet, metricsPath, nil) + rr := executeRequest(req) + checkResponseCode(t, http.StatusOK, rr.Code) +} + func waitTCPListening(address string) { for { conn, err := net.Dial("tcp", address) diff --git a/api/router.go b/api/router.go index bd58668f..584da461 100644 --- a/api/router.go +++ b/api/router.go @@ -9,6 +9,7 @@ import ( "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "github.com/go-chi/render" + "github.com/prometheus/client_golang/prometheus/promhttp" ) // GetHTTPRouter returns the configured HTTP handler @@ -31,6 +32,8 @@ func initializeRouter() { sendAPIResponse(w, r, nil, "Method not allowed", http.StatusMethodNotAllowed) })) + router.Handle(metricsPath, promhttp.Handler()) + router.Get(versionPath, func(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, utils.GetAppVersion()) }) diff --git a/dataprovider/bolt.go b/dataprovider/bolt.go index fc0cf0c5..c5faaec3 100644 --- a/dataprovider/bolt.go +++ b/dataprovider/bolt.go @@ -59,6 +59,11 @@ func initializeBoltProvider(basePath string) error { return err } +func (p BoltProvider) checkAvailability() error { + _, err := p.getUsers(1, 0, "ASC", "") + return err +} + func (p BoltProvider) validateUserAndPass(username string, password string) (User, error) { var user User if len(password) == 0 { diff --git a/dataprovider/dataprovider.go b/dataprovider/dataprovider.go index d0aef535..db5f76e4 100644 --- a/dataprovider/dataprovider.go +++ b/dataprovider/dataprovider.go @@ -15,6 +15,7 @@ import ( "path/filepath" "strconv" "strings" + "time" "github.com/alexedwards/argon2id" "golang.org/x/crypto/bcrypt" @@ -22,6 +23,7 @@ import ( "golang.org/x/crypto/ssh" "github.com/drakkan/sftpgo/logger" + "github.com/drakkan/sftpgo/metrics" "github.com/drakkan/sftpgo/utils" ) @@ -52,9 +54,10 @@ var ( sqlPlaceholders []string validPerms = []string{PermAny, PermListItems, PermDownload, PermUpload, PermDelete, PermRename, PermCreateDirs, PermCreateSymlinks} - hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix} - pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix} - logSender = "dataProvider" + hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix} + pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix} + logSender = "dataProvider" + availabilityTicker *time.Ticker ) // Config provider configuration @@ -144,23 +147,34 @@ type Provider interface { deleteUser(user User) error getUsers(limit int, offset int, order string, username string) ([]User, error) getUserByID(ID int64) (User, error) + checkAvailability() error +} + +func init() { + availabilityTicker = time.NewTicker(30 * time.Second) } // Initialize the data provider. // An error is returned if the configured driver is invalid or if the data provider cannot be initialized func Initialize(cnf Config, basePath string) error { + var err error config = cnf sqlPlaceholders = getSQLPlaceholders() if config.Driver == SQLiteDataProviderName { - return initializeSQLiteProvider(basePath) + err = initializeSQLiteProvider(basePath) } else if config.Driver == PGSQLDataProviderName { - return initializePGSQLProvider() + err = initializePGSQLProvider() } else if config.Driver == MySQLDataProviderName { - return initializeMySQLProvider() + err = initializeMySQLProvider() } else if config.Driver == BoltDataProviderName { - return initializeBoltProvider(basePath) + err = initializeBoltProvider(basePath) + } else { + err = fmt.Errorf("Unsupported data provider: %v", config.Driver) } - return fmt.Errorf("Unsupported data provider: %v", config.Driver) + if err == nil { + startAvailabilityTimer() + } + return err } // CheckUserAndPass retrieves the SFTP user with the given username and password if a match is found or an error @@ -374,6 +388,23 @@ func getSSLMode() string { return "" } +func startAvailabilityTimer() { + checkDataprovider() + go func() { + for range availabilityTicker.C { + checkDataprovider() + } + }() +} + +func checkDataprovider() { + err := provider.checkAvailability() + if err != nil { + providerLog(logger.LevelWarn, "check availability error: %v", err) + } + metrics.UpdateDataProviderAvailability(err) +} + func providerLog(level logger.LogLevel, format string, v ...interface{}) { logger.Log(level, logSender, "", format, v...) } diff --git a/dataprovider/mysql.go b/dataprovider/mysql.go index e8f7f7df..2dc19262 100644 --- a/dataprovider/mysql.go +++ b/dataprovider/mysql.go @@ -35,6 +35,10 @@ func initializeMySQLProvider() error { return err } +func (p MySQLProvider) checkAvailability() error { + return sqlCommonCheckAvailability(p.dbHandle) +} + func (p MySQLProvider) validateUserAndPass(username string, password string) (User, error) { return sqlCommonValidateUserAndPass(username, password, p.dbHandle) } diff --git a/dataprovider/pgsql.go b/dataprovider/pgsql.go index bbdc23d3..76a932c2 100644 --- a/dataprovider/pgsql.go +++ b/dataprovider/pgsql.go @@ -33,6 +33,10 @@ func initializePGSQLProvider() error { return err } +func (p PGSQLProvider) checkAvailability() error { + return sqlCommonCheckAvailability(p.dbHandle) +} + func (p PGSQLProvider) validateUserAndPass(username string, password string) (User, error) { return sqlCommonValidateUserAndPass(username, password, p.dbHandle) } diff --git a/dataprovider/sqlcommon.go b/dataprovider/sqlcommon.go index 79fd1782..7f1fa2c8 100644 --- a/dataprovider/sqlcommon.go +++ b/dataprovider/sqlcommon.go @@ -50,6 +50,10 @@ func sqlCommonValidateUserAndPubKey(username string, pubKey string, dbHandle *sq return checkUserAndPubKey(user, pubKey) } +func sqlCommonCheckAvailability(dbHandle *sql.DB) error { + return dbHandle.Ping() +} + func sqlCommonGetUserByID(ID int64, dbHandle *sql.DB) (User, error) { var user User q := getUserByIDQuery() diff --git a/dataprovider/sqlite.go b/dataprovider/sqlite.go index a72c9872..99da61e0 100644 --- a/dataprovider/sqlite.go +++ b/dataprovider/sqlite.go @@ -50,6 +50,10 @@ func initializeSQLiteProvider(basePath string) error { return err } +func (p SQLiteProvider) checkAvailability() error { + return sqlCommonCheckAvailability(p.dbHandle) +} + func (p SQLiteProvider) validateUserAndPass(username string, password string) (User, error) { return sqlCommonValidateUserAndPass(username, password, p.dbHandle) } diff --git a/go.mod b/go.mod index 73421137..4029f6b6 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,10 @@ require ( github.com/mattn/go-sqlite3 v1.11.0 github.com/pelletier/go-toml v1.4.0 // indirect github.com/pkg/sftp v1.10.2-0.20190913011139-8fc59612f2b0 + github.com/prometheus/client_golang v1.1.0 + github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect + github.com/prometheus/common v0.7.0 // indirect + github.com/prometheus/procfs v0.0.4 // indirect github.com/rs/xid v1.2.1 github.com/rs/zerolog v1.15.0 github.com/spf13/afero v1.2.2 // indirect @@ -19,9 +23,9 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/viper v1.4.0 go.etcd.io/bbolt v1.3.3 - golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 + golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect - golang.org/x/sys v0.0.0-20190830142957-1e83adbbebd0 // indirect + golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7 // indirect google.golang.org/appengine v1.6.2 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) diff --git a/go.sum b/go.sum index 14585384..8ed33dd1 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,18 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alexedwards/argon2id v0.0.0-20190612080829-01a59b2b8802 h1:RwMM1q/QSKYIGbHfOkf843hE8sSUJtf1dMwFPtEDmm0= github.com/alexedwards/argon2id v0.0.0-20190612080829-01a59b2b8802/go.mod h1:4dsm7ufQm1Gwl8S2ss57u+2J7KlxIL2QUmFGlGtWogY= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -18,6 +23,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -29,6 +35,7 @@ github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxm github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= @@ -41,16 +48,24 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -58,8 +73,10 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -68,10 +85,15 @@ github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzR github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -80,19 +102,34 @@ github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUr github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.10.2-0.20190913011139-8fc59612f2b0 h1:uSPJdIuCJRCOQWSER5+E2xC2KwmgKR4XFGiOSlJdsqA= github.com/pkg/sftp v1.10.2-0.20190913011139-8fc59612f2b0/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.4 h1:w8DjqFMJDjuVwdZBQoOozr4MVWOnwF7RcL/7uxBjY78= +github.com/prometheus/procfs v0.0.4/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= @@ -101,6 +138,7 @@ github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -121,6 +159,8 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= @@ -139,8 +179,8 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM= -golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -150,6 +190,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -163,9 +205,11 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190830142957-1e83adbbebd0 h1:7z820YPX9pxWR59qM7BE5+fglp4D/mKqAwCvGt11b+8= -golang.org/x/sys v0.0.0-20190830142957-1e83adbbebd0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7 h1:wYqz/tQaWUgGKyx+B/rssSE6wkIKdY5Ee6ryOmzarIg= +golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -177,12 +221,14 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.6.2 h1:j8RI1yW0SkI+paT6uGwMlrMI/6zwYA6/CFil8rxOzGI= google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= diff --git a/logger/request_logger.go b/logger/request_logger.go index a21cdb3a..df8edc85 100644 --- a/logger/request_logger.go +++ b/logger/request_logger.go @@ -5,6 +5,7 @@ import ( "net/http" "time" + "github.com/drakkan/sftpgo/metrics" "github.com/go-chi/chi/middleware" "github.com/rs/zerolog" ) @@ -54,6 +55,7 @@ func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry { // Write logs a new entry at the end of the HTTP request func (l *StructuredLoggerEntry) Write(status, bytes int, elapsed time.Duration) { + metrics.HTTPRequestServed(status) l.Logger.Info().Fields(l.fields).Int( "resp_status", status).Int( "resp_size", bytes).Int64( diff --git a/metrics/metrics.go b/metrics/metrics.go new file mode 100644 index 00000000..930c321c --- /dev/null +++ b/metrics/metrics.go @@ -0,0 +1,212 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var ( + // dataproviderAvailability is the metric that reports the availability for the configured data provider + dataproviderAvailability = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "sftpgo_dataprovider_availability", + Help: "Availability for the configured data provider, 1 means OK, 0 KO", + }) + + // activeConnections is the metric that reports the total number of active connections + activeConnections = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "sftpgo_active_connections", + Help: "Total number of logged in users", + }) + + // totalUploads is the metric that reports the total number of uploads + totalUploads = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_uploads_total", + Help: "The total number of uploads", + }) + + // totalDownloads is the metric that reports the total number of downloads + totalDownloads = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_downloads_total", + Help: "The total number of downloads", + }) + + // totalUploadErrors is the metric that reports the total number of upload errors + totalUploadErrors = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_upload_errors_total", + Help: "The total number of upload errors", + }) + + // totalDownloadErrors is the metric that reports the total number of download errors + totalDownloadErrors = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_download_errors_total", + Help: "The total number of download errors", + }) + + // totalUploadSize is the metric that reports the total uploads size as bytes + totalUploadSize = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_upload_size", + Help: "The total upload size as bytes", + }) + + // totalDownloadSize is the metric that reports the total downloads size as bytes + totalDownloadSize = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_download_size", + Help: "The total download size as bytes", + }) + + // totalLoginAttempts is the metric that reports the total number of login attempts + totalLoginAttempts = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_login_attempts_total", + Help: "The total number of login attempts", + }) + + // totalLoginOK is the metric that reports the total number of successfull logins + totalLoginOK = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_login_ok_total", + Help: "The total number of successfull logins", + }) + + // totalLoginFailed is the metric that reports the total number of failed logins + totalLoginFailed = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_login_ko_total", + Help: "The total number of failed logins", + }) + + // totalPasswordLoginAttempts is the metric that reports the total number of login attempts + // using a password + totalPasswordLoginAttempts = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_password_login_attempts_total", + Help: "The total number of login attempts using a password", + }) + + // totalPasswordLoginOK is the metric that reports the total number of successfull logins + // using a password + totalPasswordLoginOK = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_password_login_ok_total", + Help: "The total number of successfull logins using a password", + }) + + // totalPasswordLoginFailed is the metric that reports the total number of failed logins + // using a password + totalPasswordLoginFailed = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_password_login_ko_total", + Help: "The total number of failed logins using a password", + }) + + // totalKeyLoginAttempts is the metric that reports the total number of login attempts + // using a public key + totalKeyLoginAttempts = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_public_key_login_attempts_total", + Help: "The total number of login attempts using a public key", + }) + + // totalKeyLoginOK is the metric that reports the total number of successfull logins + // using a public key + totalKeyLoginOK = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_public_key_login_ok_total", + Help: "The total number of successfull logins using a public key", + }) + + // totalKeyLoginFailed is the metric that reports the total number of failed logins + // using a public key + totalKeyLoginFailed = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_public_key_login_ko_total", + Help: "The total number of failed logins using a public key", + }) + + totalHTTPRequests = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_http_req_total", + Help: "The total number of HTTP requests served", + }) + + totalHTTPOK = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_http_req_ok_total", + Help: "The total number of HTTP requests served with 2xx status code", + }) + + totalHTTPClientErrors = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_http_client_errors_total", + Help: "The total number of HTTP requests served with 4xx status code", + }) + + totalHTTPServerErrors = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sftpgo_http_server_errors_total", + Help: "The total number of HTTP requests served with 5xx status code", + }) +) + +// TransferCompleted update metrics after an upload or a download +func TransferCompleted(bytesSent, bytesReceived int64, transferKind int, err error) { + if transferKind == 0 { + // upload + if err == nil { + totalUploads.Inc() + totalUploadSize.Add(float64(bytesReceived)) + } else { + totalUploadErrors.Inc() + } + } else { + // download + if err == nil { + totalDownloads.Inc() + totalDownloadSize.Add(float64(bytesSent)) + } else { + totalDownloadErrors.Inc() + } + } +} + +// UpdateDataProviderAvailability updates the metric for the data provider availability +func UpdateDataProviderAvailability(err error) { + if err == nil { + dataproviderAvailability.Set(1) + } else { + dataproviderAvailability.Set(0) + } +} + +// AddLoginAttempt increments the metrics for login attempts +func AddLoginAttempt(withKey bool) { + totalLoginAttempts.Inc() + if withKey { + totalKeyLoginAttempts.Inc() + } else { + totalPasswordLoginAttempts.Inc() + } +} + +// AddLoginResult increments the metrics for login results +func AddLoginResult(withKey bool, err error) { + if err == nil { + totalLoginOK.Inc() + if withKey { + totalKeyLoginOK.Inc() + } else { + totalPasswordLoginOK.Inc() + } + } else { + totalLoginFailed.Inc() + if withKey { + totalKeyLoginFailed.Inc() + } else { + totalPasswordLoginFailed.Inc() + } + } +} + +// HTTPRequestServed increments the metrics for HTTP requests +func HTTPRequestServed(status int) { + totalHTTPRequests.Inc() + if status >= 200 && status < 300 { + totalHTTPOK.Inc() + } else if status >= 400 && status < 500 { + totalHTTPClientErrors.Inc() + } else if status >= 500 { + totalHTTPServerErrors.Inc() + } +} + +// UpdateActiveConnectionsSize sets the metric for active connections +func UpdateActiveConnectionsSize(size int) { + activeConnections.Set(float64(size)) +} diff --git a/sftpd/server.go b/sftpd/server.go index 353d9a6d..132a19fd 100644 --- a/sftpd/server.go +++ b/sftpd/server.go @@ -21,6 +21,7 @@ import ( "github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/logger" + "github.com/drakkan/sftpgo/metrics" "github.com/drakkan/sftpgo/utils" "github.com/pkg/sftp" "golang.org/x/crypto/ssh" @@ -376,21 +377,27 @@ func (c Configuration) validatePublicKeyCredentials(conn ssh.ConnMetadata, pubKe var err error var user dataprovider.User var keyID string + var sshPerm *ssh.Permissions + metrics.AddLoginAttempt(true) if user, keyID, err = dataprovider.CheckUserAndPubKey(dataProvider, conn.User(), pubKey); err == nil { - return loginUser(user, "public_key:"+keyID) + sshPerm, err = loginUser(user, "public_key:"+keyID) } - return nil, err + metrics.AddLoginResult(true, err) + return sshPerm, err } func (c Configuration) validatePasswordCredentials(conn ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { var err error var user dataprovider.User + var sshPerm *ssh.Permissions + metrics.AddLoginAttempt(false) if user, err = dataprovider.CheckUserAndPass(dataProvider, conn.User(), string(pass)); err == nil { - return loginUser(user, "password") + sshPerm, err = loginUser(user, "password") } - return nil, err + metrics.AddLoginResult(false, err) + return sshPerm, err } // Generates a private key that will be used by the SFTP server. diff --git a/sftpd/sftpd.go b/sftpd/sftpd.go index 68908d83..7794f778 100644 --- a/sftpd/sftpd.go +++ b/sftpd/sftpd.go @@ -15,6 +15,7 @@ import ( "github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/logger" + "github.com/drakkan/sftpgo/metrics" "github.com/drakkan/sftpgo/utils" ) @@ -265,6 +266,7 @@ func addConnection(id string, c Connection) { mutex.Lock() defer mutex.Unlock() openConnections[id] = c + metrics.UpdateActiveConnectionsSize(len(openConnections)) c.Log(logger.LevelDebug, logSender, "connection added, num open connections: %v", len(openConnections)) } @@ -273,6 +275,7 @@ func removeConnection(id string) { defer mutex.Unlock() c := openConnections[id] delete(openConnections, id) + metrics.UpdateActiveConnectionsSize(len(openConnections)) c.Log(logger.LevelDebug, logSender, "connection removed, num open connections: %v", len(openConnections)) } diff --git a/sftpd/transfer.go b/sftpd/transfer.go index 58c897d3..51c68875 100644 --- a/sftpd/transfer.go +++ b/sftpd/transfer.go @@ -6,6 +6,7 @@ import ( "github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/logger" + "github.com/drakkan/sftpgo/metrics" ) const ( @@ -105,6 +106,7 @@ func (t *Transfer) Close() error { executeAction(operationUpload, t.user.Username, t.path, "") } } + metrics.TransferCompleted(t.bytesSent, t.bytesReceived, t.transferType, t.transferError) removeTransfer(t) if t.transferType == transferUpload && (numFiles != 0 || t.bytesReceived > 0) { dataprovider.UpdateUserQuota(dataProvider, t.user, numFiles, t.bytesReceived, false)