From 7db839a29f534e3ace1a138711efe6bbe75297c6 Mon Sep 17 00:00:00 2001 From: Mohammed <79150699+mrpalide@users.noreply.github.com> Date: Thu, 23 Mar 2023 20:29:33 +0330 Subject: [PATCH] improve logging (#1507) * add store log file (use 3rd party lib lfshook) * add --force-color flag to change formatter of logs * fix /runtime-logs missing logs * fix app logs * fix test fail * fix since query on logs * update skywire@utilities * fix name on skywire.log * change logfile lib, add max size 1mb * change customdmsghttppath to dmsghttp_server_path --------- Co-authored-by: Moses Narrow <36607567+0pcom@users.noreply.github.com> --- go.mod | 2 + go.sum | 4 + pkg/app/appcommon/log_store.go | 3 +- pkg/app/appcommon/proc_config.go | 21 +- pkg/app/appserver/proc.go | 81 ++- pkg/app/appserver/proc_manager.go | 29 +- pkg/app/appserver/proc_manager_test.go | 12 +- pkg/visor/cmd.go | 7 +- pkg/visor/init.go | 4 +- pkg/visor/visor.go | 108 +++- pkg/visor/visorconfig/config.go | 6 +- pkg/visor/visorconfig/v1.go | 2 +- .../github.com/orandin/lumberjackrus/LICENSE | 21 + .../orandin/lumberjackrus/README.md | 56 ++ .../orandin/lumberjackrus/lumberjackrus.go | 104 ++++ .../natefinch/lumberjack.v2/.gitignore | 23 + .../natefinch/lumberjack.v2/.travis.yml | 11 + .../gopkg.in/natefinch/lumberjack.v2/LICENSE | 21 + .../natefinch/lumberjack.v2/README.md | 179 ++++++ .../gopkg.in/natefinch/lumberjack.v2/chown.go | 11 + .../natefinch/lumberjack.v2/chown_linux.go | 19 + .../natefinch/lumberjack.v2/lumberjack.go | 541 ++++++++++++++++++ vendor/modules.txt | 6 + 23 files changed, 1230 insertions(+), 41 deletions(-) create mode 100644 vendor/github.com/orandin/lumberjackrus/LICENSE create mode 100644 vendor/github.com/orandin/lumberjackrus/README.md create mode 100644 vendor/github.com/orandin/lumberjackrus/lumberjackrus.go create mode 100644 vendor/gopkg.in/natefinch/lumberjack.v2/.gitignore create mode 100644 vendor/gopkg.in/natefinch/lumberjack.v2/.travis.yml create mode 100644 vendor/gopkg.in/natefinch/lumberjack.v2/LICENSE create mode 100644 vendor/gopkg.in/natefinch/lumberjack.v2/README.md create mode 100644 vendor/gopkg.in/natefinch/lumberjack.v2/chown.go create mode 100644 vendor/gopkg.in/natefinch/lumberjack.v2/chown_linux.go create mode 100644 vendor/gopkg.in/natefinch/lumberjack.v2/lumberjack.go diff --git a/go.mod b/go.mod index 7bc2aad710..69ee4e5aeb 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,7 @@ require ( github.com/james-barrow/golang-ipc v0.0.0-20210227130457-95e7cc81f5e2 github.com/jaypipes/ghw v0.10.0 github.com/lib/pq v1.10.7 + github.com/orandin/lumberjackrus v1.0.1 github.com/pterm/pterm v0.12.49 github.com/skycoin/dmsg v1.3.0-rc1.0.20230224131835-1c194ef9791e github.com/skycoin/skywire-utilities v0.0.0-20230315234948-7c62dc34c53a @@ -102,6 +103,7 @@ require ( golang.org/x/crypto v0.1.0 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/text v0.7.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect howett.net/plist v1.0.0 // indirect diff --git a/go.sum b/go.sum index b233227ada..80592ad77a 100644 --- a/go.sum +++ b/go.sum @@ -458,6 +458,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/orandin/lumberjackrus v1.0.1 h1:7ysDQ0MHD79zIFN9/EiDHjUcgopNi5ehtxFDy8rUkWo= +github.com/orandin/lumberjackrus v1.0.1/go.mod h1:xYLt6H8W93pKnQgUQaxsApS0Eb4BwHLOkxk5DVzf5H0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= @@ -1121,6 +1123,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/app/appcommon/log_store.go b/pkg/app/appcommon/log_store.go index 8b7f8af8df..ccb93d085e 100644 --- a/pkg/app/appcommon/log_store.go +++ b/pkg/app/appcommon/log_store.go @@ -215,8 +215,7 @@ func (l *bBoltLogStore) Fire(entry *log.Entry) error { }() // time in RFC3339Nano is between the bytes 1 and 36. This will change if other time layout is in use - t := p[1 : 1+len(timeLayout)] - + t := strings.Split(strings.Split(strings.Split(p, " ")[0], "]")[0], "[")[2] err = db.Update(func(tx *bbolt.Tx) error { b := tx.Bucket(l.bucket) return b.Put([]byte(t), []byte(str)) diff --git a/pkg/app/appcommon/proc_config.go b/pkg/app/appcommon/proc_config.go index 23cdb0014c..fa7e1ee52c 100644 --- a/pkg/app/appcommon/proc_config.go +++ b/pkg/app/appcommon/proc_config.go @@ -70,16 +70,17 @@ type ProcID uint16 // ProcConfig defines configuration parameters for `Proc`. type ProcConfig struct { - AppName string `json:"app_name"` - AppSrvAddr string `json:"app_server_addr"` - ProcKey ProcKey `json:"proc_key"` - ProcArgs []string `json:"proc_args"` - ProcEnvs []string `json:"proc_envs"` // Additional env variables. Will be overwritten if they conflict with skywire-app specific envs. - ProcWorkDir string `json:"proc_work_dir"` - VisorPK cipher.PubKey `json:"visor_pk"` - RoutingPort routing.Port `json:"routing_port"` - BinaryLoc string `json:"binary_loc"` - LogDBLoc string `json:"log_db_loc"` + AppName string `json:"app_name"` + AppSrvAddr string `json:"app_server_addr"` + ProcKey ProcKey `json:"proc_key"` + ProcArgs []string `json:"proc_args"` + ProcEnvs []string `json:"proc_envs"` // Additional env variables. Will be overwritten if they conflict with skywire-app specific envs. + ProcWorkDir string `json:"proc_work_dir"` + VisorPK cipher.PubKey `json:"visor_pk"` + RoutingPort routing.Port `json:"routing_port"` + BinaryLoc string `json:"binary_loc"` + LogDBLoc string `json:"log_db_loc"` + LogStorePath string `json:"log_store_path"` } // ProcConfigFromEnv obtains a ProcConfig from the associated env variable, returning an error if any. diff --git a/pkg/app/appserver/proc.go b/pkg/app/appserver/proc.go index 79e982f87a..cd6d0d0b08 100644 --- a/pkg/app/appserver/proc.go +++ b/pkg/app/appserver/proc.go @@ -16,6 +16,7 @@ import ( "time" ipc "github.com/james-barrow/golang-ipc" + "github.com/orandin/lumberjackrus" "github.com/sirupsen/logrus" "github.com/skycoin/skywire-utilities/pkg/logging" @@ -80,7 +81,7 @@ type Proc struct { // NewProc constructs `Proc`. func NewProc(mLog *logging.MasterLogger, conf appcommon.ProcConfig, disc appdisc.Updater, m ProcManager, - appName string) *Proc { + appName, logStorePath string) *Proc { if mLog == nil { mLog = logging.NewMasterLogger() } @@ -96,8 +97,14 @@ func NewProc(mLog *logging.MasterLogger, conf appcommon.ProcConfig, disc appdisc var appLogDB appcommon.LogStore var appLog *logging.MasterLogger var stderr io.ReadCloser + procLogger := mLog if conf.LogDBLoc != "" { appLog, appLogDB = appcommon.NewProcLogger(conf, mLog) + procLogger = appLog + if logStorePath != "" { + storeLog(appLog, logStorePath) + } + cmd.Stdout = appLog.WithField("_module", moduleName).WithField("func", "(STDOUT)").WriterLevel(logrus.DebugLevel) // we read the Stderr pipe in order to filter some false positive app errors @@ -109,7 +116,7 @@ func NewProc(mLog *logging.MasterLogger, conf appcommon.ProcConfig, disc appdisc p := &Proc{ disc: disc, conf: conf, - log: mLog.PackageLogger(moduleName), + log: procLogger.PackageLogger(moduleName), logDB: appLogDB, cmd: cmd, connCh: make(chan struct{}, 1), @@ -468,3 +475,73 @@ func (p *Proc) ConnectionsSummary() []ConnectionSummary { return summaries } + +func storeLog(log *logging.MasterLogger, localPath string) { + hook, _ := lumberjackrus.NewHook( //nolint + &lumberjackrus.LogFile{ + Filename: localPath + "/log/skywire.log", + MaxSize: 1, + MaxBackups: 1, + MaxAge: 1, + Compress: false, + LocalTime: false, + }, + logrus.TraceLevel, + &logging.TextFormatter{ + DisableColors: true, + FullTimestamp: true, + ForceFormatting: true, + }, + &lumberjackrus.LogFileOpts{ + logrus.InfoLevel: &lumberjackrus.LogFile{ + Filename: localPath + "/log/skywire.log", + MaxSize: 1, + MaxBackups: 1, + MaxAge: 1, + Compress: false, + LocalTime: false, + }, + logrus.WarnLevel: &lumberjackrus.LogFile{ + Filename: localPath + "/log/skywire.log", + MaxSize: 1, + MaxBackups: 1, + MaxAge: 1, + Compress: false, + LocalTime: false, + }, + logrus.TraceLevel: &lumberjackrus.LogFile{ + Filename: localPath + "/log/skywire.log", + MaxSize: 1, + MaxBackups: 1, + MaxAge: 1, + Compress: false, + LocalTime: false, + }, + logrus.ErrorLevel: &lumberjackrus.LogFile{ + Filename: localPath + "/log/skywire.log", + MaxSize: 1, + MaxBackups: 1, + MaxAge: 1, + Compress: false, + LocalTime: false, + }, + logrus.DebugLevel: &lumberjackrus.LogFile{ + Filename: localPath + "/log/skywire.log", + MaxSize: 1, + MaxBackups: 1, + MaxAge: 1, + Compress: false, + LocalTime: false, + }, + logrus.FatalLevel: &lumberjackrus.LogFile{ + Filename: localPath + "/log/skywire.log", + MaxSize: 1, + MaxBackups: 1, + MaxAge: 1, + Compress: false, + LocalTime: false, + }, + }, + ) + log.Hooks.Add(hook) +} diff --git a/pkg/app/appserver/proc_manager.go b/pkg/app/appserver/proc_manager.go index efeab9038a..357dde3bb5 100644 --- a/pkg/app/appserver/proc_manager.go +++ b/pkg/app/appserver/proc_manager.go @@ -73,10 +73,12 @@ type procManager struct { mx sync.RWMutex done chan struct{} + + logStorePath string } // NewProcManager constructs `ProcManager`. -func NewProcManager(mLog *logging.MasterLogger, discF *appdisc.Factory, eb *appevent.Broadcaster, addr string) (ProcManager, error) { +func NewProcManager(mLog *logging.MasterLogger, discF *appdisc.Factory, eb *appevent.Broadcaster, addr, logStorePath string) (ProcManager, error) { if mLog == nil { mLog = logging.NewMasterLogger() } @@ -93,16 +95,17 @@ func NewProcManager(mLog *logging.MasterLogger, discF *appdisc.Factory, eb *appe } procM := &procManager{ - mLog: mLog, - log: mLog.PackageLogger("proc_manager"), - lis: lis, - conns: make(map[string]net.Conn), - discF: discF, - procs: make(map[string]*Proc), - procsByKey: make(map[appcommon.ProcKey]*Proc), - errors: make(map[string]string), - eb: eb, - done: make(chan struct{}), + mLog: mLog, + log: mLog.PackageLogger("proc_manager"), + lis: lis, + conns: make(map[string]net.Conn), + discF: discF, + procs: make(map[string]*Proc), + procsByKey: make(map[appcommon.ProcKey]*Proc), + errors: make(map[string]string), + eb: eb, + done: make(chan struct{}), + logStorePath: logStorePath, } procM.connsWG.Add(1) @@ -207,7 +210,7 @@ func (m *procManager) Start(conf appcommon.ProcConfig) (appcommon.ProcID, error) Debug("No app discovery associated with app.") } - proc := NewProc(m.mLog, conf, disc, m, conf.AppName) + proc := NewProc(nil, conf, disc, m, conf.AppName, m.logStorePath) m.procs[conf.AppName] = proc m.procsByKey[conf.ProcKey] = proc @@ -253,7 +256,7 @@ func (m *procManager) Register(conf appcommon.ProcConfig) (appcommon.ProcKey, er Debug("No app discovery associated with app.") } - proc := NewProc(m.mLog, conf, disc, m, conf.AppName) + proc := NewProc(nil, conf, disc, m, conf.AppName, m.logStorePath) m.procs[conf.AppName] = proc m.procsByKey[conf.ProcKey] = proc go func() { diff --git a/pkg/app/appserver/proc_manager_test.go b/pkg/app/appserver/proc_manager_test.go index dae0e1bf28..5aeefa507d 100644 --- a/pkg/app/appserver/proc_manager_test.go +++ b/pkg/app/appserver/proc_manager_test.go @@ -12,7 +12,7 @@ import ( ) func TestProcManager_ProcByName(t *testing.T) { - mI, err := NewProcManager(nil, nil, nil, ":0") + mI, err := NewProcManager(nil, nil, nil, ":0", "") require.NoError(t, err) m, ok := mI.(*procManager) @@ -32,7 +32,7 @@ func TestProcManager_ProcByName(t *testing.T) { } func TestProcManager_Range(t *testing.T) { - mI, err := NewProcManager(nil, nil, nil, ":0") + mI, err := NewProcManager(nil, nil, nil, ":0", "") require.NoError(t, err) m, ok := mI.(*procManager) @@ -61,7 +61,7 @@ func TestProcManager_Range(t *testing.T) { } func TestProcManager_Pop(t *testing.T) { - mI, err := NewProcManager(nil, nil, nil, ":0") + mI, err := NewProcManager(nil, nil, nil, ":0", "") require.NoError(t, err) m, ok := mI.(*procManager) @@ -84,7 +84,7 @@ func TestProcManager_Pop(t *testing.T) { } func TestProcManager_SetDetailedStatus(t *testing.T) { - mI, err := NewProcManager(nil, nil, nil, ":0") + mI, err := NewProcManager(nil, nil, nil, ":0", "") require.NoError(t, err) m, ok := mI.(*procManager) @@ -109,7 +109,7 @@ func TestProcManager_SetDetailedStatus(t *testing.T) { } func TestProcManager_DetailedStatus(t *testing.T) { - mI, err := NewProcManager(nil, nil, nil, ":0") + mI, err := NewProcManager(nil, nil, nil, ":0", "") require.NoError(t, err) m, ok := mI.(*procManager) @@ -132,7 +132,7 @@ func TestProcManager_DetailedStatus(t *testing.T) { } func TestProcManager_RegisterAndDeregister(t *testing.T) { - mI, err := NewProcManager(nil, nil, nil, ":0") + mI, err := NewProcManager(nil, nil, nil, ":0", "") require.NoError(t, err) m, ok := mI.(*procManager) diff --git a/pkg/visor/cmd.go b/pkg/visor/cmd.go index 50da50d396..5afa98f134 100644 --- a/pkg/visor/cmd.go +++ b/pkg/visor/cmd.go @@ -55,6 +55,8 @@ var ( visorBuildInfo *buildinfo.Info dmsgServer string rawSurvey bool + isStoreLog bool + isForceColor bool ) func init() { @@ -130,8 +132,11 @@ func init() { hiddenflags = append(hiddenflags, "completion") RootCmd.Flags().BoolVar(&rawSurvey, "raw-survey", false, "survey will generate and store decrypted if pass this flag") hiddenflags = append(hiddenflags, "raw-survey") + RootCmd.Flags().BoolVarP(&isStoreLog, "store-log", "l", false, "store all logs to file") + hiddenflags = append(hiddenflags, "store-log") + RootCmd.Flags().BoolVar(&isForceColor, "force-color", false, "set force coler true in loggers") + hiddenflags = append(hiddenflags, "force-color") RootCmd.Flags().BoolVar(&all, "all", false, "show all flags") - for _, j := range hiddenflags { RootCmd.Flags().MarkHidden(j) //nolint } diff --git a/pkg/visor/init.go b/pkg/visor/init.go index 159448e66d..5b6bdf8966 100644 --- a/pkg/visor/init.go +++ b/pkg/visor/init.go @@ -388,7 +388,7 @@ func initDmsgHTTPLogServer(ctx context.Context, v *Visor, log *logging.Logger) e printLog = true } - lsAPI := logserver.New(logger, v.conf.Transport.LogStore.Location, v.conf.LocalPath, v.conf.CustomDmsgHTTPPath, printLog) + lsAPI := logserver.New(logger, v.conf.Transport.LogStore.Location, v.conf.LocalPath, v.conf.DmsgHTTPServerPath, printLog) lis, err := dmsgC.Listen(visorconfig.DmsgHTTPPort) if err != nil { @@ -1007,7 +1007,7 @@ func initLauncher(ctx context.Context, v *Visor, log *logging.Logger) error { conf := v.conf.Launcher // Prepare proc manager. - procM, err := appserver.NewProcManager(v.MasterLogger(), &v.serviceDisc, v.ebc, conf.ServerAddr) + procM, err := appserver.NewProcManager(v.MasterLogger(), &v.serviceDisc, v.ebc, conf.ServerAddr, v.conf.LocalPath) if err != nil { err := fmt.Errorf("failed to start proc_manager: %w", err) return err diff --git a/pkg/visor/visor.go b/pkg/visor/visor.go index 3649f58f91..bf6c898870 100644 --- a/pkg/visor/visor.go +++ b/pkg/visor/visor.go @@ -13,6 +13,8 @@ import ( "sync" "time" + "github.com/orandin/lumberjackrus" + "github.com/sirupsen/logrus" dmsgdisc "github.com/skycoin/dmsg/pkg/disc" "github.com/skycoin/dmsg/pkg/dmsg" "github.com/toqueteos/webbrowser" @@ -116,6 +118,8 @@ type Visor struct { pingPcktSize int rawSurvey bool + + logStorePath string } // todo: consider moving module closing to the module system @@ -163,6 +167,8 @@ func run(conf *visorconfig.V1) error { conf = initConfig() } + conf.MasterLogger().AddHook(hook) + if disableHypervisorPKs { conf.Hypervisors = []cipher.PubKey{} } @@ -237,6 +243,11 @@ func NewVisor(ctx context.Context, conf *visorconfig.V1) (*Visor, bool) { if conf == nil { conf = initConfig() } + + if isForceColor { + setForceColor(conf) + } + v := &Visor{ log: conf.MasterLogger().PackageLogger("visor"), conf: conf, @@ -260,10 +271,14 @@ func NewVisor(ctx context.Context, conf *visorconfig.V1) (*Visor, bool) { v.conf.MasterLogger().SetLevel(logLvl) } + v.startedAt = time.Now() + if isStoreLog { + storeLog(conf) + v.logStorePath = conf.LocalPath + } log := v.MasterLogger().PackageLogger("visor:startup") log.WithField("public_key", conf.PK). Info("Begin startup.") - v.startedAt = time.Now() ctx = context.WithValue(ctx, visorKey, v) v.runtimeErrors = make(chan error) ctx = context.WithValue(ctx, runtimeErrsKey, v.runtimeErrors) @@ -539,3 +554,94 @@ func initUI() *fs.FS { return &uiFS } + +func storeLog(conf *visorconfig.V1) { + hook, _ := lumberjackrus.NewHook( //nolint + &lumberjackrus.LogFile{ + Filename: conf.LocalPath + "/log/skywire.log", + MaxSize: 1, + MaxBackups: 1, + MaxAge: 1, + Compress: false, + LocalTime: false, + }, + logrus.TraceLevel, + &logging.TextFormatter{ + DisableColors: true, + FullTimestamp: true, + ForceFormatting: true, + }, + &lumberjackrus.LogFileOpts{ + logrus.InfoLevel: &lumberjackrus.LogFile{ + Filename: conf.LocalPath + "/log/skywire.log", + MaxSize: 1, + MaxBackups: 1, + MaxAge: 1, + Compress: false, + LocalTime: false, + }, + logrus.WarnLevel: &lumberjackrus.LogFile{ + Filename: conf.LocalPath + "/log/skywire.log", + MaxSize: 1, + MaxBackups: 1, + MaxAge: 1, + Compress: false, + LocalTime: false, + }, + logrus.TraceLevel: &lumberjackrus.LogFile{ + Filename: conf.LocalPath + "/log/skywire.log", + MaxSize: 1, + MaxBackups: 1, + MaxAge: 1, + Compress: false, + LocalTime: false, + }, + logrus.ErrorLevel: &lumberjackrus.LogFile{ + Filename: conf.LocalPath + "/log/skywire.log", + MaxSize: 1, + MaxBackups: 1, + MaxAge: 1, + Compress: false, + LocalTime: false, + }, + logrus.DebugLevel: &lumberjackrus.LogFile{ + Filename: conf.LocalPath + "/log/skywire.log", + MaxSize: 1, + MaxBackups: 1, + MaxAge: 1, + Compress: false, + LocalTime: false, + }, + logrus.FatalLevel: &lumberjackrus.LogFile{ + Filename: conf.LocalPath + "/log/skywire.log", + MaxSize: 1, + MaxBackups: 1, + MaxAge: 1, + Compress: false, + LocalTime: false, + }, + }, + ) + mLog.Hooks.Add(hook) + conf.MasterLogger().Hooks.Add(hook) +} + +func setForceColor(conf *visorconfig.V1) { + conf.MasterLogger().SetFormatter(&logging.TextFormatter{ + FullTimestamp: true, + AlwaysQuoteStrings: true, + QuoteEmptyFields: true, + ForceFormatting: true, + DisableColors: false, + ForceColors: true, + }) + + mLog.SetFormatter(&logging.TextFormatter{ + FullTimestamp: true, + AlwaysQuoteStrings: true, + QuoteEmptyFields: true, + ForceFormatting: true, + DisableColors: false, + ForceColors: true, + }) +} diff --git a/pkg/visor/visorconfig/config.go b/pkg/visor/visorconfig/config.go index 34ef5be1bf..ac34164f69 100644 --- a/pkg/visor/visorconfig/config.go +++ b/pkg/visor/visorconfig/config.go @@ -92,7 +92,7 @@ func MakeBaseConfig(common *Common, testEnv bool, dmsgHTTP bool, services *Servi conf.CLIAddr = RPCAddr conf.LogLevel = LogLevel conf.LocalPath = LocalPath - conf.CustomDmsgHTTPPath = LocalPath + "/" + Custom + conf.DmsgHTTPServerPath = LocalPath + "/" + Custom conf.StunServers = services.StunServers //utilenv.GetStunServers() conf.ShutdownTimeout = DefaultTimeout conf.RestartCheckDelay = Duration(restart.DefaultCheckDelay) @@ -202,7 +202,7 @@ func MakeDefaultConfig(log *logging.MasterLogger, sk *cipher.SecKey, usrEnv bool if pkgEnv { pkgConfig := PackageConfig() conf.LocalPath = pkgConfig.LocalPath - conf.CustomDmsgHTTPPath = pkgConfig.LocalPath + "/" + Custom + conf.DmsgHTTPServerPath = pkgConfig.LocalPath + "/" + Custom conf.Launcher.BinPath = pkgConfig.LauncherBinPath conf.Transport.LogStore.Location = pkgConfig.LocalPath + "/" + TpLogStore if conf.Hypervisor != nil { @@ -213,7 +213,7 @@ func MakeDefaultConfig(log *logging.MasterLogger, sk *cipher.SecKey, usrEnv bool if usrEnv { usrConfig := UserConfig() conf.LocalPath = usrConfig.LocalPath - conf.CustomDmsgHTTPPath = usrConfig.LocalPath + "/" + Custom + conf.DmsgHTTPServerPath = usrConfig.LocalPath + "/" + Custom conf.Launcher.BinPath = usrConfig.LauncherBinPath conf.Transport.LogStore.Location = usrConfig.LocalPath + "/" + TpLogStore if conf.Hypervisor != nil { diff --git a/pkg/visor/visorconfig/v1.go b/pkg/visor/visorconfig/v1.go index 7a2f5bbc1b..35cdab5798 100644 --- a/pkg/visor/visorconfig/v1.go +++ b/pkg/visor/visorconfig/v1.go @@ -32,7 +32,7 @@ type V1 struct { LogLevel string `json:"log_level"` LocalPath string `json:"local_path"` - CustomDmsgHTTPPath string `json:"custom_dmsg_http_path"` + DmsgHTTPServerPath string `json:"dmsghttp_server_path"` StunServers []string `json:"stun_servers"` ShutdownTimeout Duration `json:"shutdown_timeout,omitempty"` // time value, examples: 10s, 1m, etc RestartCheckDelay Duration `json:"restart_check_delay,omitempty"` // time value, examples: 10s, 1m, etc diff --git a/vendor/github.com/orandin/lumberjackrus/LICENSE b/vendor/github.com/orandin/lumberjackrus/LICENSE new file mode 100644 index 0000000000..ab3c93cfd8 --- /dev/null +++ b/vendor/github.com/orandin/lumberjackrus/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Simon Delberghe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/orandin/lumberjackrus/README.md b/vendor/github.com/orandin/lumberjackrus/README.md new file mode 100644 index 0000000000..bd53f7c4ac --- /dev/null +++ b/vendor/github.com/orandin/lumberjackrus/README.md @@ -0,0 +1,56 @@ +# lumberjackrus | local filesystem hook for Logrus + +## Example + +```golang +package main + +import ( + "github.com/sirupsen/logrus" + "github.com/orandin/lumberjackrus" +) + +func init() { + logrus.SetFormatter(&logrus.TextFormatter{}) + logrus.SetLevel(logrus.DebugLevel) + + hook, err := lumberjackrus.NewHook( + &lumberjackrus.LogFile{ + Filename: "/tmp/general.log", + MaxSize: 100, + MaxBackups: 1, + MaxAge: 1, + Compress: false, + LocalTime: false, + }, + logrus.InfoLevel, + &logrus.TextFormatter{}, + &lumberjackrus.LogFileOpts{ + logrus.InfoLevel: &lumberjackrus.LogFile{ + Filename: "/tmp/info.log", + }, + logrus.ErrorLevel: &lumberjackrus.LogFile{ + Filename: "/tmp/error.log", + MaxSize: 100, // optional + MaxBackups: 1, // optional + MaxAge: 1, // optional + Compress: false, // optional + LocalTime: false, // optional + }, + }, + ) + + if err != nil { + panic(err) + } + + logrus.AddHook(hook) +} + +func main() { + logrus.Debug("Debug message") // It is not written to a file (because debug level < minLevel) + logrus.Info("Info message") // Written in /tmp/info.log + logrus.Warn("Warn message") // Written in /tmp/general.log + logrus.Error("Error message") // Written in /tmp/error.log +} +``` diff --git a/vendor/github.com/orandin/lumberjackrus/lumberjackrus.go b/vendor/github.com/orandin/lumberjackrus/lumberjackrus.go new file mode 100644 index 0000000000..492b4b3f93 --- /dev/null +++ b/vendor/github.com/orandin/lumberjackrus/lumberjackrus.go @@ -0,0 +1,104 @@ +package lumberjackrus + +import ( + "github.com/sirupsen/logrus" + "gopkg.in/natefinch/lumberjack.v2" + "fmt" +) + +type LogFile struct { + // Filename is the file to write logs to. Backup log files will be retained in the same directory. + // It uses -lumberjack.log in os.TempDir() if empty. + Filename string `json:"filename" yaml:"filename"` + + // MaxSize is the maximum size in megabytes of the log file before it gets rotated. It defaults to 100 megabytes. + MaxSize int `json:"maxsize" yaml:"maxsize"` + + // MaxAge is the maximum number of days to retain old log files based on the timestamp encoded in their filename. + // Note that a day is defined as 24 hours and may not exactly correspond to calendar days due to daylight savings, + // leap seconds, etc. The default is not to remove old log files based on age. + MaxAge int `json:"maxage" yaml:"maxage"` + + // MaxBackups is the maximum number of old log files to retain. The default is to retain all old log files (though + // MaxAge may still cause them to get deleted.) + MaxBackups int `json:"maxbackups" yaml:"maxbackups"` + + // LocalTime determines if the time used for formatting the timestamps in backup files is the computer's local time. + // The default is to use UTC time. + LocalTime bool `json:"localtime" yaml:"localtime"` + + // Compress determines if the rotated log files should be compressed using gzip. + Compress bool `json:"compress" yaml:"compress"` +} + +type LogFileOpts map[logrus.Level]*LogFile + +type Hook struct { + defaultLogger *lumberjack.Logger + formatter logrus.Formatter + minLevel logrus.Level + loggerByLevel map[logrus.Level]*lumberjack.Logger +} + +func NewHook(defaultLogger *LogFile, minLevel logrus.Level, formatter logrus.Formatter, opts *LogFileOpts) (*Hook, error) { + + if defaultLogger == nil { + return nil, fmt.Errorf("default logger cannot be nil") + } + + hook := Hook{ + defaultLogger: &lumberjack.Logger{ + Filename: defaultLogger.Filename, + MaxSize: defaultLogger.MaxSize, + MaxBackups: defaultLogger.MaxBackups, + MaxAge: defaultLogger.MaxAge, + Compress: defaultLogger.Compress, + LocalTime: defaultLogger.LocalTime, + }, + minLevel: minLevel, + formatter: formatter, + loggerByLevel: make(map[logrus.Level]*lumberjack.Logger), + } + + if opts != nil { + + maxLevel := len(hook.Levels()) + for level, config := range *opts { + + if maxLevel <= int(level) { + continue + } + + hook.loggerByLevel[level] = &lumberjack.Logger{ + Filename: config.Filename, + MaxSize: config.MaxSize, + MaxBackups: config.MaxBackups, + MaxAge: config.MaxAge, + Compress: config.Compress, + LocalTime: config.LocalTime, + } + } + } + + return &hook, nil +} + +func (hook *Hook) Fire(entry *logrus.Entry) error { + + msg, err := hook.formatter.Format(entry) + if err != nil { + return err + } + + if logger, ok := hook.loggerByLevel[entry.Level]; ok { + _, err = logger.Write([]byte(msg)) + } else { + _, err = hook.defaultLogger.Write([]byte(msg)) + } + + return err +} + +func (hook *Hook) Levels() []logrus.Level { + return logrus.AllLevels[:hook.minLevel+1] +} diff --git a/vendor/gopkg.in/natefinch/lumberjack.v2/.gitignore b/vendor/gopkg.in/natefinch/lumberjack.v2/.gitignore new file mode 100644 index 0000000000..836562412f --- /dev/null +++ b/vendor/gopkg.in/natefinch/lumberjack.v2/.gitignore @@ -0,0 +1,23 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test diff --git a/vendor/gopkg.in/natefinch/lumberjack.v2/.travis.yml b/vendor/gopkg.in/natefinch/lumberjack.v2/.travis.yml new file mode 100644 index 0000000000..21166f5c7d --- /dev/null +++ b/vendor/gopkg.in/natefinch/lumberjack.v2/.travis.yml @@ -0,0 +1,11 @@ +language: go + +go: + - tip + - 1.15.x + - 1.14.x + - 1.13.x + - 1.12.x + +env: + - GO111MODULE=on diff --git a/vendor/gopkg.in/natefinch/lumberjack.v2/LICENSE b/vendor/gopkg.in/natefinch/lumberjack.v2/LICENSE new file mode 100644 index 0000000000..c3d4cc307d --- /dev/null +++ b/vendor/gopkg.in/natefinch/lumberjack.v2/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Nate Finch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/gopkg.in/natefinch/lumberjack.v2/README.md b/vendor/gopkg.in/natefinch/lumberjack.v2/README.md new file mode 100644 index 0000000000..060eae52a2 --- /dev/null +++ b/vendor/gopkg.in/natefinch/lumberjack.v2/README.md @@ -0,0 +1,179 @@ +# lumberjack [![GoDoc](https://godoc.org/gopkg.in/natefinch/lumberjack.v2?status.png)](https://godoc.org/gopkg.in/natefinch/lumberjack.v2) [![Build Status](https://travis-ci.org/natefinch/lumberjack.svg?branch=v2.0)](https://travis-ci.org/natefinch/lumberjack) [![Build status](https://ci.appveyor.com/api/projects/status/00gchpxtg4gkrt5d)](https://ci.appveyor.com/project/natefinch/lumberjack) [![Coverage Status](https://coveralls.io/repos/natefinch/lumberjack/badge.svg?branch=v2.0)](https://coveralls.io/r/natefinch/lumberjack?branch=v2.0) + +### Lumberjack is a Go package for writing logs to rolling files. + +Package lumberjack provides a rolling logger. + +Note that this is v2.0 of lumberjack, and should be imported using gopkg.in +thusly: + + import "gopkg.in/natefinch/lumberjack.v2" + +The package name remains simply lumberjack, and the code resides at +https://github.com/natefinch/lumberjack under the v2.0 branch. + +Lumberjack is intended to be one part of a logging infrastructure. +It is not an all-in-one solution, but instead is a pluggable +component at the bottom of the logging stack that simply controls the files +to which logs are written. + +Lumberjack plays well with any logging package that can write to an +io.Writer, including the standard library's log package. + +Lumberjack assumes that only one process is writing to the output files. +Using the same lumberjack configuration from multiple processes on the same +machine will result in improper behavior. + + +**Example** + +To use lumberjack with the standard library's log package, just pass it into the SetOutput function when your application starts. + +Code: + +```go +log.SetOutput(&lumberjack.Logger{ + Filename: "/var/log/myapp/foo.log", + MaxSize: 500, // megabytes + MaxBackups: 3, + MaxAge: 28, //days + Compress: true, // disabled by default +}) +``` + + + +## type Logger +``` go +type Logger struct { + // Filename is the file to write logs to. Backup log files will be retained + // in the same directory. It uses -lumberjack.log in + // os.TempDir() if empty. + Filename string `json:"filename" yaml:"filename"` + + // MaxSize is the maximum size in megabytes of the log file before it gets + // rotated. It defaults to 100 megabytes. + MaxSize int `json:"maxsize" yaml:"maxsize"` + + // MaxAge is the maximum number of days to retain old log files based on the + // timestamp encoded in their filename. Note that a day is defined as 24 + // hours and may not exactly correspond to calendar days due to daylight + // savings, leap seconds, etc. The default is not to remove old log files + // based on age. + MaxAge int `json:"maxage" yaml:"maxage"` + + // MaxBackups is the maximum number of old log files to retain. The default + // is to retain all old log files (though MaxAge may still cause them to get + // deleted.) + MaxBackups int `json:"maxbackups" yaml:"maxbackups"` + + // LocalTime determines if the time used for formatting the timestamps in + // backup files is the computer's local time. The default is to use UTC + // time. + LocalTime bool `json:"localtime" yaml:"localtime"` + + // Compress determines if the rotated log files should be compressed + // using gzip. The default is not to perform compression. + Compress bool `json:"compress" yaml:"compress"` + // contains filtered or unexported fields +} +``` +Logger is an io.WriteCloser that writes to the specified filename. + +Logger opens or creates the logfile on first Write. If the file exists and +is less than MaxSize megabytes, lumberjack will open and append to that file. +If the file exists and its size is >= MaxSize megabytes, the file is renamed +by putting the current time in a timestamp in the name immediately before the +file's extension (or the end of the filename if there's no extension). A new +log file is then created using original filename. + +Whenever a write would cause the current log file exceed MaxSize megabytes, +the current file is closed, renamed, and a new log file created with the +original name. Thus, the filename you give Logger is always the "current" log +file. + +Backups use the log file name given to Logger, in the form `name-timestamp.ext` +where name is the filename without the extension, timestamp is the time at which +the log was rotated formatted with the time.Time format of +`2006-01-02T15-04-05.000` and the extension is the original extension. For +example, if your Logger.Filename is `/var/log/foo/server.log`, a backup created +at 6:30pm on Nov 11 2016 would use the filename +`/var/log/foo/server-2016-11-04T18-30-00.000.log` + +### Cleaning Up Old Log Files +Whenever a new logfile gets created, old log files may be deleted. The most +recent files according to the encoded timestamp will be retained, up to a +number equal to MaxBackups (or all of them if MaxBackups is 0). Any files +with an encoded timestamp older than MaxAge days are deleted, regardless of +MaxBackups. Note that the time encoded in the timestamp is the rotation +time, which may differ from the last time that file was written to. + +If MaxBackups and MaxAge are both 0, no old log files will be deleted. + + + + + + + + + + + +### func (\*Logger) Close +``` go +func (l *Logger) Close() error +``` +Close implements io.Closer, and closes the current logfile. + + + +### func (\*Logger) Rotate +``` go +func (l *Logger) Rotate() error +``` +Rotate causes Logger to close the existing log file and immediately create a +new one. This is a helper function for applications that want to initiate +rotations outside of the normal rotation rules, such as in response to +SIGHUP. After rotating, this initiates a cleanup of old log files according +to the normal rules. + +**Example** + +Example of how to rotate in response to SIGHUP. + +Code: + +```go +l := &lumberjack.Logger{} +log.SetOutput(l) +c := make(chan os.Signal, 1) +signal.Notify(c, syscall.SIGHUP) + +go func() { + for { + <-c + l.Rotate() + } +}() +``` + +### func (\*Logger) Write +``` go +func (l *Logger) Write(p []byte) (n int, err error) +``` +Write implements io.Writer. If a write would cause the log file to be larger +than MaxSize, the file is closed, renamed to include a timestamp of the +current time, and a new log file is created using the original log file name. +If the length of the write is greater than MaxSize, an error is returned. + + + + + + + + + +- - - +Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) diff --git a/vendor/gopkg.in/natefinch/lumberjack.v2/chown.go b/vendor/gopkg.in/natefinch/lumberjack.v2/chown.go new file mode 100644 index 0000000000..11d0669723 --- /dev/null +++ b/vendor/gopkg.in/natefinch/lumberjack.v2/chown.go @@ -0,0 +1,11 @@ +// +build !linux + +package lumberjack + +import ( + "os" +) + +func chown(_ string, _ os.FileInfo) error { + return nil +} diff --git a/vendor/gopkg.in/natefinch/lumberjack.v2/chown_linux.go b/vendor/gopkg.in/natefinch/lumberjack.v2/chown_linux.go new file mode 100644 index 0000000000..465f569270 --- /dev/null +++ b/vendor/gopkg.in/natefinch/lumberjack.v2/chown_linux.go @@ -0,0 +1,19 @@ +package lumberjack + +import ( + "os" + "syscall" +) + +// osChown is a var so we can mock it out during tests. +var osChown = os.Chown + +func chown(name string, info os.FileInfo) error { + f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode()) + if err != nil { + return err + } + f.Close() + stat := info.Sys().(*syscall.Stat_t) + return osChown(name, int(stat.Uid), int(stat.Gid)) +} diff --git a/vendor/gopkg.in/natefinch/lumberjack.v2/lumberjack.go b/vendor/gopkg.in/natefinch/lumberjack.v2/lumberjack.go new file mode 100644 index 0000000000..3447cdc056 --- /dev/null +++ b/vendor/gopkg.in/natefinch/lumberjack.v2/lumberjack.go @@ -0,0 +1,541 @@ +// Package lumberjack provides a rolling logger. +// +// Note that this is v2.0 of lumberjack, and should be imported using gopkg.in +// thusly: +// +// import "gopkg.in/natefinch/lumberjack.v2" +// +// The package name remains simply lumberjack, and the code resides at +// https://github.com/natefinch/lumberjack under the v2.0 branch. +// +// Lumberjack is intended to be one part of a logging infrastructure. +// It is not an all-in-one solution, but instead is a pluggable +// component at the bottom of the logging stack that simply controls the files +// to which logs are written. +// +// Lumberjack plays well with any logging package that can write to an +// io.Writer, including the standard library's log package. +// +// Lumberjack assumes that only one process is writing to the output files. +// Using the same lumberjack configuration from multiple processes on the same +// machine will result in improper behavior. +package lumberjack + +import ( + "compress/gzip" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "time" +) + +const ( + backupTimeFormat = "2006-01-02T15-04-05.000" + compressSuffix = ".gz" + defaultMaxSize = 100 +) + +// ensure we always implement io.WriteCloser +var _ io.WriteCloser = (*Logger)(nil) + +// Logger is an io.WriteCloser that writes to the specified filename. +// +// Logger opens or creates the logfile on first Write. If the file exists and +// is less than MaxSize megabytes, lumberjack will open and append to that file. +// If the file exists and its size is >= MaxSize megabytes, the file is renamed +// by putting the current time in a timestamp in the name immediately before the +// file's extension (or the end of the filename if there's no extension). A new +// log file is then created using original filename. +// +// Whenever a write would cause the current log file exceed MaxSize megabytes, +// the current file is closed, renamed, and a new log file created with the +// original name. Thus, the filename you give Logger is always the "current" log +// file. +// +// Backups use the log file name given to Logger, in the form +// `name-timestamp.ext` where name is the filename without the extension, +// timestamp is the time at which the log was rotated formatted with the +// time.Time format of `2006-01-02T15-04-05.000` and the extension is the +// original extension. For example, if your Logger.Filename is +// `/var/log/foo/server.log`, a backup created at 6:30pm on Nov 11 2016 would +// use the filename `/var/log/foo/server-2016-11-04T18-30-00.000.log` +// +// Cleaning Up Old Log Files +// +// Whenever a new logfile gets created, old log files may be deleted. The most +// recent files according to the encoded timestamp will be retained, up to a +// number equal to MaxBackups (or all of them if MaxBackups is 0). Any files +// with an encoded timestamp older than MaxAge days are deleted, regardless of +// MaxBackups. Note that the time encoded in the timestamp is the rotation +// time, which may differ from the last time that file was written to. +// +// If MaxBackups and MaxAge are both 0, no old log files will be deleted. +type Logger struct { + // Filename is the file to write logs to. Backup log files will be retained + // in the same directory. It uses -lumberjack.log in + // os.TempDir() if empty. + Filename string `json:"filename" yaml:"filename"` + + // MaxSize is the maximum size in megabytes of the log file before it gets + // rotated. It defaults to 100 megabytes. + MaxSize int `json:"maxsize" yaml:"maxsize"` + + // MaxAge is the maximum number of days to retain old log files based on the + // timestamp encoded in their filename. Note that a day is defined as 24 + // hours and may not exactly correspond to calendar days due to daylight + // savings, leap seconds, etc. The default is not to remove old log files + // based on age. + MaxAge int `json:"maxage" yaml:"maxage"` + + // MaxBackups is the maximum number of old log files to retain. The default + // is to retain all old log files (though MaxAge may still cause them to get + // deleted.) + MaxBackups int `json:"maxbackups" yaml:"maxbackups"` + + // LocalTime determines if the time used for formatting the timestamps in + // backup files is the computer's local time. The default is to use UTC + // time. + LocalTime bool `json:"localtime" yaml:"localtime"` + + // Compress determines if the rotated log files should be compressed + // using gzip. The default is not to perform compression. + Compress bool `json:"compress" yaml:"compress"` + + size int64 + file *os.File + mu sync.Mutex + + millCh chan bool + startMill sync.Once +} + +var ( + // currentTime exists so it can be mocked out by tests. + currentTime = time.Now + + // os_Stat exists so it can be mocked out by tests. + osStat = os.Stat + + // megabyte is the conversion factor between MaxSize and bytes. It is a + // variable so tests can mock it out and not need to write megabytes of data + // to disk. + megabyte = 1024 * 1024 +) + +// Write implements io.Writer. If a write would cause the log file to be larger +// than MaxSize, the file is closed, renamed to include a timestamp of the +// current time, and a new log file is created using the original log file name. +// If the length of the write is greater than MaxSize, an error is returned. +func (l *Logger) Write(p []byte) (n int, err error) { + l.mu.Lock() + defer l.mu.Unlock() + + writeLen := int64(len(p)) + if writeLen > l.max() { + return 0, fmt.Errorf( + "write length %d exceeds maximum file size %d", writeLen, l.max(), + ) + } + + if l.file == nil { + if err = l.openExistingOrNew(len(p)); err != nil { + return 0, err + } + } + + if l.size+writeLen > l.max() { + if err := l.rotate(); err != nil { + return 0, err + } + } + + n, err = l.file.Write(p) + l.size += int64(n) + + return n, err +} + +// Close implements io.Closer, and closes the current logfile. +func (l *Logger) Close() error { + l.mu.Lock() + defer l.mu.Unlock() + return l.close() +} + +// close closes the file if it is open. +func (l *Logger) close() error { + if l.file == nil { + return nil + } + err := l.file.Close() + l.file = nil + return err +} + +// Rotate causes Logger to close the existing log file and immediately create a +// new one. This is a helper function for applications that want to initiate +// rotations outside of the normal rotation rules, such as in response to +// SIGHUP. After rotating, this initiates compression and removal of old log +// files according to the configuration. +func (l *Logger) Rotate() error { + l.mu.Lock() + defer l.mu.Unlock() + return l.rotate() +} + +// rotate closes the current file, moves it aside with a timestamp in the name, +// (if it exists), opens a new file with the original filename, and then runs +// post-rotation processing and removal. +func (l *Logger) rotate() error { + if err := l.close(); err != nil { + return err + } + if err := l.openNew(); err != nil { + return err + } + l.mill() + return nil +} + +// openNew opens a new log file for writing, moving any old log file out of the +// way. This methods assumes the file has already been closed. +func (l *Logger) openNew() error { + err := os.MkdirAll(l.dir(), 0755) + if err != nil { + return fmt.Errorf("can't make directories for new logfile: %s", err) + } + + name := l.filename() + mode := os.FileMode(0600) + info, err := osStat(name) + if err == nil { + // Copy the mode off the old logfile. + mode = info.Mode() + // move the existing file + newname := backupName(name, l.LocalTime) + if err := os.Rename(name, newname); err != nil { + return fmt.Errorf("can't rename log file: %s", err) + } + + // this is a no-op anywhere but linux + if err := chown(name, info); err != nil { + return err + } + } + + // we use truncate here because this should only get called when we've moved + // the file ourselves. if someone else creates the file in the meantime, + // just wipe out the contents. + f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode) + if err != nil { + return fmt.Errorf("can't open new logfile: %s", err) + } + l.file = f + l.size = 0 + return nil +} + +// backupName creates a new filename from the given name, inserting a timestamp +// between the filename and the extension, using the local time if requested +// (otherwise UTC). +func backupName(name string, local bool) string { + dir := filepath.Dir(name) + filename := filepath.Base(name) + ext := filepath.Ext(filename) + prefix := filename[:len(filename)-len(ext)] + t := currentTime() + if !local { + t = t.UTC() + } + + timestamp := t.Format(backupTimeFormat) + return filepath.Join(dir, fmt.Sprintf("%s-%s%s", prefix, timestamp, ext)) +} + +// openExistingOrNew opens the logfile if it exists and if the current write +// would not put it over MaxSize. If there is no such file or the write would +// put it over the MaxSize, a new file is created. +func (l *Logger) openExistingOrNew(writeLen int) error { + l.mill() + + filename := l.filename() + info, err := osStat(filename) + if os.IsNotExist(err) { + return l.openNew() + } + if err != nil { + return fmt.Errorf("error getting log file info: %s", err) + } + + if info.Size()+int64(writeLen) >= l.max() { + return l.rotate() + } + + file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + // if we fail to open the old log file for some reason, just ignore + // it and open a new log file. + return l.openNew() + } + l.file = file + l.size = info.Size() + return nil +} + +// filename generates the name of the logfile from the current time. +func (l *Logger) filename() string { + if l.Filename != "" { + return l.Filename + } + name := filepath.Base(os.Args[0]) + "-lumberjack.log" + return filepath.Join(os.TempDir(), name) +} + +// millRunOnce performs compression and removal of stale log files. +// Log files are compressed if enabled via configuration and old log +// files are removed, keeping at most l.MaxBackups files, as long as +// none of them are older than MaxAge. +func (l *Logger) millRunOnce() error { + if l.MaxBackups == 0 && l.MaxAge == 0 && !l.Compress { + return nil + } + + files, err := l.oldLogFiles() + if err != nil { + return err + } + + var compress, remove []logInfo + + if l.MaxBackups > 0 && l.MaxBackups < len(files) { + preserved := make(map[string]bool) + var remaining []logInfo + for _, f := range files { + // Only count the uncompressed log file or the + // compressed log file, not both. + fn := f.Name() + if strings.HasSuffix(fn, compressSuffix) { + fn = fn[:len(fn)-len(compressSuffix)] + } + preserved[fn] = true + + if len(preserved) > l.MaxBackups { + remove = append(remove, f) + } else { + remaining = append(remaining, f) + } + } + files = remaining + } + if l.MaxAge > 0 { + diff := time.Duration(int64(24*time.Hour) * int64(l.MaxAge)) + cutoff := currentTime().Add(-1 * diff) + + var remaining []logInfo + for _, f := range files { + if f.timestamp.Before(cutoff) { + remove = append(remove, f) + } else { + remaining = append(remaining, f) + } + } + files = remaining + } + + if l.Compress { + for _, f := range files { + if !strings.HasSuffix(f.Name(), compressSuffix) { + compress = append(compress, f) + } + } + } + + for _, f := range remove { + errRemove := os.Remove(filepath.Join(l.dir(), f.Name())) + if err == nil && errRemove != nil { + err = errRemove + } + } + for _, f := range compress { + fn := filepath.Join(l.dir(), f.Name()) + errCompress := compressLogFile(fn, fn+compressSuffix) + if err == nil && errCompress != nil { + err = errCompress + } + } + + return err +} + +// millRun runs in a goroutine to manage post-rotation compression and removal +// of old log files. +func (l *Logger) millRun() { + for range l.millCh { + // what am I going to do, log this? + _ = l.millRunOnce() + } +} + +// mill performs post-rotation compression and removal of stale log files, +// starting the mill goroutine if necessary. +func (l *Logger) mill() { + l.startMill.Do(func() { + l.millCh = make(chan bool, 1) + go l.millRun() + }) + select { + case l.millCh <- true: + default: + } +} + +// oldLogFiles returns the list of backup log files stored in the same +// directory as the current log file, sorted by ModTime +func (l *Logger) oldLogFiles() ([]logInfo, error) { + files, err := ioutil.ReadDir(l.dir()) + if err != nil { + return nil, fmt.Errorf("can't read log file directory: %s", err) + } + logFiles := []logInfo{} + + prefix, ext := l.prefixAndExt() + + for _, f := range files { + if f.IsDir() { + continue + } + if t, err := l.timeFromName(f.Name(), prefix, ext); err == nil { + logFiles = append(logFiles, logInfo{t, f}) + continue + } + if t, err := l.timeFromName(f.Name(), prefix, ext+compressSuffix); err == nil { + logFiles = append(logFiles, logInfo{t, f}) + continue + } + // error parsing means that the suffix at the end was not generated + // by lumberjack, and therefore it's not a backup file. + } + + sort.Sort(byFormatTime(logFiles)) + + return logFiles, nil +} + +// timeFromName extracts the formatted time from the filename by stripping off +// the filename's prefix and extension. This prevents someone's filename from +// confusing time.parse. +func (l *Logger) timeFromName(filename, prefix, ext string) (time.Time, error) { + if !strings.HasPrefix(filename, prefix) { + return time.Time{}, errors.New("mismatched prefix") + } + if !strings.HasSuffix(filename, ext) { + return time.Time{}, errors.New("mismatched extension") + } + ts := filename[len(prefix) : len(filename)-len(ext)] + return time.Parse(backupTimeFormat, ts) +} + +// max returns the maximum size in bytes of log files before rolling. +func (l *Logger) max() int64 { + if l.MaxSize == 0 { + return int64(defaultMaxSize * megabyte) + } + return int64(l.MaxSize) * int64(megabyte) +} + +// dir returns the directory for the current filename. +func (l *Logger) dir() string { + return filepath.Dir(l.filename()) +} + +// prefixAndExt returns the filename part and extension part from the Logger's +// filename. +func (l *Logger) prefixAndExt() (prefix, ext string) { + filename := filepath.Base(l.filename()) + ext = filepath.Ext(filename) + prefix = filename[:len(filename)-len(ext)] + "-" + return prefix, ext +} + +// compressLogFile compresses the given log file, removing the +// uncompressed log file if successful. +func compressLogFile(src, dst string) (err error) { + f, err := os.Open(src) + if err != nil { + return fmt.Errorf("failed to open log file: %v", err) + } + defer f.Close() + + fi, err := osStat(src) + if err != nil { + return fmt.Errorf("failed to stat log file: %v", err) + } + + if err := chown(dst, fi); err != nil { + return fmt.Errorf("failed to chown compressed log file: %v", err) + } + + // If this file already exists, we presume it was created by + // a previous attempt to compress the log file. + gzf, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fi.Mode()) + if err != nil { + return fmt.Errorf("failed to open compressed log file: %v", err) + } + defer gzf.Close() + + gz := gzip.NewWriter(gzf) + + defer func() { + if err != nil { + os.Remove(dst) + err = fmt.Errorf("failed to compress log file: %v", err) + } + }() + + if _, err := io.Copy(gz, f); err != nil { + return err + } + if err := gz.Close(); err != nil { + return err + } + if err := gzf.Close(); err != nil { + return err + } + + if err := f.Close(); err != nil { + return err + } + if err := os.Remove(src); err != nil { + return err + } + + return nil +} + +// logInfo is a convenience struct to return the filename and its embedded +// timestamp. +type logInfo struct { + timestamp time.Time + os.FileInfo +} + +// byFormatTime sorts by newest time formatted in the name. +type byFormatTime []logInfo + +func (b byFormatTime) Less(i, j int) bool { + return b[i].timestamp.After(b[j].timestamp) +} + +func (b byFormatTime) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +func (b byFormatTime) Len() int { + return len(b) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 0b7adc4459..f35a43983a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -226,6 +226,9 @@ github.com/modern-go/concurrent # github.com/modern-go/reflect2 v1.0.2 ## explicit; go 1.12 github.com/modern-go/reflect2 +# github.com/orandin/lumberjackrus v1.0.1 +## explicit +github.com/orandin/lumberjackrus # github.com/pkg/errors v0.9.1 ## explicit github.com/pkg/errors @@ -446,6 +449,9 @@ golang.zx2c4.com/wireguard/rwcancel golang.zx2c4.com/wireguard/tun golang.zx2c4.com/wireguard/tun/wintun golang.zx2c4.com/wireguard/tun/wintun/memmod +# gopkg.in/natefinch/lumberjack.v2 v2.2.1 +## explicit; go 1.13 +gopkg.in/natefinch/lumberjack.v2 # gopkg.in/yaml.v2 v2.4.0 ## explicit; go 1.15 gopkg.in/yaml.v2