From d98363c0d79b331fcbcf14332157afa6c858b86b Mon Sep 17 00:00:00 2001 From: brent s Date: Sun, 16 Jan 2022 06:55:29 -0500 Subject: [PATCH] Finalizing for 1.2.0 --- .gitignore | 1 + logging/TODO | 5 +- logging/consts_test.go | 39 ++++ logging/consts_windows.go | 2 +- logging/doc.go | 10 +- logging/errs_windows.go | 7 +- logging/funcs.go | 5 +- logging/funcs_file.go | 19 +- logging/funcs_linux_test.go | 270 +++++++++++++++++++++++ logging/funcs_multilogger.go | 90 ++++---- logging/funcs_multilogger_mgr.go | 24 +- logging/funcs_multilogger_mgr_windows.go | 6 +- logging/funcs_std.go | 25 ++- logging/funcs_test.go | 195 ++++++++++++++++ logging/funcs_windows.go | 2 +- logging/funcs_windows_test.go | 205 +++++++++++++++++ logging/funcs_winlogger_windows.go | 32 +-- logging/muiltilogger_linux_test.go | 121 ++++++++++ logging/multilogger_windows_test.go | 118 ++++++++++ logging/types.go | 32 ++- logging/types_linux.go | 4 + logging/types_windows.go | 4 +- 22 files changed, 1106 insertions(+), 110 deletions(-) create mode 100644 logging/consts_test.go create mode 100644 logging/funcs_linux_test.go create mode 100644 logging/funcs_windows_test.go create mode 100644 logging/muiltilogger_linux_test.go create mode 100644 logging/multilogger_windows_test.go diff --git a/.gitignore b/.gitignore index 85bf646..cca14b4 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ # But DO include the actual tests. !_test.go !*_test.go +!*_test_*.go !*_test/ # Output of the go coverage tool, specifically when used with LiteIDE diff --git a/logging/TODO b/logging/TODO index c575174..1f98813 100644 --- a/logging/TODO +++ b/logging/TODO @@ -1,10 +1,11 @@ - Implement code line/func/etc. (only for debug?): https://stackoverflow.com/a/24809646 https://golang.org/pkg/runtime/#Caller + -- log.LlongFile and log.Lshortfile flags don't currently work properly for StdLogger/FileLogger; they refer to the file in logging package rather than the caller. - Suport remote loggers? (eventlog, syslog, systemd) +- JSON logger? YAML logger? XML logger? + - DOCS. -- Done, but flesh out. - -- Unit/Integration tests. diff --git a/logging/consts_test.go b/logging/consts_test.go new file mode 100644 index 0000000..855fadc --- /dev/null +++ b/logging/consts_test.go @@ -0,0 +1,39 @@ +package logging + +import ( + `log` +) + +/* + The following are strings written to the Logger in the various tests. + The %v is populated with the name of the type of Logger. +*/ +const ( + testAlert string = "This is a test ALERT-priority log message for logger %v." + testCrit string = "This is a test CRITICAL-priority (CRIT) log message for logger %v." + testDebug string = "This is a test DEBUG-priority log message for logger %v." + testEmerg string = "This is a test EMERGENCY-priority (EMERG) log message for logger %v." + testErr string = "This is a test ERROR-priority (ERR) log message for logger %v." + testInfo string = "This is a test INFO-priority log message for logger %v." + testNotice string = "This is a test NOTICE-priority log message for logger %v." + testWarning string = "This is a test WARNING-priority log message for logger %v." +) + +// Prefixes to use for tests. +const ( + // TestLogPrefix is used as the initial prefix. + TestLogPrefix string = "LOGGING_TESTRUN" + // TestLogAltPrefix is used as the alternative prefix to Logger.SetPrefix. + TestLogAltPrefix string = "LOGGING_TESTRUN_ALT" +) + +const ( + // EnvVarKeepLog is the env var key/var name to use to suppress removal of FileLogger.Path after tests complete. + EnvVarKeepLog string = "LOGGING_KEEP_TEMPLOG" +) + +const ( + // logFlags are used to set the log flags for StdLogger (and FileLogger.StdLogger). + // logFlags int = log.Ldate | log.Lmicroseconds | log.Llongfile | log.LUTC + logFlags int = log.Ldate | log.Lmicroseconds | log.Lshortfile | log.LUTC +) diff --git a/logging/consts_windows.go b/logging/consts_windows.go index 4c9c364..d96b72d 100644 --- a/logging/consts_windows.go +++ b/logging/consts_windows.go @@ -55,7 +55,7 @@ var DefaultEventID *WinEventID = &WinEventID{ Warning: EventWarning, } -// Default Event IDs for WinEventID. +// Default Event IDs for WinEventID (DefaultEventID, specifically). const ( EventAlert uint32 = 1 << iota EventCrit diff --git a/logging/doc.go b/logging/doc.go index 7c7150e..a7d2275 100644 --- a/logging/doc.go +++ b/logging/doc.go @@ -35,11 +35,11 @@ Note that in the case of a MultiLogger, err (if not nil) will be a (r00t2.io/gou logging.Logger types also have the following methods: - DoDebug(d bool) - SetPrefix(p string) - GetPrefix() (p string) - Setup() - Shutdown() + DoDebug(d bool) (err error) + SetPrefix(p string) (err error) + GetPrefix() (p string, err error) + Setup() (err error) + Shutdown() (err error) In some cases, Logger.Setup and Logger.Shutdown are no-ops. In other cases, they perform necessary initialization/cleanup and closing of the logger. It is recommended to *always* run Setup and Shutdown before and after using, respectively, regardless of the actual logging.Logger type. diff --git a/logging/errs_windows.go b/logging/errs_windows.go index 00feef0..623bccf 100644 --- a/logging/errs_windows.go +++ b/logging/errs_windows.go @@ -6,7 +6,10 @@ import ( ) var ( + // ErrBadBinPath is returned if installing a binary-registered Event Log source instead of using EventCreate.exe. ErrBadBinPath error = errors.New("evaluated binary path does not actually exist") - ErrBadPerms error = errors.New("access denied when attempting to register Event Log source") - ErrBadEid error = errors.New(fmt.Sprintf("event IDs must be between %v and %v inclusive", EIDMin, EIDMax)) + // ErrBadPerms is returned if an access denied error is received when attempting to register, write to, close, etc. a source without proper perms. + ErrBadPerms error = errors.New("access denied when attempting to register Event Log source") + // ErrBadEid is returned if an event ID is within an invalid range. + ErrBadEid error = errors.New(fmt.Sprintf("event IDs must be between %v and %v inclusive", EIDMin, EIDMax)) ) diff --git a/logging/funcs.go b/logging/funcs.go index 0f4e751..641b53d 100644 --- a/logging/funcs.go +++ b/logging/funcs.go @@ -6,13 +6,16 @@ import ( // testOpen attempts to open a file for writing to test for suitability as a LogFile path. func testOpen(path string) (success bool, err error) { + var f *os.File // Example #2, https://golang.org/pkg/os/#OpenFile if f, err = os.OpenFile(path, appendFlags, logPerm); err != nil { return } - defer f.Close() + if err = f.Close(); err != nil { + return + } success = true diff --git a/logging/funcs_file.go b/logging/funcs_file.go index 623dabb..f2f1c9e 100644 --- a/logging/funcs_file.go +++ b/logging/funcs_file.go @@ -3,7 +3,6 @@ package logging import ( `errors` "fmt" - "io" `io/fs` "log" "os" @@ -13,8 +12,6 @@ import ( // Setup sets up/configures a FileLogger and prepares it for use. func (l *FileLogger) Setup() (err error) { - var multi io.Writer - // This uses a shared handle across the import. We don't want that. // l.Logger = log.Default() if l.Prefix != "" { @@ -25,19 +22,7 @@ func (l *FileLogger) Setup() (err error) { return } - // https://stackoverflow.com/a/36719588/733214 - switch { - case l.EnableStdErr && l.EnableStdOut: - multi = io.MultiWriter(os.Stdout, os.Stderr, l.writer) - case l.EnableStdErr: - multi = io.MultiWriter(os.Stderr, l.writer) - case l.EnableStdOut: - multi = io.MultiWriter(os.Stdout, l.writer) - default: - multi = l.writer - } - - l.Logger = log.New(multi, l.Prefix, l.LogFlags) + l.Logger = log.New(l.writer, l.Prefix, l.LogFlags) // l.Logger.SetOutput(multi) return @@ -50,6 +35,8 @@ func (l *FileLogger) Shutdown() (err error) { if !errors.Is(err, fs.ErrClosed) { return } + err = nil + return err } return diff --git a/logging/funcs_linux_test.go b/logging/funcs_linux_test.go new file mode 100644 index 0000000..e43c9b3 --- /dev/null +++ b/logging/funcs_linux_test.go @@ -0,0 +1,270 @@ +package logging + +import ( + `fmt` + `os` + `testing` +) + +/* + TestSysDLogger tests functionality for SystemDLogger. +*/ +func TestSysDLogger(t *testing.T) { + + var l *SystemDLogger + var ltype string = "SystemDLogger" + var prefix string + var err error + + l = &SystemDLogger{ + EnableDebug: true, + Prefix: TestLogPrefix, + } + + if err = l.Setup(); err != nil { + t.Fatalf("error when running Setup: %v", err.Error()) + } + + t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l) + + if err = l.Alert(testAlert, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Crit(testCrit, ltype); err != nil { + t.Fatalf("error for Crit: %v", err.Error()) + } + + if err = l.Debug(testDebug, ltype); err != nil { + t.Fatalf("error for Debug: %v", err.Error()) + } + + if err = l.Emerg(testEmerg, ltype); err != nil { + t.Fatalf("error for Emerg: %v", err.Error()) + } + + if err = l.Err(testErr, ltype); err != nil { + t.Fatalf("error for Err: %v", err.Error()) + } + + if err = l.Info(testInfo, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Notice(testNotice, ltype); err != nil { + t.Fatalf("error for Notice: %v", err.Error()) + } + + if err = l.Warning(testWarning, ltype); err != nil { + t.Fatalf("error for Warning: %v", err.Error()) + } + + if prefix, err = l.GetPrefix(); err != nil { + t.Fatalf("error when fetching prefix: %v", err.Error()) + } + + if prefix != TestLogPrefix { + t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix) + } + if err = l.SetPrefix(TestLogAltPrefix); err != nil { + t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error()) + } else { + _ = l.SetPrefix(TestLogPrefix) + } + + if err = l.DoDebug(false); err != nil { + t.Fatalf("error when changing debug to false: %v", err.Error()) + } else if l.EnableDebug { + t.Fatalf("did not properly set Debug filter state") + } else { + _ = l.DoDebug(true) + } + + if err = l.Shutdown(); err != nil { + t.Fatalf("Error when running Shutdown: %v", err.Error()) + } + + t.Logf("Logger %v passed all logging targets.", ltype) +} + +/* + TestSyslogLogger tests functionality for SyslogLogger. +*/ +func TestSyslogLogger(t *testing.T) { + + var l *SyslogLogger + var ltype string = "SyslogLogger" + var prefix string + var err error + + l = &SyslogLogger{ + EnableDebug: true, + Prefix: TestLogPrefix, + } + + if err = l.Setup(); err != nil { + t.Fatalf("error when running Setup: %v", err.Error()) + } + + t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l) + + if err = l.Alert(testAlert, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Crit(testCrit, ltype); err != nil { + t.Fatalf("error for Crit: %v", err.Error()) + } + + if err = l.Debug(testDebug, ltype); err != nil { + t.Fatalf("error for Debug: %v", err.Error()) + } + + if err = l.Emerg(testEmerg, ltype); err != nil { + t.Fatalf("error for Emerg: %v", err.Error()) + } + + if err = l.Err(testErr, ltype); err != nil { + t.Fatalf("error for Err: %v", err.Error()) + } + + if err = l.Info(testInfo, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Notice(testNotice, ltype); err != nil { + t.Fatalf("error for Notice: %v", err.Error()) + } + + if err = l.Warning(testWarning, ltype); err != nil { + t.Fatalf("error for Warning: %v", err.Error()) + } + + if prefix, err = l.GetPrefix(); err != nil { + t.Fatalf("error when fetching prefix: %v", err.Error()) + } + + if prefix != TestLogPrefix { + t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix) + } + if err = l.SetPrefix(TestLogAltPrefix); err != nil { + t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error()) + } else { + _ = l.SetPrefix(TestLogPrefix) + } + + if err = l.DoDebug(false); err != nil { + t.Fatalf("error when changing debug to false: %v", err.Error()) + } else if l.EnableDebug { + t.Fatalf("did not properly set Debug filter state") + } else { + _ = l.DoDebug(true) + } + + if err = l.Shutdown(); err != nil { + t.Fatalf("Error when running Shutdown: %v", err.Error()) + } + + t.Logf("Logger %v passed all logging targets.", ltype) +} + +// TestDefaultLogger tests GetLogger. +func TestDefaultLogger(t *testing.T) { + + var l Logger + var tempfile *os.File + var tempfilePath string + var keepLog bool + var ltype string + var prefix string + var testPrefix string + var err error + + if tempfile, err = os.CreateTemp("", ".LOGGINGTEST_*"); err != nil { + t.Fatalf("error when creating temporary log file '%v': %v", tempfile.Name(), err.Error()) + } + tempfilePath = tempfile.Name() + // We can close the handler immediately; we don't need it since the FileLogger opens its own. + if err = tempfile.Close(); err != nil { + t.Fatalf("error when closing handler for temporary log file '%v': %v", tempfile.Name(), err.Error()) + } + + if l, err = GetLogger(true, TestLogPrefix, logFlags, tempfilePath); err != nil { + t.Fatalf("error when spawning default Linux logger via GetLogger: %v", err.Error()) + } + + ltype = fmt.Sprintf("%T", l) + + t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l) + + if err = l.Alert(testAlert, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Crit(testCrit, ltype); err != nil { + t.Fatalf("error for Crit: %v", err.Error()) + } + + if err = l.Debug(testDebug, ltype); err != nil { + t.Fatalf("error for Debug: %v", err.Error()) + } + + if err = l.Emerg(testEmerg, ltype); err != nil { + t.Fatalf("error for Emerg: %v", err.Error()) + } + + if err = l.Err(testErr, ltype); err != nil { + t.Fatalf("error for Err: %v", err.Error()) + } + + if err = l.Info(testInfo, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Notice(testNotice, ltype); err != nil { + t.Fatalf("error for Notice: %v", err.Error()) + } + + if err = l.Warning(testWarning, ltype); err != nil { + t.Fatalf("error for Warning: %v", err.Error()) + } + + if prefix, err = l.GetPrefix(); err != nil { + t.Fatalf("error when fetching prefix: %v", err.Error()) + } + + if ltype == "StdLogger" || ltype == "FileLogger" { // StdLogger (and thus FileLogger) adds a space at the end. + testPrefix = TestLogPrefix + " " + } else { + testPrefix = TestLogPrefix + } + + if prefix != testPrefix { + t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix) + } + if err = l.SetPrefix(TestLogAltPrefix); err != nil { + t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error()) + } else { + _ = l.SetPrefix(TestLogPrefix) + } + + if err = l.DoDebug(false); err != nil { + t.Fatalf("error when changing debug to false: %v", err.Error()) + } else { + _ = l.DoDebug(true) + } + + if err = l.Shutdown(); err != nil { + t.Fatalf("Error when running Shutdown: %v", err.Error()) + } + + _, keepLog = os.LookupEnv(EnvVarKeepLog) + + if !keepLog { + if err = os.Remove(tempfilePath); err != nil { + t.Fatalf("error when removing temporary log file '%v': %v", tempfilePath, err.Error()) + } + } + + t.Logf("Logger %v passed all logging targets.", ltype) +} diff --git a/logging/funcs_multilogger.go b/logging/funcs_multilogger.go index bf1dc88..ee9d712 100644 --- a/logging/funcs_multilogger.go +++ b/logging/funcs_multilogger.go @@ -1,6 +1,8 @@ package logging import ( + `errors` + `fmt` `sync` `r00t2.io/goutils/multierr` @@ -12,16 +14,17 @@ func (m *MultiLogger) Setup() (err error) { var wg sync.WaitGroup var errs *multierr.MultiError = multierr.NewMultiError(nil) - for _, l := range m.Loggers { + for logName, l := range m.Loggers { wg.Add(1) - go func() { + go func(logger Logger, lName string) { var err2 error defer wg.Done() - if err2 = l.Setup(); err2 != nil { + if err2 = logger.Setup(); err2 != nil { + errs.AddError(errors.New(fmt.Sprintf("error on Setup for logger %v; follows (may be out of order):", lName))) errs.AddError(err2) err2 = nil } - }() + }(l, logName) } wg.Wait() @@ -40,16 +43,17 @@ func (m *MultiLogger) Shutdown() (err error) { var wg sync.WaitGroup var errs *multierr.MultiError = multierr.NewMultiError(nil) - for _, l := range m.Loggers { + for logName, l := range m.Loggers { wg.Add(1) - go func() { + go func(logger Logger, lName string) { var err2 error defer wg.Done() - if err2 = l.Shutdown(); err2 != nil { + if err2 = logger.Shutdown(); err2 != nil { + errs.AddError(errors.New(fmt.Sprintf("error on Shutdown for logger %v; follows (may be out of order):", lName))) errs.AddError(err2) err2 = nil } - }() + }(l, logName) } wg.Wait() @@ -87,16 +91,17 @@ func (m *MultiLogger) DoDebug(d bool) (err error) { m.EnableDebug = d - for _, l := range m.Loggers { + for logName, l := range m.Loggers { wg.Add(1) - go func() { + go func(logger Logger, lName string) { var err2 error defer wg.Done() if err2 = l.DoDebug(d); err2 != nil { + errs.AddError(errors.New(fmt.Sprintf("error on DoDebug for logger %v; follows (may be out of order):", lName))) errs.AddError(err2) err2 = nil } - }() + }(l, logName) } wg.Wait() @@ -121,16 +126,17 @@ func (m *MultiLogger) SetPrefix(prefix string) (err error) { m.Prefix = prefix - for _, l := range m.Loggers { + for logName, l := range m.Loggers { wg.Add(1) - go func() { + go func(logger Logger, lName string) { var err2 error defer wg.Done() if err2 = l.SetPrefix(prefix); err != nil { + errs.AddError(errors.New(fmt.Sprintf("error on SetPrefix for logger %v; follows (may be out of order):", lName))) errs.AddError(err2) err2 = nil } - }() + }(l, logName) } wg.Wait() @@ -149,15 +155,16 @@ func (m *MultiLogger) Alert(s string, v ...interface{}) (err error) { var wg sync.WaitGroup var e *multierr.MultiError = multierr.NewMultiError(nil) - for _, l := range m.Loggers { + for logName, l := range m.Loggers { wg.Add(1) - go func(logObj Logger, msg string, rplc ...interface{}) { + go func(logObj Logger, msg, lName string, rplc ...interface{}) { defer wg.Done() if err = logObj.Alert(msg, rplc...); err != nil { + e.AddError(errors.New(fmt.Sprintf("error on Alert for logger %v; follows (may be out of order):", lName))) e.AddError(err) err = nil } - }(l, s, v...) + }(l, s, logName, v...) } wg.Wait() @@ -175,15 +182,16 @@ func (m *MultiLogger) Crit(s string, v ...interface{}) (err error) { var wg sync.WaitGroup var e *multierr.MultiError = multierr.NewMultiError(nil) - for _, l := range m.Loggers { + for logName, l := range m.Loggers { wg.Add(1) - go func(logObj Logger, msg string, rplc ...interface{}) { + go func(logObj Logger, msg, lName string, rplc ...interface{}) { defer wg.Done() if err = logObj.Crit(msg, rplc...); err != nil { + e.AddError(errors.New(fmt.Sprintf("error on Crit for logger %v; follows (may be out of order):", lName))) e.AddError(err) err = nil } - }(l, s, v...) + }(l, s, logName, v...) } wg.Wait() @@ -201,15 +209,16 @@ func (m *MultiLogger) Debug(s string, v ...interface{}) (err error) { var wg sync.WaitGroup var e *multierr.MultiError = multierr.NewMultiError(nil) - for _, l := range m.Loggers { + for logName, l := range m.Loggers { wg.Add(1) - go func(logObj Logger, msg string, rplc ...interface{}) { + go func(logObj Logger, msg, lName string, rplc ...interface{}) { defer wg.Done() if err = logObj.Debug(msg, rplc...); err != nil { + e.AddError(errors.New(fmt.Sprintf("error on Debug for logger %v; follows (may be out of order):", lName))) e.AddError(err) err = nil } - }(l, s, v...) + }(l, s, logName, v...) } wg.Wait() @@ -226,15 +235,16 @@ func (m *MultiLogger) Emerg(s string, v ...interface{}) (err error) { var wg sync.WaitGroup var e *multierr.MultiError = multierr.NewMultiError(nil) - for _, l := range m.Loggers { + for logName, l := range m.Loggers { wg.Add(1) - go func(logObj Logger, msg string, rplc ...interface{}) { + go func(logObj Logger, msg, lName string, rplc ...interface{}) { defer wg.Done() if err = logObj.Emerg(msg, rplc...); err != nil { + e.AddError(errors.New(fmt.Sprintf("error on Emerg for logger %v; follows (may be out of order):", lName))) e.AddError(err) err = nil } - }(l, s, v...) + }(l, s, logName, v...) } wg.Wait() @@ -251,15 +261,16 @@ func (m *MultiLogger) Err(s string, v ...interface{}) (err error) { var wg sync.WaitGroup var e *multierr.MultiError = multierr.NewMultiError(nil) - for _, l := range m.Loggers { + for logName, l := range m.Loggers { wg.Add(1) - go func(logObj Logger, msg string, rplc ...interface{}) { + go func(logObj Logger, msg, lName string, rplc ...interface{}) { defer wg.Done() if err = logObj.Err(msg, rplc...); err != nil { + e.AddError(errors.New(fmt.Sprintf("error on Err for logger %v; follows (may be out of order):", lName))) e.AddError(err) err = nil } - }(l, s, v...) + }(l, s, logName, v...) } wg.Wait() @@ -277,15 +288,16 @@ func (m *MultiLogger) Info(s string, v ...interface{}) (err error) { var wg sync.WaitGroup var e *multierr.MultiError = multierr.NewMultiError(nil) - for _, l := range m.Loggers { + for logName, l := range m.Loggers { wg.Add(1) - go func(logObj Logger, msg string, rplc ...interface{}) { + go func(logObj Logger, msg, lName string, rplc ...interface{}) { defer wg.Done() if err = logObj.Info(msg, rplc...); err != nil { + e.AddError(errors.New(fmt.Sprintf("error on Info for logger %v; follows (may be out of order):", lName))) e.AddError(err) err = nil } - }(l, s, v...) + }(l, s, logName, v...) } wg.Wait() @@ -303,15 +315,16 @@ func (m *MultiLogger) Notice(s string, v ...interface{}) (err error) { var wg sync.WaitGroup var e *multierr.MultiError = multierr.NewMultiError(nil) - for _, l := range m.Loggers { + for logName, l := range m.Loggers { wg.Add(1) - go func(logObj Logger, msg string, rplc ...interface{}) { + go func(logObj Logger, msg, lName string, rplc ...interface{}) { defer wg.Done() if err = logObj.Notice(msg, rplc...); err != nil { + e.AddError(errors.New(fmt.Sprintf("error on Notice for logger %v; follows (may be out of order):", lName))) e.AddError(err) err = nil } - }(l, s, v...) + }(l, s, logName, v...) } wg.Wait() @@ -329,15 +342,16 @@ func (m *MultiLogger) Warning(s string, v ...interface{}) (err error) { var wg sync.WaitGroup var e *multierr.MultiError = multierr.NewMultiError(nil) - for _, l := range m.Loggers { + for logName, l := range m.Loggers { wg.Add(1) - go func(logObj Logger, msg string, rplc ...interface{}) { + go func(logObj Logger, msg, lName string, rplc ...interface{}) { defer wg.Done() if err = logObj.Warning(msg, rplc...); err != nil { + e.AddError(errors.New(fmt.Sprintf("error on Warning for logger %v; follows (may be out of order):", lName))) e.AddError(err) err = nil } - }(l, s, v...) + }(l, s, logName, v...) } wg.Wait() diff --git a/logging/funcs_multilogger_mgr.go b/logging/funcs_multilogger_mgr.go index a08ba02..dfdc5bf 100644 --- a/logging/funcs_multilogger_mgr.go +++ b/logging/funcs_multilogger_mgr.go @@ -37,9 +37,15 @@ func GetMultiLogger(enableDebug bool, prefix string) (m *MultiLogger) { identifier is a string to use to identify the added StdLogger in MultiLogger.Loggers. If empty, one will be automatically generated. + enableStdOut indicates that messages should be logged to STDOUT; + it is *strongly encouraged* to set at least one of enableStdOut or enableStdErr to true. + + enableStdErr indicates that messages should be logged to STDERR; + it is *strongly encouraged* to set at least one of enableStdErr or enableStdOut to true. + See GetLogger's logConfigFlags argument and StdLogger.LogFlags for details on logFlags. */ -func (m *MultiLogger) AddStdLogger(identifier string, logFlags int) (err error) { +func (m *MultiLogger) AddStdLogger(identifier string, enableStdOut, enableStdErr bool, logFlags int) (err error) { var exists bool var prefix string @@ -54,10 +60,12 @@ func (m *MultiLogger) AddStdLogger(identifier string, logFlags int) (err error) } m.Loggers[identifier] = &StdLogger{ - Logger: nil, - EnableDebug: m.EnableDebug, - Prefix: m.Prefix, - LogFlags: logFlags, + Logger: nil, + EnableDebug: m.EnableDebug, + Prefix: m.Prefix, + LogFlags: logFlags, + EnableStdOut: enableStdOut, + EnableStdErr: enableStdErr, } if err = m.Loggers[identifier].Setup(); err != nil { return @@ -80,7 +88,7 @@ func (m *MultiLogger) AddStdLogger(identifier string, logFlags int) (err error) logfilePath is a string for the path to the desired logfile. */ -func (m *MultiLogger) AddFileLogger(identifier string, enableStdOut, enableStdErr bool, logFlags int, logfilePath string) (err error) { +func (m *MultiLogger) AddFileLogger(identifier string, logFlags int, logfilePath string) (err error) { var exists bool var success bool @@ -122,9 +130,7 @@ func (m *MultiLogger) AddFileLogger(identifier string, enableStdOut, enableStdEr Prefix: m.Prefix, LogFlags: logFlags, }, - Path: logfilePath, - EnableStdOut: enableStdOut, - EnableStdErr: enableStdErr, + Path: logfilePath, } if err = m.Loggers[identifier].Setup(); err != nil { return diff --git a/logging/funcs_multilogger_mgr_windows.go b/logging/funcs_multilogger_mgr_windows.go index 4b95d50..1354499 100644 --- a/logging/funcs_multilogger_mgr_windows.go +++ b/logging/funcs_multilogger_mgr_windows.go @@ -65,7 +65,7 @@ func (m *MultiLogger) AddDefaultLogger(identifier string, eventIDs *WinEventID, See GetLogger for details. */ -func (m *MultiLogger) AddWinLogger(identifier, source string, eventIDs *WinEventID) (err error) { +func (m *MultiLogger) AddWinLogger(identifier string, eventIDs *WinEventID) (err error) { var exists bool var prefix string @@ -84,9 +84,9 @@ func (m *MultiLogger) AddWinLogger(identifier, source string, eventIDs *WinEvent } m.Loggers[identifier] = &WinLogger{ - Prefix: source, + Prefix: m.Prefix, EnableDebug: m.EnableDebug, - eids: eventIDs, + EIDs: eventIDs, } if err = m.Loggers[identifier].Setup(); err != nil { return diff --git a/logging/funcs_std.go b/logging/funcs_std.go index dab726f..7816429 100644 --- a/logging/funcs_std.go +++ b/logging/funcs_std.go @@ -2,6 +2,7 @@ package logging import ( "fmt" + `io` `log` `os` `strings` @@ -13,14 +14,34 @@ import ( */ func (l *StdLogger) Setup() (err error) { + var multi io.Writer + // This uses a shared handle across the import. We don't want that. // l.Logger = log.Default() if l.Prefix != "" { l.Prefix = strings.TrimRight(l.Prefix, " ") + " " // l.Logger.SetPrefix(l.Prefix) } - // (stdlib).log.std is returned by log.Default(), which uses os.Stderr. - l.Logger = log.New(os.Stderr, l.Prefix, l.LogFlags) + // (stdlib).log.std is returned by log.Default(), which uses os.Stderr but we have flags for that. + // https://stackoverflow.com/a/36719588/733214 + switch { + case l.EnableStdErr && l.EnableStdOut: + multi = io.MultiWriter(os.Stdout, os.Stderr) + case l.EnableStdErr: + multi = os.Stderr + case l.EnableStdOut: + multi = os.Stdout + default: + multi = nil + } + if multi != nil { + l.Logger = log.New(multi, l.Prefix, l.LogFlags) + } else { + // This honestly should throw an error. + l.Logger = &log.Logger{} + l.Logger.SetPrefix(l.Prefix) + l.Logger.SetFlags(l.LogFlags) + } return } diff --git a/logging/funcs_test.go b/logging/funcs_test.go index 2b43acc..95f72dc 100644 --- a/logging/funcs_test.go +++ b/logging/funcs_test.go @@ -1 +1,196 @@ package logging + +import ( + `os` + `testing` +) + +/* + TestStdLogger tests functionality for StdLogger. +*/ +func TestStdLogger(t *testing.T) { + + var l *StdLogger + var ltype string = "StdLogger" + var prefix string + var err error + + l = &StdLogger{ + EnableDebug: true, + Prefix: TestLogPrefix, + LogFlags: logFlags, + EnableStdOut: false, + EnableStdErr: true, + } + + if err = l.Setup(); err != nil { + t.Fatalf("error when running Setup: %v", err.Error()) + } + + t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l) + + if err = l.Alert(testAlert, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Crit(testCrit, ltype); err != nil { + t.Fatalf("error for Crit: %v", err.Error()) + } + + if err = l.Debug(testDebug, ltype); err != nil { + t.Fatalf("error for Debug: %v", err.Error()) + } + + if err = l.Emerg(testEmerg, ltype); err != nil { + t.Fatalf("error for Emerg: %v", err.Error()) + } + + if err = l.Err(testErr, ltype); err != nil { + t.Fatalf("error for Err: %v", err.Error()) + } + + if err = l.Info(testInfo, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Notice(testNotice, ltype); err != nil { + t.Fatalf("error for Notice: %v", err.Error()) + } + + if err = l.Warning(testWarning, ltype); err != nil { + t.Fatalf("error for Warning: %v", err.Error()) + } + + if prefix, err = l.GetPrefix(); err != nil { + t.Fatalf("error when fetching prefix: %v", err.Error()) + } + + if prefix != (TestLogPrefix + " ") { // StdLogger adds a space at the end. + t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix) + } + if err = l.SetPrefix(TestLogAltPrefix); err != nil { + t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error()) + } else { + _ = l.SetPrefix(TestLogPrefix) + } + + if err = l.DoDebug(false); err != nil { + t.Fatalf("error when changing debug to false: %v", err.Error()) + } else if l.EnableDebug { + t.Fatalf("did not properly set Debug filter state") + } else { + _ = l.DoDebug(true) + } + + if err = l.Shutdown(); err != nil { + t.Fatalf("Error when running Shutdown: %v", err.Error()) + } + + t.Logf("Logger %v passed all logging targets.", ltype) +} + +/* + TestFileLogger tests functionality for FileLogger. + If the appropriate env var is set (see the EnvVarKeepLog constant), the temporary log file that is created will not be cleaned up. +*/ +func TestFileLogger(t *testing.T) { + + var l *FileLogger + var ltype string = "FileLogger" + var prefix string + var tempfile *os.File + var tempfilePath string + var keepLog bool + var err error + + if tempfile, err = os.CreateTemp("", ".LOGGINGTEST_*"); err != nil { + t.Fatalf("error when creating temporary log file '%v': %v", tempfile.Name(), err.Error()) + } + tempfilePath = tempfile.Name() + // We can close the handler immediately; we don't need it since the FileLogger opens its own. + if err = tempfile.Close(); err != nil { + t.Fatalf("error when closing handler for temporary log file '%v': %v", tempfile.Name(), err.Error()) + } + + l = &FileLogger{ + StdLogger: StdLogger{ + EnableDebug: true, + Prefix: TestLogPrefix, + LogFlags: logFlags, + }, + Path: tempfilePath, + } + + if err = l.Setup(); err != nil { + t.Fatalf("error when running Setup: %v", err.Error()) + } + + t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l) + + if err = l.Alert(testAlert, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Crit(testCrit, ltype); err != nil { + t.Fatalf("error for Crit: %v", err.Error()) + } + + if err = l.Debug(testDebug, ltype); err != nil { + t.Fatalf("error for Debug: %v", err.Error()) + } + + if err = l.Emerg(testEmerg, ltype); err != nil { + t.Fatalf("error for Emerg: %v", err.Error()) + } + + if err = l.Err(testErr, ltype); err != nil { + t.Fatalf("error for Err: %v", err.Error()) + } + + if err = l.Info(testInfo, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Notice(testNotice, ltype); err != nil { + t.Fatalf("error for Notice: %v", err.Error()) + } + + if err = l.Warning(testWarning, ltype); err != nil { + t.Fatalf("error for Warning: %v", err.Error()) + } + + if prefix, err = l.GetPrefix(); err != nil { + t.Fatalf("error when fetching prefix: %v", err.Error()) + } + + if prefix != (TestLogPrefix + " ") { // StdLogger (and thus FileLogger) adds a space at the end. + t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix) + } + if err = l.SetPrefix(TestLogAltPrefix); err != nil { + t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error()) + } else { + _ = l.SetPrefix(TestLogPrefix) + } + + if err = l.DoDebug(false); err != nil { + t.Fatalf("error when changing debug to false: %v", err.Error()) + } else if l.EnableDebug { + t.Fatalf("did not properly set Debug filter state") + } else { + _ = l.DoDebug(true) + } + + if err = l.Shutdown(); err != nil { + t.Fatalf("Error when running Shutdown: %v", err.Error()) + } + + _, keepLog = os.LookupEnv(EnvVarKeepLog) + + if !keepLog { + if err = os.Remove(tempfilePath); err != nil { + t.Fatalf("error when removing temporary log file '%v': %v", tempfilePath, err.Error()) + } + } + + t.Logf("Logger %v passed all logging targets.", ltype) +} diff --git a/logging/funcs_windows.go b/logging/funcs_windows.go index 284437c..07436ed 100644 --- a/logging/funcs_windows.go +++ b/logging/funcs_windows.go @@ -90,7 +90,7 @@ func GetLogger(enableDebug bool, source string, eventIDs *WinEventID, logConfigF logger = &WinLogger{ Prefix: source, EnableDebug: enableDebug, - eids: eventIDs, + EIDs: eventIDs, } } else { if logFlags.HasFlag(LogFile) { diff --git a/logging/funcs_windows_test.go b/logging/funcs_windows_test.go new file mode 100644 index 0000000..4635467 --- /dev/null +++ b/logging/funcs_windows_test.go @@ -0,0 +1,205 @@ +package logging + +import ( + `fmt` + `os` + `testing` +) + +/* + TestWinLogger tests functionality for WinLogger. + You will probably need to run it with an Administrator shell. +*/ +func TestWinLogger(t *testing.T) { + + var l *WinLogger + var ltype string = "WinLogger" + var prefix string + var exists bool + var err error + + l = &WinLogger{ + EnableDebug: true, + Prefix: TestLogPrefix, + RemoveOnClose: true, + EIDs: DefaultEventID, + } + + if exists, err = l.Exists(); err != nil { + t.Fatalf("error when checking for existence of registered Event Log source '%v': %v", TestLogPrefix, err.Error()) + } else { + t.Logf("Prefix (source) '%v' exists before setup: %v", TestLogPrefix, exists) + } + + if err = l.Setup(); err != nil { + t.Fatalf("error when running Setup: %v", err.Error()) + } + + if exists, err = l.Exists(); err != nil { + t.Fatalf("error when checking for existence of registered Event Log source '%v': %v", TestLogPrefix, err.Error()) + } else { + t.Logf("Prefix (source) '%v' exists after setup: %v", TestLogPrefix, exists) + } + + t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l) + + if err = l.Alert(testAlert, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Crit(testCrit, ltype); err != nil { + t.Fatalf("error for Crit: %v", err.Error()) + } + + if err = l.Debug(testDebug, ltype); err != nil { + t.Fatalf("error for Debug: %v", err.Error()) + } + + if err = l.Emerg(testEmerg, ltype); err != nil { + t.Fatalf("error for Emerg: %v", err.Error()) + } + + if err = l.Err(testErr, ltype); err != nil { + t.Fatalf("error for Err: %v", err.Error()) + } + + if err = l.Info(testInfo, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Notice(testNotice, ltype); err != nil { + t.Fatalf("error for Notice: %v", err.Error()) + } + + if err = l.Warning(testWarning, ltype); err != nil { + t.Fatalf("error for Warning: %v", err.Error()) + } + + if prefix, err = l.GetPrefix(); err != nil { + t.Fatalf("error when fetching prefix: %v", err.Error()) + } + + if prefix != TestLogPrefix { + t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix) + } + if err = l.SetPrefix(TestLogAltPrefix); err != nil { + t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error()) + } else { + _ = l.SetPrefix(TestLogPrefix) + } + + if err = l.DoDebug(false); err != nil { + t.Fatalf("error when changing debug to false: %v", err.Error()) + } else if l.EnableDebug { + t.Fatalf("did not properly set Debug filter state") + } else { + _ = l.DoDebug(true) + } + + if err = l.Shutdown(); err != nil { + t.Fatalf("Error when running Shutdown: %v", err.Error()) + } + + t.Logf("Logger %v passed all logging targets.", ltype) +} + +// TestDefaultLogger tests GetLogger. +func TestDefaultLogger(t *testing.T) { + + var l Logger + var tempfile *os.File + var tempfilePath string + var keepLog bool + var ltype string + var prefix string + var testPrefix string + var err error + + if tempfile, err = os.CreateTemp("", ".LOGGINGTEST_*"); err != nil { + t.Fatalf("error when creating temporary log file '%v': %v", tempfile.Name(), err.Error()) + } + tempfilePath = tempfile.Name() + // We can close the handler immediately; we don't need it since the FileLogger opens its own. + if err = tempfile.Close(); err != nil { + t.Fatalf("error when closing handler for temporary log file '%v': %v", tempfile.Name(), err.Error()) + } + + if l, err = GetLogger(true, TestLogPrefix, DefaultEventID, logFlags, tempfilePath); err != nil { + t.Fatalf("error when spawning default Windows logger via GetLogger: %v", err.Error()) + } + + ltype = fmt.Sprintf("%T", l) + + t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l) + + if err = l.Alert(testAlert, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Crit(testCrit, ltype); err != nil { + t.Fatalf("error for Crit: %v", err.Error()) + } + + if err = l.Debug(testDebug, ltype); err != nil { + t.Fatalf("error for Debug: %v", err.Error()) + } + + if err = l.Emerg(testEmerg, ltype); err != nil { + t.Fatalf("error for Emerg: %v", err.Error()) + } + + if err = l.Err(testErr, ltype); err != nil { + t.Fatalf("error for Err: %v", err.Error()) + } + + if err = l.Info(testInfo, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Notice(testNotice, ltype); err != nil { + t.Fatalf("error for Notice: %v", err.Error()) + } + + if err = l.Warning(testWarning, ltype); err != nil { + t.Fatalf("error for Warning: %v", err.Error()) + } + + if prefix, err = l.GetPrefix(); err != nil { + t.Fatalf("error when fetching prefix: %v", err.Error()) + } + + if ltype == "StdLogger" || ltype == "FileLogger" { // StdLogger (and thus FileLogger) adds a space at the end. + testPrefix = TestLogPrefix + " " + } else { + testPrefix = TestLogPrefix + } + + if prefix != testPrefix { + t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix) + } + if err = l.SetPrefix(TestLogAltPrefix); err != nil { + t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error()) + } else { + _ = l.SetPrefix(TestLogPrefix) + } + + if err = l.DoDebug(false); err != nil { + t.Fatalf("error when changing debug to false: %v", err.Error()) + } else { + _ = l.DoDebug(true) + } + + if err = l.Shutdown(); err != nil { + t.Fatalf("Error when running Shutdown: %v", err.Error()) + } + + _, keepLog = os.LookupEnv(EnvVarKeepLog) + + if !keepLog { + if err = os.Remove(tempfilePath); err != nil { + t.Fatalf("error when removing temporary log file '%v': %v", tempfilePath, err.Error()) + } + } + + t.Logf("Logger %v passed all logging targets.", ltype) +} diff --git a/logging/funcs_winlogger_windows.go b/logging/funcs_winlogger_windows.go index b76367c..f8edf9a 100644 --- a/logging/funcs_winlogger_windows.go +++ b/logging/funcs_winlogger_windows.go @@ -29,14 +29,14 @@ func (l *WinLogger) Setup() (err error) { Since we use eventcreate, all Event IDs must be 1 <= eid <= 1000. */ for _, eid := range []uint32{ - l.eids.Alert, - l.eids.Crit, - l.eids.Debug, - l.eids.Emerg, - l.eids.Err, - l.eids.Info, - l.eids.Notice, - l.eids.Warning, + l.EIDs.Alert, + l.EIDs.Crit, + l.EIDs.Debug, + l.EIDs.Emerg, + l.EIDs.Err, + l.EIDs.Info, + l.EIDs.Notice, + l.EIDs.Warning, } { if !((eid <= EIDMax) && (EIDMin <= eid)) { err = ErrBadEid @@ -204,7 +204,7 @@ func (l *WinLogger) Alert(s string, v ...interface{}) (err error) { } // Treat ALERT as Warning - err = l.elog.Warning(l.eids.Alert, msg) + err = l.elog.Warning(l.EIDs.Alert, msg) return } @@ -221,7 +221,7 @@ func (l *WinLogger) Crit(s string, v ...interface{}) (err error) { } // Treat CRIT as Error - err = l.elog.Error(l.eids.Crit, msg) + err = l.elog.Error(l.EIDs.Crit, msg) return } @@ -242,7 +242,7 @@ func (l *WinLogger) Debug(s string, v ...interface{}) (err error) { } // Treat DEBUG as Info - err = l.elog.Info(l.eids.Debug, msg) + err = l.elog.Info(l.EIDs.Debug, msg) return @@ -260,7 +260,7 @@ func (l *WinLogger) Emerg(s string, v ...interface{}) (err error) { } // Treat EMERG as Error - err = l.elog.Error(l.eids.Emerg, msg) + err = l.elog.Error(l.EIDs.Emerg, msg) return @@ -277,7 +277,7 @@ func (l *WinLogger) Err(s string, v ...interface{}) (err error) { msg = s } - err = l.elog.Error(l.eids.Err, msg) + err = l.elog.Error(l.EIDs.Err, msg) return @@ -294,7 +294,7 @@ func (l *WinLogger) Info(s string, v ...interface{}) (err error) { msg = s } - err = l.elog.Info(l.eids.Info, msg) + err = l.elog.Info(l.EIDs.Info, msg) return @@ -312,7 +312,7 @@ func (l *WinLogger) Notice(s string, v ...interface{}) (err error) { } // Treat NOTICE as Info - err = l.elog.Info(l.eids.Notice, msg) + err = l.elog.Info(l.EIDs.Notice, msg) return @@ -329,7 +329,7 @@ func (l *WinLogger) Warning(s string, v ...interface{}) (err error) { msg = s } - err = l.elog.Warning(l.eids.Warning, msg) + err = l.elog.Warning(l.EIDs.Warning, msg) return diff --git a/logging/muiltilogger_linux_test.go b/logging/muiltilogger_linux_test.go new file mode 100644 index 0000000..28a7dcf --- /dev/null +++ b/logging/muiltilogger_linux_test.go @@ -0,0 +1,121 @@ +package logging + +import ( + `os` + `testing` +) + +// TestMultiLogger tests GetMultiLogger and MultiLogger methods. +func TestMultiLogger(t *testing.T) { + + var l *MultiLogger + var tempfile *os.File + var tempfilePath string + var keepLog bool + var ltype string = "MultiLogger" + var prefix string + var testPrefix string + var err error + + if tempfile, err = os.CreateTemp("", ".LOGGINGTEST_*"); err != nil { + t.Fatalf("error when creating temporary log file '%v': %v", tempfile.Name(), err.Error()) + } + tempfilePath = tempfile.Name() + // We can close the handler immediately; we don't need it since the FileLogger opens its own. + if err = tempfile.Close(); err != nil { + t.Fatalf("error when closing handler for temporary log file '%v': %v", tempfile.Name(), err.Error()) + } + + l = GetMultiLogger(true, TestLogPrefix) + + if err = l.AddStdLogger("StdLogger", false, true, logFlags); err != nil { + t.Fatalf("error when adding StdLogger to MultiLogger: %v", err.Error()) + } + if err = l.AddFileLogger("FileLogger", logFlags, tempfilePath); err != nil { + t.Fatalf("error when adding FileLogger to MultiLogger: %v", err.Error()) + } + + if err = l.AddDefaultLogger("DefaultLogger", logFlags, tempfilePath); err != nil { + t.Fatalf("error when adding default logger to MultiLogger: %v", err.Error()) + } + + if err = l.AddSysdLogger("SystemDLogger"); err != nil { + t.Fatalf("error when adding SystemDLogger to MultiLogger: %v", err.Error()) + } + if err = l.AddSyslogLogger("SyslogLogger"); err != nil { + t.Fatalf("error when adding SyslogLogger to MultiLogger: %v", err.Error()) + } + + t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l) + + if err = l.Alert(testAlert, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Crit(testCrit, ltype); err != nil { + t.Fatalf("error for Crit: %v", err.Error()) + } + + if err = l.Debug(testDebug, ltype); err != nil { + t.Fatalf("error for Debug: %v", err.Error()) + } + + if err = l.Emerg(testEmerg, ltype); err != nil { + t.Fatalf("error for Emerg: %v", err.Error()) + } + + if err = l.Err(testErr, ltype); err != nil { + t.Fatalf("error for Err: %v", err.Error()) + } + + if err = l.Info(testInfo, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Notice(testNotice, ltype); err != nil { + t.Fatalf("error for Notice: %v", err.Error()) + } + + if err = l.Warning(testWarning, ltype); err != nil { + t.Fatalf("error for Warning: %v", err.Error()) + } + + if prefix, err = l.GetPrefix(); err != nil { + t.Fatalf("error when fetching prefix: %v", err.Error()) + } + + if ltype == "StdLogger" || ltype == "FileLogger" { // StdLogger (and thus FileLogger) adds a space at the end. + testPrefix = TestLogPrefix + " " + } else { + testPrefix = TestLogPrefix + } + + if prefix != testPrefix { + t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix) + } + if err = l.SetPrefix(TestLogAltPrefix); err != nil { + t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error()) + } else { + _ = l.SetPrefix(TestLogPrefix) + } + + if err = l.DoDebug(false); err != nil { + t.Fatalf("error when changing debug to false: %v", err.Error()) + } else { + _ = l.DoDebug(true) + } + + if err = l.Shutdown(); err != nil { + t.Fatalf("Error when running Shutdown: %v", err.Error()) + } + + _, keepLog = os.LookupEnv(EnvVarKeepLog) + + if !keepLog { + if err = os.Remove(tempfilePath); err != nil { + t.Fatalf("error when removing temporary log file '%v': %v", tempfilePath, err.Error()) + } + } + + t.Logf("Logger %v passed all logging targets.", ltype) +} diff --git a/logging/multilogger_windows_test.go b/logging/multilogger_windows_test.go new file mode 100644 index 0000000..d558d44 --- /dev/null +++ b/logging/multilogger_windows_test.go @@ -0,0 +1,118 @@ +package logging + +import ( + `os` + `testing` +) + +// TestMultiLogger tests GetMultiLogger and MultiLogger methods. +func TestMultiLogger(t *testing.T) { + + var l *MultiLogger + var tempfile *os.File + var tempfilePath string + var keepLog bool + var ltype string = "MultiLogger" + var prefix string + var testPrefix string + var err error + + if tempfile, err = os.CreateTemp("", ".LOGGINGTEST_*"); err != nil { + t.Fatalf("error when creating temporary log file '%v': %v", tempfile.Name(), err.Error()) + } + tempfilePath = tempfile.Name() + // We can close the handler immediately; we don't need it since the FileLogger opens its own. + if err = tempfile.Close(); err != nil { + t.Fatalf("error when closing handler for temporary log file '%v': %v", tempfile.Name(), err.Error()) + } + + l = GetMultiLogger(true, TestLogPrefix) + + if err = l.AddStdLogger("StdLogger", false, true, logFlags); err != nil { + t.Fatalf("error when adding StdLogger to MultiLogger: %v", err.Error()) + } + if err = l.AddFileLogger("FileLogger", logFlags, tempfilePath); err != nil { + t.Fatalf("error when adding FileLogger to MultiLogger: %v", err.Error()) + } + + if err = l.AddDefaultLogger("DefaultLogger", DefaultEventID, logFlags, tempfilePath); err != nil { + t.Fatalf("error when adding default logger to MultiLogger: %v", err.Error()) + } + + if err = l.AddWinLogger("WinLogger", DefaultEventID); err != nil { + t.Fatalf("error when adding WinLogger to MultiLogger: %v", err.Error()) + } + + t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l) + + if err = l.Alert(testAlert, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Crit(testCrit, ltype); err != nil { + t.Fatalf("error for Crit: %v", err.Error()) + } + + if err = l.Debug(testDebug, ltype); err != nil { + t.Fatalf("error for Debug: %v", err.Error()) + } + + if err = l.Emerg(testEmerg, ltype); err != nil { + t.Fatalf("error for Emerg: %v", err.Error()) + } + + if err = l.Err(testErr, ltype); err != nil { + t.Fatalf("error for Err: %v", err.Error()) + } + + if err = l.Info(testInfo, ltype); err != nil { + t.Fatalf("error for Alert: %v", err.Error()) + } + + if err = l.Notice(testNotice, ltype); err != nil { + t.Fatalf("error for Notice: %v", err.Error()) + } + + if err = l.Warning(testWarning, ltype); err != nil { + t.Fatalf("error for Warning: %v", err.Error()) + } + + if prefix, err = l.GetPrefix(); err != nil { + t.Fatalf("error when fetching prefix: %v", err.Error()) + } + + if ltype == "StdLogger" || ltype == "FileLogger" { // StdLogger (and thus FileLogger) adds a space at the end. + testPrefix = TestLogPrefix + " " + } else { + testPrefix = TestLogPrefix + } + + if prefix != testPrefix { + t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix) + } + if err = l.SetPrefix(TestLogAltPrefix); err != nil { + t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error()) + } else { + _ = l.SetPrefix(TestLogPrefix) + } + + if err = l.DoDebug(false); err != nil { + t.Fatalf("error when changing debug to false: %v", err.Error()) + } else { + _ = l.DoDebug(true) + } + + if err = l.Shutdown(); err != nil { + t.Fatalf("Error when running Shutdown: %v", err.Error()) + } + + _, keepLog = os.LookupEnv(EnvVarKeepLog) + + if !keepLog { + if err = os.Remove(tempfilePath); err != nil { + t.Fatalf("error when removing temporary log file '%v': %v", tempfilePath, err.Error()) + } + } + + t.Logf("Logger %v passed all logging targets.", ltype) +} diff --git a/logging/types.go b/logging/types.go index a32b9f3..02da98f 100644 --- a/logging/types.go +++ b/logging/types.go @@ -48,6 +48,26 @@ type StdLogger struct { You will need to run *StdLogger.Shutdown and then *StdLogger.Setup again if you wish to change this. */ LogFlags int + /* + EnableStdOut is true if the log will send to STDOUT. + If false (default), no output will be written to STDOUT. + You will need to run StdLogger.Shutdown and then StdLogger.Setup again if you wish to change this. + + If EnableStdOut is false and EnableStdErr is false, no logging output will occur by default + and StdLogger.Logger will be largely useless. + It will be up to you to modify the underlying log.Logger to behave as you want. + */ + EnableStdOut bool + /* + EnableStdErr is true if the log will send to STDERR. + If false (default), no output will be written to STDERR. + You will need to run StdLogger.Shutdown and then StdLogger.Setup again if you wish to change this. + + If EnableStdErr is false and EnableStdOut is false, no logging output will occur by default + and StdLogger.Logger will be largely useless. + It will be up to you to modify the underlying log.Logger to behave as you want. + */ + EnableStdErr bool } /* @@ -62,18 +82,6 @@ type FileLogger struct { StdLogger // Path is the path to the logfile. Path string - /* - EnableStdOut is true if the log will send to STDOUT as well as the file (and STDERR if FileLogger.EnableStdErr == true). - If false (default), it will only (silently) write to the log file. - You will need to run FileLogger.Shutdown and then FileLogger.Setup again if you wish to change this. - */ - EnableStdOut bool - /* - EnableStdErr is true if the log will send to STDERR as well as the file (and STDOUT if FileLogger.EnableStdOut == true). - If false (default), it will only (silently) write to the log file. - You will need to run *FileLogger.Shutdown and then *FileLogger.Setup again if you wish to change this. - */ - EnableStdErr bool // writer is used for the writing out of the log file. writer *os.File } diff --git a/logging/types_linux.go b/logging/types_linux.go index a352ffa..03f93b2 100644 --- a/logging/types_linux.go +++ b/logging/types_linux.go @@ -4,11 +4,15 @@ import ( `log/syslog` ) +/* + SystemDLogger (yes, I'm aware it's actually written as "systemd") writes to journald on systemd-enabled systems. +*/ type SystemDLogger struct { EnableDebug bool Prefix string } +// SyslogLogger writes to syslog on syslog-enabled systems. type SyslogLogger struct { EnableDebug bool Prefix string diff --git a/logging/types_windows.go b/logging/types_windows.go index f065526..9db291d 100644 --- a/logging/types_windows.go +++ b/logging/types_windows.go @@ -39,8 +39,8 @@ type WinLogger struct { RemoveOnClose bool // elog is the actual writer to the Event Log. elog *eventlog.Log - // eids is used to look up what event ID to use when writing to a WinLogger.elog. - eids *WinEventID + // EIDs is used to look up what event ID to use when writing to a WinLogger.elog. + EIDs *WinEventID } /*