Replace default console output with timestamped log messages
This commit is contained in:
parent
f0612c6422
commit
785a6182bf
6 changed files with 189 additions and 99 deletions
40
Readme.md
40
Readme.md
|
@ -21,6 +21,7 @@ It perfectly fits if you would like to give some people the possibility to uploa
|
|||
* [TLS](#tls)
|
||||
* [Behind a proxy](#behind-a-proxy)
|
||||
* [User management](#user-management)
|
||||
* [Logging](#logging)
|
||||
* [Live reload](#live-reload)
|
||||
- [Installation](#installation)
|
||||
* [Binary-Installation](#binary-installation)
|
||||
|
@ -98,16 +99,41 @@ The password must be in form of a BCrypt hash. You can generate one calling the
|
|||
|
||||
If a subdirectory is configured for a user, the user is jailed within it and can't see anything that exists outside of this directory. If no subdirectory is configured for an user, the user can see and modify all files within the base directory.
|
||||
|
||||
### Logging
|
||||
|
||||
You can enable / disable logging for the following operations:
|
||||
|
||||
- **C**reation of files or directories
|
||||
- **R**eading of files or directories
|
||||
- **U**pdating of files or directories
|
||||
- **D**eletion of files or directories
|
||||
|
||||
All logs are disabled per default until you will turn it on via the following config entries:
|
||||
|
||||
address: "127.0.0.1" # the bind address
|
||||
port: "8000" # the listening port
|
||||
dir: "/home/webdav" # the provided base directory
|
||||
log:
|
||||
create: true
|
||||
read: true
|
||||
update: true
|
||||
delete: true
|
||||
...
|
||||
|
||||
Be aware, that the log pattern of an attached tty differs from the log pattern of a detached tty.
|
||||
|
||||
Example of an attached tty:
|
||||
|
||||
INFO[0000] Server is starting and listening address=0.0.0.0 port=8000 security=none
|
||||
|
||||
Example of a detached tty:
|
||||
|
||||
time="2018-04-14T20:46:00+02:00" level=info msg="Server is starting and listening" address=0.0.0.0 port=8000 security=none
|
||||
|
||||
### Live reload
|
||||
|
||||
If you're editing the user section of the configuration to:
|
||||
There is no need to restart the server itself, if you're editing the user or log section of the configuration. The config file will be re-read and the application will update it's own configuration silently in background.
|
||||
|
||||
- Remove a user
|
||||
- Add a user
|
||||
- Add, remove or change a user's subdirectory
|
||||
- Update a users password
|
||||
|
||||
There is no need to restart the server itself. The config file will be re-read and the application will update it's own configuration silently in background.
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ package app
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
@ -16,9 +16,18 @@ type Config struct {
|
|||
Prefix string
|
||||
Dir string
|
||||
TLS *TLS
|
||||
Log Logging
|
||||
Users map[string]*UserInfo
|
||||
}
|
||||
|
||||
// Logging allows definition for logging each CRUD method.
|
||||
type Logging struct {
|
||||
Create bool
|
||||
Read bool
|
||||
Update bool
|
||||
Delete bool
|
||||
}
|
||||
|
||||
// TLS allows specification of a certificate and private key file.
|
||||
type TLS struct {
|
||||
CertFile string
|
||||
|
@ -75,14 +84,18 @@ func setDefaults() {
|
|||
viper.SetDefault("Prefix", "")
|
||||
viper.SetDefault("Dir", "/tmp")
|
||||
viper.SetDefault("TLS", nil)
|
||||
viper.SetDefault("Log.Create", false)
|
||||
viper.SetDefault("Log.Read", false)
|
||||
viper.SetDefault("Log.Update", false)
|
||||
viper.SetDefault("Log.Delete", false)
|
||||
}
|
||||
|
||||
func (cfg *Config) updateConfig(e fsnotify.Event) {
|
||||
fmt.Println("Config file changed:", e.Name)
|
||||
log.WithField("path", e.Name).Info("Config file changed")
|
||||
|
||||
file, err := os.Open(e.Name)
|
||||
if err != nil {
|
||||
fmt.Println("Error reloading config", e.Name)
|
||||
log.WithField("path", e.Name).Warn("Error reloading config")
|
||||
}
|
||||
|
||||
var updatedCfg = &Config{}
|
||||
|
@ -91,30 +104,50 @@ func (cfg *Config) updateConfig(e fsnotify.Event) {
|
|||
|
||||
for username := range cfg.Users {
|
||||
if updatedCfg.Users[username] == nil {
|
||||
fmt.Printf("Removed User from configuration: %s\n", username)
|
||||
log.WithField("user", username).Info("Removed User from configuration")
|
||||
cfg.Users[username] = nil
|
||||
}
|
||||
}
|
||||
|
||||
for username, v := range updatedCfg.Users {
|
||||
if cfg.Users[username] == nil {
|
||||
fmt.Printf("Added User to configuration: %s\n", username)
|
||||
log.WithField("user", username).Info("Added User to configuration")
|
||||
cfg.Users[username] = v
|
||||
} else {
|
||||
if cfg.Users[username].Password != v.Password {
|
||||
fmt.Printf("Updated password of user: %s\n", username)
|
||||
log.WithField("user", username).Info("Updated password of user")
|
||||
cfg.Users[username].Password = v.Password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg.ensureUserDirs()
|
||||
|
||||
if cfg.Log.Create != updatedCfg.Log.Create {
|
||||
cfg.Log.Create = updatedCfg.Log.Create
|
||||
log.WithField("enabled", cfg.Log.Create).Info("Set logging for create operations")
|
||||
}
|
||||
|
||||
if cfg.Log.Read != updatedCfg.Log.Read {
|
||||
cfg.Log.Read = updatedCfg.Log.Read
|
||||
log.WithField("enabled", cfg.Log.Read).Info("Set logging for read operations")
|
||||
}
|
||||
|
||||
if cfg.Log.Update != updatedCfg.Log.Update {
|
||||
cfg.Log.Update = updatedCfg.Log.Update
|
||||
log.WithField("enabled", cfg.Log.Update).Info("Set logging for update operations")
|
||||
}
|
||||
|
||||
if cfg.Log.Delete != updatedCfg.Log.Delete {
|
||||
cfg.Log.Delete = updatedCfg.Log.Delete
|
||||
log.WithField("enabled", cfg.Log.Delete).Info("Set logging for delete operations")
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *Config) ensureUserDirs() {
|
||||
if _, err := os.Stat(cfg.Dir); os.IsNotExist(err) {
|
||||
os.Mkdir(cfg.Dir, os.ModePerm)
|
||||
fmt.Printf("Created base dir: %s\n", cfg.Dir)
|
||||
log.WithField("path", cfg.Dir).Info("Created base dir")
|
||||
}
|
||||
|
||||
for _, user := range cfg.Users {
|
||||
|
@ -122,7 +155,7 @@ func (cfg *Config) ensureUserDirs() {
|
|||
path := filepath.Join(cfg.Dir, *user.Subdir)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
os.Mkdir(path, os.ModePerm)
|
||||
fmt.Printf("Created user dir: %s\n", path)
|
||||
log.WithField("path", path).Info("Created user dir")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
63
app/fs.go
63
app/fs.go
|
@ -3,6 +3,7 @@ package app
|
|||
import (
|
||||
"context"
|
||||
"github.com/abbot/go-http-auth"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/webdav"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -18,6 +19,15 @@ type Dir struct {
|
|||
Config *Config
|
||||
}
|
||||
|
||||
func (d Dir) resolveUser(ctx context.Context) string {
|
||||
authInfo := auth.FromContext(ctx)
|
||||
if authInfo != nil && authInfo.Authenticated {
|
||||
return authInfo.Username
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// resolve tries to gain authentication information and suffixes the BaseDir with the
|
||||
// username of the authentication information. If none authentication information can
|
||||
// achieved during the process, the BaseDir is used
|
||||
|
@ -48,7 +58,19 @@ func (d Dir) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
|
|||
if name = d.resolve(ctx, name); name == "" {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
return os.Mkdir(name, perm)
|
||||
err := os.Mkdir(name, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.Config.Log.Create {
|
||||
log.WithFields(log.Fields{
|
||||
"path": name,
|
||||
"user": d.resolveUser(ctx),
|
||||
}).Info("Created directory")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// OpenFile resolves the physical file and delegates this to an os.OpenFile execution
|
||||
|
@ -60,6 +82,14 @@ func (d Dir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMo
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if d.Config.Log.Read {
|
||||
log.WithFields(log.Fields{
|
||||
"path": name,
|
||||
"user": d.resolveUser(ctx),
|
||||
}).Info("Opened file")
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
|
@ -72,7 +102,20 @@ func (d Dir) RemoveAll(ctx context.Context, name string) error {
|
|||
// Prohibit removing the virtual root directory.
|
||||
return os.ErrInvalid
|
||||
}
|
||||
return os.RemoveAll(name)
|
||||
|
||||
err := os.RemoveAll(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.Config.Log.Delete {
|
||||
log.WithFields(log.Fields{
|
||||
"path": name,
|
||||
"user": d.resolveUser(ctx),
|
||||
}).Info("Deleted file or directory")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rename resolves the physical file and delegates this to an os.Rename execution
|
||||
|
@ -87,7 +130,21 @@ func (d Dir) Rename(ctx context.Context, oldName, newName string) error {
|
|||
// Prohibit renaming from or to the virtual root directory.
|
||||
return os.ErrInvalid
|
||||
}
|
||||
return os.Rename(oldName, newName)
|
||||
|
||||
err := os.Rename(oldName, newName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.Config.Log.Update {
|
||||
log.WithFields(log.Fields{
|
||||
"oldPath": oldName,
|
||||
"newPath": newName,
|
||||
"user": d.resolveUser(ctx),
|
||||
}).Info("Renamed file or directory")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stat resolves the physical file and delegates this to an os.Stat execution
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"github.com/abbot/go-http-auth"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ModificationLogHandler logs each incoming request, which has a method
|
||||
// to create, update or delete a file or directory. If the request carries
|
||||
// authentication information, the respective username will also be logged.
|
||||
func ModificationLogHandler(r *http.Request, e error) {
|
||||
if r.Method == "PUT" || r.Method == "POST" || r.Method == "MKCOL" ||
|
||||
r.Method == "DELETE" || r.Method == "COPY" || r.Method == "MOVE" {
|
||||
|
||||
var contextLogger *log.Entry
|
||||
authInfo := auth.FromContext(r.Context())
|
||||
|
||||
if authInfo == nil || !authInfo.Authenticated {
|
||||
contextLogger = log.WithFields(log.Fields{
|
||||
"url": r.URL.Path,
|
||||
"method": r.Method,
|
||||
})
|
||||
} else {
|
||||
contextLogger = log.WithFields(log.Fields{
|
||||
"url": r.URL.Path,
|
||||
"method": r.Method,
|
||||
"user": authInfo.Username,
|
||||
})
|
||||
}
|
||||
|
||||
contextLogger.Info("File modified")
|
||||
}
|
||||
}
|
|
@ -2,8 +2,8 @@ package app
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/abbot/go-http-auth"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
@ -16,7 +16,7 @@ func Authorize(config *Config) auth.SecretProvider {
|
|||
return user.Password
|
||||
}
|
||||
|
||||
fmt.Printf("Username not found: %s\n", username)
|
||||
log.WithField("user", username).Warn("Username not found")
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,14 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/micromata/swd/app"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/webdav"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFormatter(&log.TextFormatter{})
|
||||
|
||||
config := app.ParseConfig()
|
||||
|
||||
wdHandler := &webdav.Handler{
|
||||
|
@ -19,8 +21,6 @@ func main() {
|
|||
LockSystem: webdav.NewMemLS(),
|
||||
}
|
||||
|
||||
//wdHandler.Logger = app.ModificationLogHandler
|
||||
|
||||
a := &app.App{
|
||||
Config: config,
|
||||
Handler: wdHandler,
|
||||
|
@ -31,10 +31,18 @@ func main() {
|
|||
connAddr := fmt.Sprintf("%s:%s", config.Address, config.Port)
|
||||
|
||||
if config.TLS != nil {
|
||||
fmt.Printf("TLS Server is starting and listening at: %s\n", connAddr)
|
||||
log.WithFields(log.Fields{
|
||||
"address": config.Address,
|
||||
"port": config.Port,
|
||||
"security": "TLS",
|
||||
}).Info("Server is starting and listening")
|
||||
log.Fatal(http.ListenAndServeTLS(connAddr, config.TLS.CertFile, config.TLS.KeyFile, nil))
|
||||
} else {
|
||||
fmt.Printf("Server is starting and listening at: %s\n", connAddr)
|
||||
log.WithFields(log.Fields{
|
||||
"address": config.Address,
|
||||
"port": config.Port,
|
||||
"security": "none",
|
||||
}).Info("Server is starting and listening")
|
||||
log.Fatal(http.ListenAndServe(connAddr, nil))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue