瀏覽代碼

sdk: add a logger interface

we are now ready to make the sdk a separate module

Signed-off-by: Nicola Murino <[email protected]>
Nicola Murino 3 年之前
父節點
當前提交
2912b2e92e

+ 0 - 1
common/protocol_test.go

@@ -34,7 +34,6 @@ import (
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
-	_ "github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/sdk"

+ 0 - 1
config/config_test.go

@@ -17,7 +17,6 @@ import (
 	"github.com/drakkan/sftpgo/v2/ftpd"
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpd"
-	_ "github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"

+ 2 - 1
dataprovider/dataprovider.go

@@ -52,6 +52,7 @@ import (
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk/kms"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin"
+	sdkutil "github.com/drakkan/sftpgo/v2/sdk/util"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/vfs"
 )
@@ -2889,7 +2890,7 @@ func getExternalAuthResponse(username, password, pkey, keyboardInteractive, ip,
 	var tlsCert string
 	if cert != nil {
 		var err error
-		tlsCert, err = util.EncodeTLSCertToPem(cert)
+		tlsCert, err = sdkutil.EncodeTLSCertToPem(cert)
 		if err != nil {
 			return nil, err
 		}

+ 0 - 1
ftpd/ftpd_test.go

@@ -32,7 +32,6 @@ import (
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/ftpd"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
-	_ "github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/sdk"

+ 0 - 4
go.sum

@@ -140,8 +140,6 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
 github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
-github.com/aws/aws-sdk-go v1.42.25 h1:BbdvHAi+t9LRiaYUyd53noq9jcaAcfzOhSVbKfr6Avs=
-github.com/aws/aws-sdk-go v1.42.25/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs=
 github.com/aws/aws-sdk-go v1.42.26 h1:3+GcxzyI+kvqoASDNeeLZfqGkvyNMrE9IDuErxPRtCA=
 github.com/aws/aws-sdk-go v1.42.26/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs=
 github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4=
@@ -271,8 +269,6 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
 github.com/go-chi/chi/v5 v5.0.4/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
-github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
-github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
 github.com/go-chi/chi/v5 v5.0.8-0.20220103230436-7dbe9a0bd10f h1:6kLofhLkWj7lgCc+mvcVLnwhTzQYgL/yW/Y0e/JYwjg=
 github.com/go-chi/chi/v5 v5.0.8-0.20220103230436-7dbe9a0bd10f/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
 github.com/go-chi/jwtauth/v5 v5.0.2 h1:CSKtr+b6Jnfy5T27sMaiBPxaVE/bjnjS3ramFQ0526w=

+ 0 - 1
httpd/httpd_test.go

@@ -45,7 +45,6 @@ import (
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpd"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
-	_ "github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/sdk"

+ 2 - 1
httpd/server.go

@@ -25,6 +25,7 @@ import (
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/sdk"
+	sdklogger "github.com/drakkan/sftpgo/v2/sdk/logger"
 	"github.com/drakkan/sftpgo/v2/smtp"
 	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/version"
@@ -75,7 +76,7 @@ func (s *httpdServer) listenAndServe() error {
 		WriteTimeout:      60 * time.Second,
 		IdleTimeout:       60 * time.Second,
 		MaxHeaderBytes:    1 << 16, // 64KB
-		ErrorLog:          log.New(&logger.StdLoggerWrapper{Sender: logSender}, "", 0),
+		ErrorLog:          log.New(&sdklogger.StdLoggerWrapper{Sender: logSender}, "", 0),
 	}
 	if certMgr != nil && s.binding.EnableHTTPS {
 		config := &tls.Config{

+ 0 - 2
kms/kms.go

@@ -1,2 +0,0 @@
-// Package kms provides built-in Key Management Services support
-package kms

+ 44 - 17
logger/logger.go

@@ -16,8 +16,11 @@ import (
 	"time"
 
 	ftpserverlog "github.com/fclairamb/go-log"
+	"github.com/hashicorp/go-hclog"
 	"github.com/rs/zerolog"
 	lumberjack "gopkg.in/natefinch/lumberjack.v2"
+
+	sdklogger "github.com/drakkan/sftpgo/v2/sdk/logger"
 )
 
 const (
@@ -41,22 +44,25 @@ var (
 	rollingLogger *lumberjack.Logger
 )
 
-// StdLoggerWrapper is a wrapper for standard logger compatibility
-type StdLoggerWrapper struct {
-	Sender string
+type logWrapper struct{}
+
+// LogWithKeyVals logs at the specified level for the specified sender adding the specified key vals
+func (l *logWrapper) LogWithKeyVals(level hclog.Level, sender, msg string, args ...interface{}) {
+	LogWithKeyVals(level, sender, msg, args...)
 }
 
-// Write implements the io.Writer interface. This is useful to set as a writer
-// for the standard library log.
-func (l *StdLoggerWrapper) Write(p []byte) (n int, err error) {
-	n = len(p)
-	if n > 0 && p[n-1] == '\n' {
-		// Trim CR added by stdlog.
-		p = p[0 : n-1]
+// Log logs at the specified level for the specified sender
+func (l *logWrapper) Log(level hclog.Level, sender, format string, v ...interface{}) {
+	switch level {
+	case hclog.Info:
+		Log(LevelInfo, sender, "", format, v...)
+	case hclog.Warn:
+		Log(LevelWarn, sender, "", format, v...)
+	case hclog.Error:
+		Log(LevelError, sender, "", format, v...)
+	default:
+		Log(LevelDebug, sender, "", format, v...)
 	}
-
-	Log(LevelError, l.Sender, "", string(p))
-	return
 }
 
 // LeveledLogger is a logger that accepts a message string and a variadic number of key-value pairs
@@ -176,6 +182,7 @@ func InitLogger(logFilePath string, logMaxSize int, logMaxBackups int, logMaxAge
 		consoleLogger = zerolog.Nop()
 	}
 	logger = logger.Level(level)
+	sdklogger.SetLogger(&logWrapper{})
 }
 
 // InitStdErrLogger configures the logger to write to stderr
@@ -184,6 +191,7 @@ func InitStdErrLogger(level zerolog.Level) {
 		output: os.Stderr,
 	}).Level(level)
 	consoleLogger = zerolog.Nop()
+	sdklogger.SetLogger(&logWrapper{})
 }
 
 // DisableLogger disable the main logger.
@@ -191,6 +199,7 @@ func InitStdErrLogger(level zerolog.Level) {
 func DisableLogger() {
 	logger = zerolog.Nop()
 	rollingLogger = nil
+	sdklogger.DisableLogger()
 }
 
 // EnableConsoleLogger enables the console logger
@@ -210,6 +219,24 @@ func RotateLogFile() error {
 	return errors.New("logging to file is disabled")
 }
 
+// LogWithKeyVals logs at the specified level for the specified sender adding the specified key vals
+func LogWithKeyVals(level hclog.Level, sender, msg string, args ...interface{}) {
+	var ev *zerolog.Event
+	switch level {
+	case hclog.Info:
+		ev = logger.Info()
+	case hclog.Warn:
+		ev = logger.Warn()
+	case hclog.Error:
+		ev = logger.Error()
+	default:
+		ev = logger.Debug()
+	}
+	ev.Timestamp().Str("sender", sender)
+	addKeysAndValues(ev, args...)
+	ev.Msg(msg)
+}
+
 // Log logs at the specified level for the specified sender
 func Log(level LogLevel, sender string, connectionID string, format string, v ...interface{}) {
 	var ev *zerolog.Event
@@ -231,22 +258,22 @@ func Log(level LogLevel, sender string, connectionID string, format string, v ..
 }
 
 // Debug logs at debug level for the specified sender
-func Debug(sender string, connectionID string, format string, v ...interface{}) {
+func Debug(sender, connectionID, format string, v ...interface{}) {
 	Log(LevelDebug, sender, connectionID, format, v...)
 }
 
 // Info logs at info level for the specified sender
-func Info(sender string, connectionID string, format string, v ...interface{}) {
+func Info(sender, connectionID, format string, v ...interface{}) {
 	Log(LevelInfo, sender, connectionID, format, v...)
 }
 
 // Warn logs at warn level for the specified sender
-func Warn(sender string, connectionID string, format string, v ...interface{}) {
+func Warn(sender, connectionID, format string, v ...interface{}) {
 	Log(LevelWarn, sender, connectionID, format, v...)
 }
 
 // Error logs at error level for the specified sender
-func Error(sender string, connectionID string, format string, v ...interface{}) {
+func Error(sender, connectionID, format string, v ...interface{}) {
 	Log(LevelError, sender, connectionID, format, v...)
 }
 

+ 0 - 1
main.go

@@ -11,7 +11,6 @@ import (
 	"go.uber.org/automaxprocs/maxprocs"
 
 	"github.com/drakkan/sftpgo/v2/cmd"
-	_ "github.com/drakkan/sftpgo/v2/kms"
 )
 
 func main() {

+ 13 - 15
kms/builtin.go → sdk/kms/builtin.go

@@ -8,8 +8,6 @@ import (
 	"encoding/hex"
 	"errors"
 	"io"
-
-	sdkkms "github.com/drakkan/sftpgo/v2/sdk/kms"
 )
 
 var (
@@ -17,14 +15,14 @@ var (
 )
 
 type builtinSecret struct {
-	sdkkms.BaseSecret
+	BaseSecret
 }
 
 func init() {
-	sdkkms.RegisterSecretProvider(sdkkms.SchemeBuiltin, sdkkms.SecretStatusAES256GCM, newBuiltinSecret)
+	RegisterSecretProvider(SchemeBuiltin, SecretStatusAES256GCM, newBuiltinSecret)
 }
 
-func newBuiltinSecret(base sdkkms.BaseSecret, url, masterKey string) sdkkms.SecretProvider {
+func newBuiltinSecret(base BaseSecret, url, masterKey string) SecretProvider {
 	return &builtinSecret{
 		BaseSecret: base,
 	}
@@ -35,7 +33,7 @@ func (s *builtinSecret) Name() string {
 }
 
 func (s *builtinSecret) IsEncrypted() bool {
-	return s.Status == sdkkms.SecretStatusAES256GCM
+	return s.Status == SecretStatusAES256GCM
 }
 
 func (s *builtinSecret) deriveKey(key []byte) []byte {
@@ -51,10 +49,10 @@ func (s *builtinSecret) deriveKey(key []byte) []byte {
 
 func (s *builtinSecret) Encrypt() error {
 	if s.Payload == "" {
-		return sdkkms.ErrInvalidSecret
+		return ErrInvalidSecret
 	}
 	switch s.Status {
-	case sdkkms.SecretStatusPlain:
+	case SecretStatusPlain:
 		key := make([]byte, 32)
 		if _, err := io.ReadFull(rand.Reader, key); err != nil {
 			return err
@@ -78,16 +76,16 @@ func (s *builtinSecret) Encrypt() error {
 		ciphertext := gcm.Seal(nonce, nonce, []byte(s.Payload), aad)
 		s.Key = hex.EncodeToString(key)
 		s.Payload = hex.EncodeToString(ciphertext)
-		s.Status = sdkkms.SecretStatusAES256GCM
+		s.Status = SecretStatusAES256GCM
 		return nil
 	default:
-		return sdkkms.ErrWrongSecretStatus
+		return ErrWrongSecretStatus
 	}
 }
 
 func (s *builtinSecret) Decrypt() error {
 	switch s.Status {
-	case sdkkms.SecretStatusAES256GCM:
+	case SecretStatusAES256GCM:
 		encrypted, err := hex.DecodeString(s.Payload)
 		if err != nil {
 			return err
@@ -117,18 +115,18 @@ func (s *builtinSecret) Decrypt() error {
 		if err != nil {
 			return err
 		}
-		s.Status = sdkkms.SecretStatusPlain
+		s.Status = SecretStatusPlain
 		s.Payload = string(plaintext)
 		s.Key = ""
 		s.AdditionalData = ""
 		return nil
 	default:
-		return sdkkms.ErrWrongSecretStatus
+		return ErrWrongSecretStatus
 	}
 }
 
-func (s *builtinSecret) Clone() sdkkms.SecretProvider {
-	baseSecret := sdkkms.BaseSecret{
+func (s *builtinSecret) Clone() SecretProvider {
+	baseSecret := BaseSecret{
 		Status:         s.Status,
 		Payload:        s.Payload,
 		Key:            s.Key,

+ 6 - 6
sdk/kms/kms.go

@@ -8,8 +8,8 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/drakkan/sftpgo/v2/logger"
-	"github.com/drakkan/sftpgo/v2/util"
+	"github.com/drakkan/sftpgo/v2/sdk/logger"
+	"github.com/drakkan/sftpgo/v2/sdk/util"
 )
 
 // SecretProvider defines the interface for a KMS secrets provider
@@ -141,7 +141,7 @@ func (c *Configuration) Initialize() error {
 		config.Secrets.URL = SchemeLocal + "://"
 	}
 	for k, v := range secretProviders {
-		logger.Debug(logSender, "", "secret provider registered for scheme: %#v, encrypted status: %#v",
+		logger.Debug(logSender, "secret provider registered for scheme: %#v, encrypted status: %#v",
 			k, v.encryptedStatus)
 	}
 	return nil
@@ -166,8 +166,8 @@ func (c *Configuration) getSecretProvider(base BaseSecret) SecretProvider {
 		}
 	}
 	// we assume that SchemeLocal is always registered
-	logger.Warn(logSender, "", "no secret provider registered for URL %v, fallback to local provider", c.Secrets.URL)
-	return secretProviders[SchemeLocal].newFn(base, c.Secrets.URL, c.Secrets.masterKey)
+	logger.Warn(logSender, "no secret provider registered for URL %v, fallback to local provider", c.Secrets.URL)
+	return NewLocalSecret(base, c.Secrets.URL, c.Secrets.masterKey)
 }
 
 // Secret defines the struct used to store confidential data
@@ -217,7 +217,7 @@ func (s *Secret) UnmarshalJSON(data []byte) error {
 			return nil
 		}
 	}
-	logger.Debug(logSender, "", "no provider registered for status %#v", baseSecret.Status)
+	logger.Debug(logSender, "no provider registered for status %#v", baseSecret.Status)
 
 	return ErrInvalidSecret
 }

+ 12 - 14
kms/local.go → sdk/kms/local.go

@@ -9,21 +9,19 @@ import (
 
 	"gocloud.dev/secrets/localsecrets"
 	"golang.org/x/crypto/hkdf"
-
-	sdkkms "github.com/drakkan/sftpgo/v2/sdk/kms"
 )
 
 func init() {
-	sdkkms.RegisterSecretProvider(sdkkms.SchemeLocal, sdkkms.SecretStatusSecretBox, NewLocalSecret)
+	RegisterSecretProvider(SchemeLocal, SecretStatusSecretBox, NewLocalSecret)
 }
 
 type localSecret struct {
-	sdkkms.BaseSecret
+	BaseSecret
 	masterKey string
 }
 
 // NewLocalSecret returns a SecretProvider that use a locally provided symmetric key
-func NewLocalSecret(base sdkkms.BaseSecret, url, masterKey string) sdkkms.SecretProvider {
+func NewLocalSecret(base BaseSecret, url, masterKey string) SecretProvider {
 	return &localSecret{
 		BaseSecret: base,
 		masterKey:  masterKey,
@@ -35,15 +33,15 @@ func (s *localSecret) Name() string {
 }
 
 func (s *localSecret) IsEncrypted() bool {
-	return s.Status == sdkkms.SecretStatusSecretBox
+	return s.Status == SecretStatusSecretBox
 }
 
 func (s *localSecret) Encrypt() error {
-	if s.Status != sdkkms.SecretStatusPlain {
-		return sdkkms.ErrWrongSecretStatus
+	if s.Status != SecretStatusPlain {
+		return ErrWrongSecretStatus
 	}
 	if s.Payload == "" {
-		return sdkkms.ErrInvalidSecret
+		return ErrInvalidSecret
 	}
 	secretKey, err := localsecrets.NewRandomKey()
 	if err != nil {
@@ -62,14 +60,14 @@ func (s *localSecret) Encrypt() error {
 	}
 	s.Key = hex.EncodeToString(secretKey[:])
 	s.Payload = base64.StdEncoding.EncodeToString(ciphertext)
-	s.Status = sdkkms.SecretStatusSecretBox
+	s.Status = SecretStatusSecretBox
 	s.Mode = s.getEncryptionMode()
 	return nil
 }
 
 func (s *localSecret) Decrypt() error {
 	if !s.IsEncrypted() {
-		return sdkkms.ErrWrongSecretStatus
+		return ErrWrongSecretStatus
 	}
 	encrypted, err := base64.StdEncoding.DecodeString(s.Payload)
 	if err != nil {
@@ -90,7 +88,7 @@ func (s *localSecret) Decrypt() error {
 	if err != nil {
 		return err
 	}
-	s.Status = sdkkms.SecretStatusPlain
+	s.Status = SecretStatusPlain
 	s.Payload = string(plaintext)
 	s.Key = ""
 	s.AdditionalData = ""
@@ -131,8 +129,8 @@ func (s *localSecret) getEncryptionMode() int {
 	return 1
 }
 
-func (s *localSecret) Clone() sdkkms.SecretProvider {
-	baseSecret := sdkkms.BaseSecret{
+func (s *localSecret) Clone() SecretProvider {
+	baseSecret := BaseSecret{
 		Status:         s.Status,
 		Payload:        s.Payload,
 		Key:            s.Key,

+ 19 - 15
logger/hclog_adapter.go → sdk/logger/hclog_adapter.go

@@ -5,7 +5,6 @@ import (
 	"log"
 
 	"github.com/hashicorp/go-hclog"
-	"github.com/rs/zerolog"
 )
 
 // HCLogAdapter is an adapter for hclog.Logger
@@ -15,20 +14,7 @@ type HCLogAdapter struct {
 
 // Log emits a message and key/value pairs at a provided log level
 func (l *HCLogAdapter) Log(level hclog.Level, msg string, args ...interface{}) {
-	var ev *zerolog.Event
-	switch level {
-	case hclog.Info:
-		ev = logger.Info()
-	case hclog.Warn:
-		ev = logger.Warn()
-	case hclog.Error:
-		ev = logger.Error()
-	default:
-		ev = logger.Debug()
-	}
-	ev.Timestamp().Str("sender", l.Name())
-	addKeysAndValues(ev, args...)
-	ev.Msg(msg)
+	logger.LogWithKeyVals(level, l.Name(), msg, args...)
 }
 
 // Trace emits a message and key/value pairs at the TRACE level
@@ -75,3 +61,21 @@ func (l *HCLogAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Lo
 func (l *HCLogAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {
 	return &StdLoggerWrapper{Sender: l.Name()}
 }
+
+// StdLoggerWrapper is a wrapper for standard logger compatibility
+type StdLoggerWrapper struct {
+	Sender string
+}
+
+// Write implements the io.Writer interface. This is useful to set as a writer
+// for the standard library log.
+func (l *StdLoggerWrapper) Write(p []byte) (n int, err error) {
+	n = len(p)
+	if n > 0 && p[n-1] == '\n' {
+		// Trim CR added by stdlog.
+		p = p[0 : n-1]
+	}
+
+	logger.Log(hclog.Error, l.Sender, "", string(p))
+	return
+}

+ 56 - 0
sdk/logger/logger.go

@@ -0,0 +1,56 @@
+// Package logger provides logging capabilities.
+package logger
+
+import "github.com/hashicorp/go-hclog"
+
+var (
+	logger Logger
+)
+
+func init() {
+	DisableLogger()
+}
+
+// Logger interface
+type Logger interface {
+	// LogWithKeyVals logs at the specified level for the specified sender adding the specified key vals
+	LogWithKeyVals(level hclog.Level, sender, msg string, args ...interface{})
+	// Log logs at the specified level for the specified sender
+	Log(level hclog.Level, sender, format string, v ...interface{})
+}
+
+// SetLogger sets the specified logger
+func SetLogger(l Logger) {
+	logger = l
+}
+
+// DisableLogger disables logging
+func DisableLogger() {
+	logger = &noLogger{}
+}
+
+type noLogger struct{}
+
+func (*noLogger) LogWithKeyVals(level hclog.Level, sender, msg string, args ...interface{}) {}
+
+func (*noLogger) Log(level hclog.Level, sender, format string, v ...interface{}) {}
+
+// Debug logs at debug level for the specified sender
+func Debug(sender, format string, v ...interface{}) {
+	logger.Log(hclog.Debug, sender, format, v...)
+}
+
+// Info logs at info level for the specified sender
+func Info(sender, format string, v ...interface{}) {
+	logger.Log(hclog.Info, sender, format, v...)
+}
+
+// Warn logs at warn level for the specified sender
+func Warn(sender, format string, v ...interface{}) {
+	logger.Log(hclog.Warn, sender, format, v...)
+}
+
+// Error logs at error level for the specified sender
+func Error(sender, format string, v ...interface{}) {
+	logger.Log(hclog.Error, sender, format, v...)
+}

+ 1 - 1
sdk/plugin/auth.go

@@ -9,7 +9,7 @@ import (
 	"github.com/hashicorp/go-hclog"
 	"github.com/hashicorp/go-plugin"
 
-	"github.com/drakkan/sftpgo/v2/logger"
+	"github.com/drakkan/sftpgo/v2/sdk/logger"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/auth"
 )
 

+ 2 - 2
sdk/plugin/kms.go

@@ -9,10 +9,10 @@ import (
 	"github.com/hashicorp/go-hclog"
 	"github.com/hashicorp/go-plugin"
 
-	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/sdk/kms"
+	"github.com/drakkan/sftpgo/v2/sdk/logger"
 	kmsplugin "github.com/drakkan/sftpgo/v2/sdk/plugin/kms"
-	"github.com/drakkan/sftpgo/v2/util"
+	"github.com/drakkan/sftpgo/v2/sdk/util"
 )
 
 var (

+ 1 - 1
sdk/plugin/metadata.go

@@ -8,7 +8,7 @@ import (
 	"github.com/hashicorp/go-hclog"
 	"github.com/hashicorp/go-plugin"
 
-	"github.com/drakkan/sftpgo/v2/logger"
+	"github.com/drakkan/sftpgo/v2/sdk/logger"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/metadata"
 )
 

+ 2 - 2
sdk/plugin/notifier.go

@@ -10,9 +10,9 @@ import (
 	"github.com/hashicorp/go-hclog"
 	"github.com/hashicorp/go-plugin"
 
-	"github.com/drakkan/sftpgo/v2/logger"
+	"github.com/drakkan/sftpgo/v2/sdk/logger"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/notifier"
-	"github.com/drakkan/sftpgo/v2/util"
+	"github.com/drakkan/sftpgo/v2/sdk/util"
 )
 
 // NotifierConfig defines configuration parameters for notifiers plugins

+ 23 - 23
sdk/plugin/plugin.go

@@ -11,14 +11,14 @@ import (
 
 	"github.com/hashicorp/go-hclog"
 
-	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/sdk/kms"
+	"github.com/drakkan/sftpgo/v2/sdk/logger"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/auth"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/eventsearcher"
 	kmsplugin "github.com/drakkan/sftpgo/v2/sdk/plugin/kms"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/metadata"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/notifier"
-	"github.com/drakkan/sftpgo/v2/util"
+	"github.com/drakkan/sftpgo/v2/sdk/util"
 )
 
 const (
@@ -100,7 +100,7 @@ type Manager struct {
 
 // Initialize initializes the configured plugins
 func Initialize(configs []Config, logVerbose bool) error {
-	logger.Debug(logSender, "", "initialize")
+	logger.Debug(logSender, "initialize")
 	Handler = Manager{
 		Configs:    configs,
 		done:       make(chan bool),
@@ -135,7 +135,7 @@ func Initialize(configs []Config, logVerbose bool) error {
 			kmsID++
 			kms.RegisterSecretProvider(config.KMSOptions.Scheme, config.KMSOptions.EncryptedStatus,
 				Handler.Configs[idx].newKMSPluginSecretProvider)
-			logger.Debug(logSender, "", "registered secret provider for scheme: %v, encrypted status: %v",
+			logger.Debug(logSender, "registered secret provider for scheme: %v, encrypted status: %v",
 				config.KMSOptions.Scheme, config.KMSOptions.EncryptedStatus)
 		case auth.PluginName:
 			plugin, err := newAuthPlugin(config)
@@ -357,7 +357,7 @@ func (m *Manager) Authenticate(username, password, ip, protocol string, pkey str
 	case AuthScopeTLSCertificate:
 		cert, err := util.EncodeTLSCertToPem(tlsCert)
 		if err != nil {
-			logger.Warn(logSender, "", "unable to encode tls certificate to pem: %v", err)
+			logger.Error(logSender, "unable to encode tls certificate to pem: %v", err)
 			return nil, fmt.Errorf("unable to encode tls cert to pem: %w", err)
 		}
 		return m.checkUserAndTLSCert(username, cert, ip, protocol, userAsJSON)
@@ -520,10 +520,10 @@ func (m *Manager) restartNotifierPlugin(config Config, idx int) {
 	if atomic.LoadInt32(&m.closed) == 1 {
 		return
 	}
-	logger.Info(logSender, "", "try to restart crashed notifier plugin %#v, idx: %v", config.Cmd, idx)
+	logger.Info(logSender, "try to restart crashed notifier plugin %#v, idx: %v", config.Cmd, idx)
 	plugin, err := newNotifierPlugin(config)
 	if err != nil {
-		logger.Warn(logSender, "", "unable to restart notifier plugin %#v, err: %v", config.Cmd, err)
+		logger.Error(logSender, "unable to restart notifier plugin %#v, err: %v", config.Cmd, err)
 		return
 	}
 
@@ -538,10 +538,10 @@ func (m *Manager) restartKMSPlugin(config Config, idx int) {
 	if atomic.LoadInt32(&m.closed) == 1 {
 		return
 	}
-	logger.Info(logSender, "", "try to restart crashed kms plugin %#v, idx: %v", config.Cmd, idx)
+	logger.Info(logSender, "try to restart crashed kms plugin %#v, idx: %v", config.Cmd, idx)
 	plugin, err := newKMSPlugin(config)
 	if err != nil {
-		logger.Warn(logSender, "", "unable to restart kms plugin %#v, err: %v", config.Cmd, err)
+		logger.Error(logSender, "unable to restart kms plugin %#v, err: %v", config.Cmd, err)
 		return
 	}
 
@@ -554,10 +554,10 @@ func (m *Manager) restartAuthPlugin(config Config, idx int) {
 	if atomic.LoadInt32(&m.closed) == 1 {
 		return
 	}
-	logger.Info(logSender, "", "try to restart crashed auth plugin %#v, idx: %v", config.Cmd, idx)
+	logger.Info(logSender, "try to restart crashed auth plugin %#v, idx: %v", config.Cmd, idx)
 	plugin, err := newAuthPlugin(config)
 	if err != nil {
-		logger.Warn(logSender, "", "unable to restart auth plugin %#v, err: %v", config.Cmd, err)
+		logger.Error(logSender, "unable to restart auth plugin %#v, err: %v", config.Cmd, err)
 		return
 	}
 
@@ -570,10 +570,10 @@ func (m *Manager) restartSearcherPlugin(config Config) {
 	if atomic.LoadInt32(&m.closed) == 1 {
 		return
 	}
-	logger.Info(logSender, "", "try to restart crashed searcher plugin %#v", config.Cmd)
+	logger.Info(logSender, "try to restart crashed searcher plugin %#v", config.Cmd)
 	plugin, err := newSearcherPlugin(config)
 	if err != nil {
-		logger.Warn(logSender, "", "unable to restart searcher plugin %#v, err: %v", config.Cmd, err)
+		logger.Error(logSender, "unable to restart searcher plugin %#v, err: %v", config.Cmd, err)
 		return
 	}
 
@@ -586,10 +586,10 @@ func (m *Manager) restartMetadaterPlugin(config Config) {
 	if atomic.LoadInt32(&m.closed) == 1 {
 		return
 	}
-	logger.Info(logSender, "", "try to restart crashed metadater plugin %#v", config.Cmd)
+	logger.Info(logSender, "try to restart crashed metadater plugin %#v", config.Cmd)
 	plugin, err := newMetadaterPlugin(config)
 	if err != nil {
-		logger.Warn(logSender, "", "unable to restart metadater plugin %#v, err: %v", config.Cmd, err)
+		logger.Error(logSender, "unable to restart metadater plugin %#v, err: %v", config.Cmd, err)
 		return
 	}
 
@@ -600,40 +600,40 @@ func (m *Manager) restartMetadaterPlugin(config Config) {
 
 // Cleanup releases all the active plugins
 func (m *Manager) Cleanup() {
-	logger.Debug(logSender, "", "cleanup")
+	logger.Debug(logSender, "cleanup")
 	atomic.StoreInt32(&m.closed, 1)
 	close(m.done)
 	m.notifLock.Lock()
 	for _, n := range m.notifiers {
-		logger.Debug(logSender, "", "cleanup notifier plugin %v", n.config.Cmd)
+		logger.Debug(logSender, "cleanup notifier plugin %v", n.config.Cmd)
 		n.cleanup()
 	}
 	m.notifLock.Unlock()
 
 	m.kmsLock.Lock()
 	for _, k := range m.kms {
-		logger.Debug(logSender, "", "cleanup kms plugin %v", k.config.Cmd)
+		logger.Debug(logSender, "cleanup kms plugin %v", k.config.Cmd)
 		k.cleanup()
 	}
 	m.kmsLock.Unlock()
 
 	m.authLock.Lock()
 	for _, a := range m.auths {
-		logger.Debug(logSender, "", "cleanup auth plugin %v", a.config.Cmd)
+		logger.Debug(logSender, "cleanup auth plugin %v", a.config.Cmd)
 		a.cleanup()
 	}
 	m.authLock.Unlock()
 
 	if m.hasSearcher {
 		m.searcherLock.Lock()
-		logger.Debug(logSender, "", "cleanup searcher plugin %v", m.searcher.config.Cmd)
+		logger.Debug(logSender, "cleanup searcher plugin %v", m.searcher.config.Cmd)
 		m.searcher.cleanup()
 		m.searcherLock.Unlock()
 	}
 
 	if m.hasMetadater {
 		m.metadaterLock.Lock()
-		logger.Debug(logSender, "", "cleanup metadater plugin %v", m.metadater.config.Cmd)
+		logger.Debug(logSender, "cleanup metadater plugin %v", m.metadater.config.Cmd)
 		m.metadater.cleanup()
 		m.metadaterLock.Unlock()
 	}
@@ -648,14 +648,14 @@ func setLogLevel(logVerbose bool) {
 }
 
 func startCheckTicker() {
-	logger.Debug(logSender, "", "start plugins checker")
+	logger.Debug(logSender, "start plugins checker")
 	checker := time.NewTicker(30 * time.Second)
 
 	go func() {
 		for {
 			select {
 			case <-Handler.done:
-				logger.Debug(logSender, "", "handler done, stop plugins checker")
+				logger.Debug(logSender, "handler done, stop plugins checker")
 				checker.Stop()
 				return
 			case <-checker.C:

+ 1 - 1
sdk/plugin/searcher.go

@@ -8,7 +8,7 @@ import (
 	"github.com/hashicorp/go-hclog"
 	"github.com/hashicorp/go-plugin"
 
-	"github.com/drakkan/sftpgo/v2/logger"
+	"github.com/drakkan/sftpgo/v2/sdk/logger"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/eventsearcher"
 )
 

+ 3 - 3
sdk/plugin/util.go

@@ -3,7 +3,7 @@ package plugin
 import (
 	"github.com/shirou/gopsutil/v3/process"
 
-	"github.com/drakkan/sftpgo/v2/logger"
+	"github.com/drakkan/sftpgo/v2/sdk/logger"
 )
 
 func killProcess(processPath string) {
@@ -16,10 +16,10 @@ func killProcess(processPath string) {
 		if err == nil {
 			if cmdLine == processPath {
 				err = p.Kill()
-				logger.Debug(logSender, "", "killed process %v, pid %v, err %v", cmdLine, p.Pid, err)
+				logger.Debug(logSender, "killed process %v, pid %v, err %v", cmdLine, p.Pid, err)
 				return
 			}
 		}
 	}
-	logger.Debug(logSender, "", "no match for plugin process %v", processPath)
+	logger.Debug(logSender, "no match for plugin process %v", processPath)
 }

+ 1 - 1
sdk/user.go

@@ -4,7 +4,7 @@ import (
 	"strings"
 
 	"github.com/drakkan/sftpgo/v2/sdk/kms"
-	"github.com/drakkan/sftpgo/v2/util"
+	"github.com/drakkan/sftpgo/v2/sdk/util"
 )
 
 // Web Client/user REST API restrictions

+ 31 - 0
sdk/util/util.go

@@ -0,0 +1,31 @@
+// Package util provides some common utility methods
+package util
+
+import (
+	"crypto/x509"
+	"encoding/pem"
+	"errors"
+)
+
+// IsStringInSlice searches a string in a slice and returns true if the string is found
+func IsStringInSlice(obj string, list []string) bool {
+	for i := 0; i < len(list); i++ {
+		if list[i] == obj {
+			return true
+		}
+	}
+	return false
+}
+
+// EncodeTLSCertToPem returns the specified certificate PEM encoded.
+// This can be verified using openssl x509 -in cert.crt  -text -noout
+func EncodeTLSCertToPem(tlsCert *x509.Certificate) (string, error) {
+	if len(tlsCert.Raw) == 0 {
+		return "", errors.New("invalid x509 certificate, no der contents")
+	}
+	publicKeyBlock := pem.Block{
+		Type:  "CERTIFICATE",
+		Bytes: tlsCert.Raw,
+	}
+	return string(pem.EncodeToMemory(&publicKeyBlock)), nil
+}

+ 0 - 1
sftpd/sftpd_test.go

@@ -42,7 +42,6 @@ import (
 	"github.com/drakkan/sftpgo/v2/config"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
-	_ "github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/mfa"
 	"github.com/drakkan/sftpgo/v2/sdk"

+ 2 - 1
telemetry/telemetry.go

@@ -16,6 +16,7 @@ import (
 
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/logger"
+	sdklogger "github.com/drakkan/sftpgo/v2/sdk/logger"
 	"github.com/drakkan/sftpgo/v2/util"
 )
 
@@ -95,7 +96,7 @@ func (c Conf) Initialize(configDir string) error {
 		WriteTimeout:      60 * time.Second,
 		IdleTimeout:       60 * time.Second,
 		MaxHeaderBytes:    1 << 14, // 16KB
-		ErrorLog:          log.New(&logger.StdLoggerWrapper{Sender: logSender}, "", 0),
+		ErrorLog:          log.New(&sdklogger.StdLoggerWrapper{Sender: logSender}, "", 0),
 	}
 	if certificateFile != "" && certificateKeyFile != "" {
 		certMgr, err = common.NewCertManager(certificateFile, certificateKeyFile, configDir, logSender)

+ 2 - 2
tests/eventsearcher/go.sum

@@ -126,7 +126,7 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
 github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
-github.com/aws/aws-sdk-go v1.42.25/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs=
+github.com/aws/aws-sdk-go v1.42.26/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs=
 github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4=
 github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
 github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY=
@@ -229,7 +229,7 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
 github.com/go-chi/chi/v5 v5.0.4/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
-github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
+github.com/go-chi/chi/v5 v5.0.8-0.20220103230436-7dbe9a0bd10f/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
 github.com/go-chi/jwtauth/v5 v5.0.2/go.mod h1:TeA7vmPe3uYThvHw8O8W13HOOpOd4MTgToxL41gZyjs=
 github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=

+ 0 - 14
util/util.go

@@ -11,7 +11,6 @@ import (
 	"crypto/tls"
 	"crypto/x509"
 	"encoding/pem"
-	"errors"
 	"fmt"
 	"html/template"
 	"io"
@@ -423,19 +422,6 @@ func GetTLSCiphersFromNames(cipherNames []string) []uint16 {
 	return ciphers
 }
 
-// EncodeTLSCertToPem returns the specified certificate PEM encoded.
-// This can be verified using openssl x509 -in cert.crt  -text -noout
-func EncodeTLSCertToPem(tlsCert *x509.Certificate) (string, error) {
-	if len(tlsCert.Raw) == 0 {
-		return "", errors.New("invalid x509 certificate, no der contents")
-	}
-	publicKeyBlock := pem.Block{
-		Type:  "CERTIFICATE",
-		Bytes: tlsCert.Raw,
-	}
-	return string(pem.EncodeToMemory(&publicKeyBlock)), nil
-}
-
 // CheckTCP4Port quits the app if bind on the given IPv4 port fails.
 // This is a ugly hack to avoid to bind on an already used port.
 // It is required on Windows only. Upstream does not consider this

+ 2 - 1
webdavd/server.go

@@ -23,6 +23,7 @@ import (
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/metric"
+	sdklogger "github.com/drakkan/sftpgo/v2/sdk/logger"
 	"github.com/drakkan/sftpgo/v2/util"
 )
 
@@ -39,7 +40,7 @@ func (s *webDavServer) listenAndServe(compressor *middleware.Compressor) error {
 		WriteTimeout:      60 * time.Second,
 		IdleTimeout:       60 * time.Second,
 		MaxHeaderBytes:    1 << 16, // 64KB
-		ErrorLog:          log.New(&logger.StdLoggerWrapper{Sender: logSender}, "", 0),
+		ErrorLog:          log.New(&sdklogger.StdLoggerWrapper{Sender: logSender}, "", 0),
 	}
 	if s.config.Cors.Enabled {
 		c := cors.New(cors.Options{

+ 0 - 1
webdavd/webdavd_test.go

@@ -31,7 +31,6 @@ import (
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/httpclient"
 	"github.com/drakkan/sftpgo/v2/httpdtest"
-	_ "github.com/drakkan/sftpgo/v2/kms"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/sdk"
 	"github.com/drakkan/sftpgo/v2/sdk/kms"