diff --git a/go.mod b/go.mod index a9c808e..2fa1a8d 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,7 @@ go 1.16 require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf - r00t2.io/sysutils v0.0.0-20210224054841-55ac47c86928 + github.com/google/uuid v1.3.0 + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e + r00t2.io/sysutils v1.1.1 ) diff --git a/go.sum b/go.sum index 4da76d9..44ee240 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,8 @@ github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/jszwec/csvutil v1.5.0/go.mod h1:Rpu7Uu9giO9subDyMCIQfHVDuLrcaC36UA4YcJjGBkg= -r00t2.io/sysutils v0.0.0-20210224054841-55ac47c86928 h1:aYEn20eguqsmqT3J9VjkzdhyPwmOVDGzzffcEfV18a4= -r00t2.io/sysutils v0.0.0-20210224054841-55ac47c86928/go.mod h1:XzJkBF6SHAODEszJlOcjtGoTHwYnZZNmseA6PyOujes= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +r00t2.io/sysutils v1.1.1 h1:q2P5u50HIIRk6muCPo1Gpapy6sNT4oaB1l2O/C/mi3A= +r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o= diff --git a/logging/consts.go b/logging/consts.go index 5c45941..bd7f61e 100644 --- a/logging/consts.go +++ b/logging/consts.go @@ -5,7 +5,10 @@ import ( ) const ( + // logPerm is the octal mode to use for testing the file. logPerm os.FileMode = 0600 + // logPrefix is the default log prefix. logPrefix string = "GOLANG PROGRAM" - appendFlags int = os.O_APPEND|os.O_CREATE|os.O_WRONLY + // appendFlags are the flags used for testing the file (and opening/writing). + appendFlags int = os.O_APPEND | os.O_CREATE | os.O_WRONLY ) diff --git a/logging/consts_linux.go b/logging/consts_linux.go index 223faf3..354947d 100644 --- a/logging/consts_linux.go +++ b/logging/consts_linux.go @@ -7,20 +7,28 @@ import ( ) const ( - devlog string = "/dev/log" + // devlog is the path to the syslog char device. + devlog string = "/dev/log" + // syslogFacility is the facility to use; it's a little like a context or scope if you think of it in those terms. syslogFacility syslog.Priority = syslog.LOG_USER ) -// Flags for logger configuration +// Flags for logger configuration. These are used internally. const ( + // LogUndefined indicates an undefined Logger type. LogUndefined bitmask.MaskBit = 1 << iota + // LogJournald flags a SystemDLogger Logger type. LogJournald + // LogSyslog flags a SyslogLogger Logger type. LogSyslog + // LogFile flags a FileLogger Logger type. LogFile + // LogStdout flags a StdLogger Logger type. LogStdout ) var ( + // defLogPaths indicates default log paths. defLogPaths = []string{ "/var/log/golang/program.log", "~/.local/log/golang/program.log", diff --git a/logging/consts_windows.go b/logging/consts_windows.go index 22804f7..e674112 100644 --- a/logging/consts_windows.go +++ b/logging/consts_windows.go @@ -6,24 +6,30 @@ import ( `regexp` ) -// Flags for logger configuration +// Flags for logger configuration. These are used internally. const ( + // LogUndefined indicates an undefined Logger type. LogUndefined types.MaskBit = 1 << iota + // LogWinLogger indicates a WinLogger Logger type (Event Log). LogWinLogger + // LogFile flags a FileLogger Logger type. LogFile + // LogStdout flags a StdLogger Logger type. LogStdout ) var ( + // defLogPaths indicates default log paths. defLogPaths = []string{ filepath.Join(os.Getenv("ALLUSERSPROFILE"), "golang", "program.log"), // C:\ProgramData\log\golang\program.log filepath.Join(os.Getenv("LOCALAPPDATA"), "log", "golang", "program.log"), // C:\Users\\AppData\Local\log\golang\program.log } ) +// ptrnSourceExists is a regex pattern to check for a registry entry (Event Log entry) already existing. var ptrnSourceExists *regexp.Regexp = regexp.MustCompile(`registry\skey\salready\sexists$`) -// Default WinEventID +// Default WinEventID, (can be) used in GetLogger and MultiLogger.AddWinLogger. var DefaultEventID *WinEventID = &WinEventID{ Alert: EventAlert, Crit: EventCrit, diff --git a/logging/doc.go b/logging/doc.go new file mode 100644 index 0000000..9b9321f --- /dev/null +++ b/logging/doc.go @@ -0,0 +1,43 @@ +/* +Package logging implements and presents various loggers under a unified interface, making them completely swappable. + +These particular loggers (logging.Logger) available are: + + StdLogger + FileLogger + SystemDLogger (Linux only) + SyslogLogger (Linux only) + WinLogger (Windows only) + +There is a sixth type of logging.Logger, MultiLogger, that allows for multiple loggers to be written to with a single call. + +Every logging.Logger type has the following methods that correspond to certain "levels". + + Alert(s string, v ...interface{}) (err error) + Crit(s string, v ...interface{}) (err error) + Debug(s string, v ...interface{}) (err error) + Emerg(s string, v ...interface{}) (err error) + Err(s string, v ...interface{}) (err error) + Info(s string, v ...interface{}) (err error) + Notice(s string, v ...interface{}) (err error) + Warning(s string, v ...interface{}) (err error) + +Not all loggers implement the concept of levels, so approximations are made when/where possible. + +In each of the above methods, s is the message that is optionally in a fmt.Sprintf-compatible format. +If it is, the values to fmt.Sprintf can be passed as v. + +Note that in the case of a MultiLogger, err (if not nil) will be a (r00t2.io/goutils/)multierr.MultiError. + +logging.Logger types also have the following methods: + + DoDebug(d bool) + SetPrefix(p string) + GetPrefix() (p string) + Setup() + Shutdown() + +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. +*/ +package logging diff --git a/logging/errs.go b/logging/errs.go new file mode 100644 index 0000000..cc441c5 --- /dev/null +++ b/logging/errs.go @@ -0,0 +1,17 @@ +package logging + +import ( + `errors` +) + +var ( + // ErrExistingLogger indicates that the user attempted to add a Logger to a MultiLogger using an already-existing identifier. + ErrExistingLogger error = errors.New("a Logger with that identifier already exists; please remove it first") + /* + ErrInvalidFile indicates that the user attempted to add a FileLogger to a MultiLogger but the file doesn't exist, + exists with too restrictive perms to write/append to, and/or could not be created. + */ + ErrInvalidFile error = errors.New("a FileLogger was requested but the file does not exist and cannot be created") + // ErrNoEntry indicates that the user attempted to MultiLogger.RemoveLogger a Logger but one by that identifier does not exist. + ErrNoEntry error = errors.New("the Logger specified to be removed does not exist") +) diff --git a/logging/errs_linux.go b/logging/errs_linux.go new file mode 100644 index 0000000..6c86265 --- /dev/null +++ b/logging/errs_linux.go @@ -0,0 +1,18 @@ +package logging + +import ( + `errors` + `fmt` +) + +var ( + // ErrNoSysD indicates that the user attempted to add a SystemDLogger to a MultiLogger but systemd is unavailable. + ErrNoSysD error = errors.New("a systemd (journald) Logger was requested but systemd is unavailable on this system") + // ErrNoSyslog indicates that the user attempted to add a SyslogLogger to a MultiLogger but syslog's logger device is unavailable. + ErrNoSyslog error = errors.New("a Syslog Logger was requested but Syslog is unavailable on this system") + /* + ErrInvalidDevLog indicates that the user attempted to add a SyslogLogger to a MultiLogger but + the Syslog char device file is... not actually a char device file. + */ + ErrInvalidDevLog error = errors.New(fmt.Sprintf("a Syslog Logger was requested but %v is not a valid logger handle", devlog)) +) diff --git a/logging/funcs.go b/logging/funcs.go index c10edb9..0f4e751 100644 --- a/logging/funcs.go +++ b/logging/funcs.go @@ -4,6 +4,7 @@ import ( "os" ) +// 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 diff --git a/logging/funcs_file.go b/logging/funcs_file.go index 7d8585a..ebd0fe1 100644 --- a/logging/funcs_file.go +++ b/logging/funcs_file.go @@ -7,6 +7,7 @@ import ( "os" ) +// Setup sets up/configures a FileLogger and prepares it for use. func (l *FileLogger) Setup() { var err error @@ -21,6 +22,7 @@ func (l *FileLogger) Setup() { l.Logger.SetOutput(multi) } +// Shutdown cleanly shuts down a FileLogger. func (l *FileLogger) Shutdown() { var err error @@ -31,19 +33,26 @@ func (l *FileLogger) Shutdown() { } +// GetPrefix returns the prefix used by this FileLogger. func (l *FileLogger) GetPrefix() string { return l.Prefix } +/* + DoDebug sets the debug state of this FileLogger. + Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log. +*/ func (l *FileLogger) DoDebug(d bool) { l.EnableDebug = d } +// SetPrefix sets the prefix for this FileLogger. func (l *FileLogger) SetPrefix(prefix string) { l.Prefix = prefix l.Logger.SetPrefix(prefix) } +// Alert writes an ALERT-level message to this FileLogger. func (l *FileLogger) Alert(s string, v ...interface{}) (err error) { var msg string @@ -59,6 +68,7 @@ func (l *FileLogger) Alert(s string, v ...interface{}) (err error) { return } +// Crit writes an CRITICAL-level message to this FileLogger. func (l *FileLogger) Crit(s string, v ...interface{}) (err error) { var msg string @@ -74,6 +84,7 @@ func (l *FileLogger) Crit(s string, v ...interface{}) (err error) { return } +// Debug writes a DEBUG-level message to this FileLogger. func (l *FileLogger) Debug(s string, v ...interface{}) (err error) { if !l.EnableDebug { @@ -93,6 +104,7 @@ func (l *FileLogger) Debug(s string, v ...interface{}) (err error) { return } +// Emerg writes an EMERGENCY-level message to this FileLogger. func (l *FileLogger) Emerg(s string, v ...interface{}) (err error) { var msg string @@ -108,6 +120,7 @@ func (l *FileLogger) Emerg(s string, v ...interface{}) (err error) { return } +// Err writes an ERROR-level message to this FileLogger. func (l *FileLogger) Err(s string, v ...interface{}) (err error) { var msg string @@ -123,6 +136,7 @@ func (l *FileLogger) Err(s string, v ...interface{}) (err error) { return } +// Info writes an INFO-level message to this FileLogger. func (l *FileLogger) Info(s string, v ...interface{}) (err error) { var msg string @@ -138,6 +152,7 @@ func (l *FileLogger) Info(s string, v ...interface{}) (err error) { return } +// Notice writes a NOTICE-level message to this FileLogger. func (l *FileLogger) Notice(s string, v ...interface{}) (err error) { var msg string @@ -153,6 +168,7 @@ func (l *FileLogger) Notice(s string, v ...interface{}) (err error) { return } +// Warning writes a WARNING/WARN-level message to this FileLogger. func (l *FileLogger) Warning(s string, v ...interface{}) (err error) { var msg string @@ -168,6 +184,7 @@ func (l *FileLogger) Warning(s string, v ...interface{}) (err error) { return } +// renderWrite prepares/formats a log message to be written to this FileLogger. func (l *FileLogger) renderWrite(msg, prio string) { s := fmt.Sprintf("[%v] %v", prio, msg) diff --git a/logging/funcs_linux.go b/logging/funcs_linux.go index bc6d13c..f765828 100644 --- a/logging/funcs_linux.go +++ b/logging/funcs_linux.go @@ -6,6 +6,7 @@ import ( `path` sysd `github.com/coreos/go-systemd/journal` + `r00t2.io/goutils/bitmask` `r00t2.io/sysutils/paths` ) @@ -17,16 +18,24 @@ var ( /* GetLogger returns an instance of Logger that best suits your system's capabilities. + If enableDebug is true, debug messages (which according to your program may or may not contain sensitive data) are rendered and written. + If prefix is "\x00" (a null byte), then the default logging prefix will be used. If anything else, even an empty string, is specified then that will be used instead for the prefix. + logPaths is an (optional) list of strings to use as paths to test for writing. If the file can be created/written to, it will be used (assuming you have no higher-level loggers available). Only the first logPaths entry that "works" will be used, later entries will be ignored. + If you want to log to multiple files simultaneously, use a MultiLogger instead. + + If you call GetLogger, you will only get a single ("best") logger your system supports. + If you want to log to multiple Logger destinations at once (or want to log to an explicit Logger type), + use GetMultiLogger. */ func GetLogger(enableDebug bool, prefix string, logPaths ...string) (logger Logger, err error) { var logPath string - var logFlags types.MaskBit + var logFlags bitmask.MaskBit // Configure system-supported logger(s). if sysd.Enabled() { @@ -67,7 +76,7 @@ func GetLogger(enableDebug bool, prefix string, logPaths ...string) (logger Logg break } else { dirPath := path.Dir(p) - if err = paths.MakeDirIfNotExist(&dirPath); err != nil { + if err = paths.MakeDirIfNotExist(dirPath); err != nil { continue } if success, err = testOpen(p); err != nil { diff --git a/logging/funcs_multilogger.go b/logging/funcs_multilogger.go new file mode 100644 index 0000000..5864b37 --- /dev/null +++ b/logging/funcs_multilogger.go @@ -0,0 +1,275 @@ +package logging + +import ( + `sync` + + `r00t2.io/goutils/multierr` +) + +// Setup sets up/configures a MultiLogger (and all its MultiLogger.Loggers) and prepares it for use. +func (m *MultiLogger) Setup() { + + var wg sync.WaitGroup + + for _, l := range m.Loggers { + wg.Add(1) + go func() { + defer wg.Done() + l.Setup() + }() + } + + wg.Wait() +} + +// Shutdown cleanly shuts down a MultiLogger (and all its MultiLogger.Loggers). +func (m *MultiLogger) Shutdown() { + + var wg sync.WaitGroup + + for _, l := range m.Loggers { + wg.Add(1) + go func() { + defer wg.Done() + l.Shutdown() + }() + } + + wg.Wait() +} + +// GetPrefix returns the prefix used by this MultiLogger (and all its MultiLogger.Loggers). +func (m *MultiLogger) GetPrefix() string { + + return m.Prefix + +} + +/* + DoDebug sets the debug state of this MultiLogger (and all its MultiLogger.Loggers). + Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log. + + If you had a logger-specific EnableDebug set, you will need to re-set it to your desired state after running this method. +*/ +func (m *MultiLogger) DoDebug(d bool) { + + var wg sync.WaitGroup + + m.EnableDebug = d + + for _, l := range m.Loggers { + wg.Add(1) + go func() { + defer wg.Done() + l.DoDebug(d) + }() + } + + wg.Wait() +} + +/* + SetPrefix sets the prefix for this MultiLogger (and all its MultiLogger.Loggers). + + If you had a logger-specific Prefix set, you will need to re-set it to your desired prefix after running this method. +*/ +func (m *MultiLogger) SetPrefix(prefix string) { + + var wg sync.WaitGroup + + m.Prefix = prefix + + for _, l := range m.Loggers { + wg.Add(1) + go func() { + defer wg.Done() + l.SetPrefix(prefix) + }() + } + + wg.Wait() +} + +// Alert writes an ALERT-level message to this MultiLogger (and all its MultiLogger.Loggers). +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 { + wg.Add(1) + go func(logObj Logger, msg string, rplc ...interface{}) { + if err = logObj.Alert(msg, rplc...); err != nil { + e.AddError(err) + err = nil + } + }(l, s, v) + } + + wg.Wait() + + err = e + + return +} + +// Crit writes an CRITICAL-level message to this MultiLogger (and all its MultiLogger.Loggers). +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 { + wg.Add(1) + go func(logObj Logger, msg string, rplc ...interface{}) { + if err = logObj.Crit(msg, rplc...); err != nil { + e.AddError(err) + err = nil + } + }(l, s, v) + } + + wg.Wait() + + err = e + + return +} + +// Debug writes a DEBUG-level message to this MultiLogger (and all its MultiLogger.Loggers). +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 { + wg.Add(1) + go func(logObj Logger, msg string, rplc ...interface{}) { + if err = logObj.Debug(msg, rplc...); err != nil { + e.AddError(err) + err = nil + } + }(l, s, v) + } + + wg.Wait() + + err = e + + return +} + +// Emerg writes an EMERGENCY-level message to this MultiLogger (and all its MultiLogger.Loggers). +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 { + wg.Add(1) + go func(logObj Logger, msg string, rplc ...interface{}) { + if err = logObj.Emerg(msg, rplc...); err != nil { + e.AddError(err) + err = nil + } + }(l, s, v) + } + + wg.Wait() + + err = e + + return +} + +// Err writes an ERROR-level message to this MultiLogger (and all its MultiLogger.Loggers). +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 { + wg.Add(1) + go func(logObj Logger, msg string, rplc ...interface{}) { + if err = logObj.Err(msg, rplc...); err != nil { + e.AddError(err) + err = nil + } + }(l, s, v) + } + + wg.Wait() + + err = e + + return +} + +// Info writes an INFO-level message to this MultiLogger (and all its MultiLogger.Loggers). +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 { + wg.Add(1) + go func(logObj Logger, msg string, rplc ...interface{}) { + if err = logObj.Info(msg, rplc...); err != nil { + e.AddError(err) + err = nil + } + }(l, s, v) + } + + wg.Wait() + + err = e + + return +} + +// Notice writes a NOTICE-level message to this MultiLogger (and all its MultiLogger.Loggers). +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 { + wg.Add(1) + go func(logObj Logger, msg string, rplc ...interface{}) { + if err = logObj.Notice(msg, rplc...); err != nil { + e.AddError(err) + err = nil + } + }(l, s, v) + } + + wg.Wait() + + err = e + + return +} + +// Warning writes a WARNING/WARN-level message to this MultiLogger (and all its MultiLogger.Loggers). +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 { + wg.Add(1) + go func(logObj Logger, msg string, rplc ...interface{}) { + if err = logObj.Warning(msg, rplc...); err != nil { + e.AddError(err) + err = nil + } + }(l, s, v) + } + + wg.Wait() + + err = e + + return +} diff --git a/logging/funcs_multilogger_mgr.go b/logging/funcs_multilogger_mgr.go new file mode 100644 index 0000000..13a71e8 --- /dev/null +++ b/logging/funcs_multilogger_mgr.go @@ -0,0 +1,133 @@ +package logging + +import ( + `path` + + `github.com/google/uuid` + `r00t2.io/sysutils/paths` +) + +/* + GetMultiLogger returns a MultiLogger. + If you call GetLogger, you will only get a single ("best") logger your system supports. + If you want to log to multiple Logger destinations at once (or want to log to an explicit Logger type), + use GetMultiLogger. + + Remember to add at least one Logger (e.g. MultiLogger.AddStdLogger), otherwise no entries will actually be logged. + + If you want to modify e.g. if debug is enabled for a specific Logger, reference the Logger directly (e.g. MultiLogger.Loggers[identifier].SetDebug(false)). +*/ +func GetMultiLogger(enableDebug bool, prefix string) (m *MultiLogger) { + + m = &MultiLogger{ + EnableDebug: enableDebug, + Prefix: "", + Loggers: make(map[string]Logger), + } + if prefix != "\x00" { + m.Prefix = prefix + } + + return +} + +/* + AddStdLogger adds a StdLogger to a MultiLogger. + + identifier is a string to use to identify the added StdLogger in MultiLogger.Loggers. + If empty, one will be automatically generated. +*/ +func (m *MultiLogger) AddStdLogger(identifier string) (err error) { + + var exists bool + + if identifier == "" { + identifier = uuid.New().String() + } + + if _, exists = m.Loggers[identifier]; exists { + err = ErrExistingLogger + return + } + + m.Loggers[identifier] = &StdLogger{ + Logger: nil, + EnableDebug: m.EnableDebug, + Prefix: m.Prefix, + } + m.Loggers[identifier].Setup() + + m.Loggers[identifier].Info("logger initialized of type %T with prefix %v", m.Loggers[identifier], m.Loggers[identifier].GetPrefix()) + + return +} + +/* + AddFileLogger adds a FileLogger to a MultiLogger. + + identifier is a string to use to identify the added FileLogger in MultiLogger.Loggers. + If empty, one will be automatically generated. + + logfilePath is a string for the path to the desired logfile. +*/ +func (m *MultiLogger) AddFileLogger(identifier, logfilePath string) (err error) { + + var exists bool + var success bool + var dirPath string + + if identifier == "" { + identifier = uuid.New().String() + } + + if _, exists = m.Loggers[identifier]; exists { + err = ErrExistingLogger + return + } + + if exists, _ = paths.RealPathExists(&logfilePath); !exists { + if success, err = testOpen(logfilePath); err != nil { + return + } else if !success { + dirPath = path.Dir(logfilePath) + if err = paths.MakeDirIfNotExist(dirPath); err != nil { + return + } + if success, err = testOpen(dirPath); err != nil { + return + } else if !success { + err = ErrInvalidFile + return + } + } + } + + m.Loggers[identifier] = &FileLogger{ + StdLogger: StdLogger{ + Logger: nil, + EnableDebug: m.EnableDebug, + Prefix: m.Prefix, + }, + Path: logfilePath, + } + m.Loggers[identifier].Setup() + + m.Loggers[identifier].Info("logger initialized of type %T with prefix %v", m.Loggers[identifier], m.Loggers[identifier].GetPrefix()) + + return +} + +// RemoveLogger will let you remove a Logger from MultiLogger.Loggers. +func (m *MultiLogger) RemoveLogger(identifier string) (err error) { + + var exists bool + + if _, exists = m.Loggers[identifier]; !exists { + err = ErrNoEntry + return + } + + delete(m.Loggers, identifier) + + return +} diff --git a/logging/funcs_multilogger_mgr_linux.go b/logging/funcs_multilogger_mgr_linux.go new file mode 100644 index 0000000..bc9f4fb --- /dev/null +++ b/logging/funcs_multilogger_mgr_linux.go @@ -0,0 +1,118 @@ +package logging + +import ( + `os` + + sysd `github.com/coreos/go-systemd/journal` + `github.com/google/uuid` + `r00t2.io/sysutils/paths` +) + +/* + AddDefaultLogger adds a default Logger (as would be determined by GetLogger) to a MultiLogger. + + identifier is a string to use to identify the added Logger in MultiLogger.Loggers. + If empty, one will be automatically generated. +*/ +func (m *MultiLogger) AddDefaultLogger(identifier string, logPaths ...string) (err error) { + + var l Logger + var exists bool + + if identifier == "" { + identifier = uuid.New().String() + } + + if _, exists = m.Loggers[identifier]; exists { + err = ErrExistingLogger + return + } + + if l, err = GetLogger(m.EnableDebug, m.Prefix, logPaths...); err != nil { + return + } + + m.Loggers[identifier] = l + + return +} + +/* + AddSysdLogger adds a SystemDLogger to a MultiLogger. + + identifier is a string to use to identify the added SystemDLogger in MultiLogger.Loggers. + If empty, one will be automatically generated. +*/ +func (m *MultiLogger) AddSysdLogger(identifier string) (err error) { + + var exists bool + + if identifier == "" { + identifier = uuid.New().String() + } + + if _, exists = m.Loggers[identifier]; exists { + err = ErrExistingLogger + return + } + + if !sysd.Enabled() { + err = ErrNoSysD + return + } + + m.Loggers[identifier] = &SystemDLogger{ + EnableDebug: m.EnableDebug, + Prefix: m.Prefix, + } + m.Loggers[identifier].Setup() + + m.Loggers[identifier].Info("logger initialized of type %T with prefix %v", m.Loggers[identifier], m.Loggers[identifier].GetPrefix()) + + return +} + +/* + AddSyslogLogger adds a SyslogLogger to a MultiLogger. + + identifier is a string to use to identify the added SyslogLogger in MultiLogger.Loggers. + If empty, one will be automatically generated. +*/ +func (m *MultiLogger) AddSyslogLogger(identifier string) (err error) { + + var exists bool + var hasSyslog bool + var stat os.FileInfo + var devlogPath string = devlog + + if identifier == "" { + identifier = uuid.New().String() + } + + if _, exists = m.Loggers[identifier]; exists { + err = ErrExistingLogger + return + } + + if hasSyslog, stat, err = paths.RealPathExistsStat(&devlogPath); hasSyslog && err != nil { + return + } else if !hasSyslog { + err = ErrNoSyslog + return + } + + if stat.Mode().IsRegular() { + err = ErrInvalidDevLog + return + } + + m.Loggers[identifier] = &SyslogLogger{ + EnableDebug: m.EnableDebug, + Prefix: m.Prefix, + } + m.Loggers[identifier].Setup() + + m.Loggers[identifier].Info("logger initialized of type %T with prefix %v", m.Loggers[identifier], m.Loggers[identifier].GetPrefix()) + + return +} diff --git a/logging/funcs_multilogger_mgr_windows.go b/logging/funcs_multilogger_mgr_windows.go new file mode 100644 index 0000000..7d00972 --- /dev/null +++ b/logging/funcs_multilogger_mgr_windows.go @@ -0,0 +1,84 @@ +package logging + +/* + AddDefaultLogger adds a default Logger (as would be determined by GetLogger) to a MultiLogger. + + identifier is a string to use to identify the added Logger in MultiLogger.Loggers. + If empty, one will be automatically generated. + + A pointer to a WinEventID struct may be specified for eventIDs to map extended logging levels (as Windows only supports three levels natively). + If it is nil, a default one (DefaultEventID) will be used. + + logPaths is an (optional) list of strings to use as paths to test for writing. If the file can be created/written to, + it will be used (assuming you have no higher-level loggers available). + + Only the first logPaths entry that "works" will be used, later entries will be ignored. + Currently this will almost always return a WinLogger. +*/ +func (m *MultiLogger) AddDefaultLogger(identifier string, eventIDs *WinEventID) (err error) { + + var l Logger + var exists bool + + if identifier == "" { + identifier = uuid.New().String() + } + + if _, exists = m.Loggers[identifier]; exists { + err = ErrExistingLogger + return + } + + if l, err = GetLogger(m.EnableDebug, m.Prefix, eventIDs, logPaths...); err != nil { + return + } + + m.Loggers[identifier] = l + + return +} + +/* + AddWinLogger adds a WinLogger to a MultiLogger. Note that this is a VERY generalized interface to the Windows Event Log. + + If you require more robust logging capabilities (e.g. custom event IDs per uniquely identifiable event), + you will want to set up your own logger (golang.org/x/sys/windows/svc/eventlog). + + identifier is a string to use to identify the added WinLogger in MultiLogger.Loggers. + If empty, one will be automatically generated. + + A blank source will return an error as it's used as the source name. Other functions, struct fields, etc. will refer to this as the "prefix". + + A pointer to a WinEventID struct may be specified for eventIDs to map extended logging levels (as Windows only supports three levels natively). + If it is nil, a default one (DefaultEventID) will be used. + + See GetLogger for details. +*/ +func (m *MultiLogger) AddWinLogger(identifier, source string, eventIDs *WinEventID) (err error) { + + var exists bool + + if identifier == "" { + identifier = uuid.New().String() + } + + if _, exists = m.Loggers[identifier]; exists { + err = ErrExistingLogger + return + } + + if eventIDs == nil { + eventIDs = DefaultEventID + } + + m.Loggers[identifier] = &WinLogger{ + Prefix: source, + EnableDebug: m.EnableDebug, + eids: eventIDs, + } + m.Loggers[identifier].Setup() + + m.Loggers[identifier].Info("logger initialized of type %T with prefix %v", m.Loggers[identifier], m.Loggers[identifier].GetPrefix()) + + return +} diff --git a/logging/funcs_std.go b/logging/funcs_std.go index f18f972..319e566 100644 --- a/logging/funcs_std.go +++ b/logging/funcs_std.go @@ -5,12 +5,14 @@ import ( "log" ) +// Setup sets up/configures a StdLogger and prepares it for use. func (l *StdLogger) Setup() { l.Logger = log.Default() l.Logger.SetPrefix(l.Prefix) } +// Shutdown cleanly shuts down a StdLogger. func (l *StdLogger) Shutdown() { // NOOP @@ -18,15 +20,7 @@ func (l *StdLogger) Shutdown() { } -func (l *StdLogger) DoDebug(d bool) { - l.EnableDebug = d -} - -func (l *StdLogger) SetPrefix(prefix string) { - l.Prefix = prefix - l.Logger.SetPrefix(prefix) -} - +// GetPrefix returns the prefix used by this StdLogger. func (l *StdLogger) GetPrefix() (prefix string) { prefix = l.Prefix @@ -34,6 +28,21 @@ func (l *StdLogger) GetPrefix() (prefix string) { return } +/* + DoDebug sets the debug state of this StdLogger. + Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log. +*/ +func (l *StdLogger) DoDebug(d bool) { + l.EnableDebug = d +} + +// SetPrefix sets the prefix for this StdLogger. +func (l *StdLogger) SetPrefix(prefix string) { + l.Prefix = prefix + l.Logger.SetPrefix(prefix) +} + +// Alert writes an ALERT-level message to this StdLogger. func (l *StdLogger) Alert(s string, v ...interface{}) (err error) { var msg string @@ -49,6 +58,7 @@ func (l *StdLogger) Alert(s string, v ...interface{}) (err error) { return } +// Crit writes an CRITICAL-level message to this StdLogger. func (l *StdLogger) Crit(s string, v ...interface{}) (err error) { var msg string @@ -64,6 +74,7 @@ func (l *StdLogger) Crit(s string, v ...interface{}) (err error) { return } +// Debug writes a DEBUG-level message to this StdLogger. func (l *StdLogger) Debug(s string, v ...interface{}) (err error) { if !l.EnableDebug { @@ -83,6 +94,7 @@ func (l *StdLogger) Debug(s string, v ...interface{}) (err error) { return } +// Emerg writes an EMERGENCY-level message to this StdLogger. func (l *StdLogger) Emerg(s string, v ...interface{}) (err error) { var msg string @@ -98,6 +110,7 @@ func (l *StdLogger) Emerg(s string, v ...interface{}) (err error) { return } +// Err writes an ERROR-level message to this StdLogger. func (l *StdLogger) Err(s string, v ...interface{}) (err error) { var msg string @@ -113,6 +126,7 @@ func (l *StdLogger) Err(s string, v ...interface{}) (err error) { return } +// Info writes an INFO-level message to this StdLogger. func (l *StdLogger) Info(s string, v ...interface{}) (err error) { var msg string @@ -128,6 +142,7 @@ func (l *StdLogger) Info(s string, v ...interface{}) (err error) { return } +// Notice writes a NOTICE-level message to this StdLogger. func (l *StdLogger) Notice(s string, v ...interface{}) (err error) { var msg string @@ -143,6 +158,7 @@ func (l *StdLogger) Notice(s string, v ...interface{}) (err error) { return } +// Warning writes a WARNING/WARN-level message to this StdLogger. func (l *StdLogger) Warning(s string, v ...interface{}) (err error) { var msg string @@ -158,6 +174,7 @@ func (l *StdLogger) Warning(s string, v ...interface{}) (err error) { return } +// renderWrite prepares/formats a log message to be written to this StdLogger. func (l *StdLogger) renderWrite(msg, prio string) { s := fmt.Sprintf("[%v] %v", prio, msg) diff --git a/logging/funcs_sysd_linux.go b/logging/funcs_sysd_linux.go index f39cf15..fd0bfe6 100644 --- a/logging/funcs_sysd_linux.go +++ b/logging/funcs_sysd_linux.go @@ -7,6 +7,7 @@ import ( "github.com/coreos/go-systemd/journal" ) +// Setup sets up/configures a SystemDLogger and prepares it for use. func (l *SystemDLogger) Setup() { // NOOP @@ -14,6 +15,7 @@ func (l *SystemDLogger) Setup() { } +// Shutdown cleanly shuts down a SystemDLogger. func (l *SystemDLogger) Shutdown() { // NOOP @@ -21,14 +23,7 @@ func (l *SystemDLogger) Shutdown() { } -func (l *SystemDLogger) DoDebug(d bool) { - l.EnableDebug = d -} - -func (l *SystemDLogger) SetPrefix(prefix string) { - l.Prefix = prefix -} - +// GetPrefix returns the prefix used by this SystemDLogger. func (l *SystemDLogger) GetPrefix() (prefix string) { prefix = l.Prefix @@ -36,6 +31,20 @@ func (l *SystemDLogger) GetPrefix() (prefix string) { return } +/* + DoDebug sets the debug state of this SystemDLogger. + Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log. +*/ +func (l *SystemDLogger) DoDebug(d bool) { + l.EnableDebug = d +} + +// SetPrefix sets the prefix for this SystemDLogger. +func (l *SystemDLogger) SetPrefix(prefix string) { + l.Prefix = prefix +} + +// Alert writes an ALERT-level message to this SystemDLogger. func (l *SystemDLogger) Alert(s string, v ...interface{}) (err error) { var msg string @@ -51,6 +60,7 @@ func (l *SystemDLogger) Alert(s string, v ...interface{}) (err error) { return } +// Crit writes an CRITICAL-level message to this SystemDLogger. func (l *SystemDLogger) Crit(s string, v ...interface{}) (err error) { var msg string @@ -66,6 +76,7 @@ func (l *SystemDLogger) Crit(s string, v ...interface{}) (err error) { return } +// Debug writes a DEBUG-level message to this SystemDLogger. func (l *SystemDLogger) Debug(s string, v ...interface{}) (err error) { if !l.EnableDebug { @@ -85,6 +96,7 @@ func (l *SystemDLogger) Debug(s string, v ...interface{}) (err error) { return } +// Emerg writes an EMERGENCY-level message to this SystemDLogger. func (l *SystemDLogger) Emerg(s string, v ...interface{}) (err error) { var msg string @@ -100,6 +112,7 @@ func (l *SystemDLogger) Emerg(s string, v ...interface{}) (err error) { return } +// Err writes an ERROR-level message to this SystemDLogger. func (l *SystemDLogger) Err(s string, v ...interface{}) (err error) { var msg string @@ -115,6 +128,7 @@ func (l *SystemDLogger) Err(s string, v ...interface{}) (err error) { return } +// Info writes an INFO-level message to this SystemDLogger. func (l *SystemDLogger) Info(s string, v ...interface{}) (err error) { var msg string @@ -130,6 +144,7 @@ func (l *SystemDLogger) Info(s string, v ...interface{}) (err error) { return } +// Notice writes a NOTICE-level message to this SystemDLogger. func (l *SystemDLogger) Notice(s string, v ...interface{}) (err error) { var msg string @@ -145,6 +160,7 @@ func (l *SystemDLogger) Notice(s string, v ...interface{}) (err error) { return } +// Warning writes a WARNING/WARN-level message to this SystemDLogger. func (l *SystemDLogger) Warning(s string, v ...interface{}) (err error) { var msg string @@ -160,6 +176,7 @@ func (l *SystemDLogger) Warning(s string, v ...interface{}) (err error) { return } +// renderWrite prepares/formats a log message to be written to this SystemDLogger. func (l *SystemDLogger) renderWrite(msg string, prio journal.Priority) { // TODO: implement code line, etc. diff --git a/logging/funcs_syslog_linux.go b/logging/funcs_syslog_linux.go index fcc50b7..ce79cc1 100644 --- a/logging/funcs_syslog_linux.go +++ b/logging/funcs_syslog_linux.go @@ -6,6 +6,7 @@ import ( "log/syslog" ) +// Setup sets up/configures a SyslogLogger and prepares it for use. func (l *SyslogLogger) Setup() { var err error @@ -37,6 +38,7 @@ func (l *SyslogLogger) Setup() { } +// Shutdown cleanly shuts down a SyslogLogger. func (l *SyslogLogger) Shutdown() { var err error @@ -49,15 +51,7 @@ func (l *SyslogLogger) Shutdown() { } -func (l *SyslogLogger) DoDebug(d bool) { - l.EnableDebug = d -} - -func (l *SyslogLogger) SetPrefix(prefix string) { - l.Prefix = prefix - l.Setup() -} - +// GetPrefix returns the prefix used by this SyslogLogger. func (l *SyslogLogger) GetPrefix() (prefix string) { prefix = l.Prefix @@ -65,6 +59,21 @@ func (l *SyslogLogger) GetPrefix() (prefix string) { return } +/* + DoDebug sets the debug state of this SyslogLogger. + Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log. +*/ +func (l *SyslogLogger) DoDebug(d bool) { + l.EnableDebug = d +} + +// SetPrefix sets the prefix for this SyslogLogger. +func (l *SyslogLogger) SetPrefix(prefix string) { + l.Prefix = prefix + l.Setup() +} + +// Alert writes an ALERT-level message to this SyslogLogger. func (l *SyslogLogger) Alert(s string, v ...interface{}) (err error) { var msg string @@ -82,6 +91,7 @@ func (l *SyslogLogger) Alert(s string, v ...interface{}) (err error) { return } +// Crit writes an CRITICAL-level message to this SyslogLogger. func (l *SyslogLogger) Crit(s string, v ...interface{}) (err error) { var msg string @@ -99,6 +109,7 @@ func (l *SyslogLogger) Crit(s string, v ...interface{}) (err error) { return } +// Debug writes a DEBUG-level message to this SyslogLogger. func (l *SyslogLogger) Debug(s string, v ...interface{}) (err error) { if !l.EnableDebug { @@ -120,6 +131,7 @@ func (l *SyslogLogger) Debug(s string, v ...interface{}) (err error) { return } +// Emerg writes an EMERGENCY-level message to this SyslogLogger. func (l *SyslogLogger) Emerg(s string, v ...interface{}) (err error) { var msg string @@ -137,6 +149,7 @@ func (l *SyslogLogger) Emerg(s string, v ...interface{}) (err error) { return } +// Err writes an ERROR-level message to this SyslogLogger. func (l *SyslogLogger) Err(s string, v ...interface{}) (err error) { var msg string @@ -154,6 +167,7 @@ func (l *SyslogLogger) Err(s string, v ...interface{}) (err error) { return } +// Info writes an INFO-level message to this SyslogLogger. func (l *SyslogLogger) Info(s string, v ...interface{}) (err error) { var msg string @@ -171,6 +185,7 @@ func (l *SyslogLogger) Info(s string, v ...interface{}) (err error) { return } +// Notice writes a NOTICE-level message to this SyslogLogger. func (l *SyslogLogger) Notice(s string, v ...interface{}) (err error) { var msg string @@ -188,6 +203,7 @@ func (l *SyslogLogger) Notice(s string, v ...interface{}) (err error) { return } +// Warning writes a WARNING/WARN-level message to this SyslogLogger. func (l *SyslogLogger) Warning(s string, v ...interface{}) (err error) { var msg string diff --git a/logging/funcs_windows.go b/logging/funcs_windows.go index 13b9ac2..47ff61c 100644 --- a/logging/funcs_windows.go +++ b/logging/funcs_windows.go @@ -7,21 +7,31 @@ import ( /* GetLogger returns an instance of Logger that best suits your system's capabilities. Note that this is a VERY generalized interface to the Windows Event Log. + If you require more robust logging capabilities (e.g. custom event IDs per uniquely identifiable event), you will want to set up your own logger (golang.org/x/sys/windows/svc/eventlog). + If enableDebug is true, debug messages (which according to your program may or may not contain sensitive data) are rendered and written (otherwise they are ignored). + A blank source will return an error as it's used as the source name. Other functions, struct fields, etc. will refer to this as the "prefix". + A pointer to a WinEventID struct may be specified for eventIDs to map extended logging levels (as Windows only supports three levels natively). If it is nil, a default one (DefaultEventID) will be used. + logPaths is an (optional) list of strings to use as paths to test for writing. If the file can be created/written to, it will be used (assuming you have no higher-level loggers available). + Only the first logPaths entry that "works" will be used, later entries will be ignored. - Currently this will almost always return a WinLogger until multiple logging destination support is added. + Currently this will almost always return a WinLogger. + + If you call GetLogger, you will only get a single ("best") logger your system supports. + If you want to log to multiple Logger destinations at once (or want to log to an explicit Logger type), + use GetMultiLogger. */ func GetLogger(enableDebug bool, source string, eventIDs *WinEventID, logPaths ...string) (logger Logger, err error) { var logPath string - var logFlags types.MaskBit + var logFlags bitmask.MaskBit var exists bool var success bool var ckLogPaths []string @@ -53,7 +63,7 @@ func GetLogger(enableDebug bool, source string, eventIDs *WinEventID, logPaths . break } else { dirPath := path.Dir(p) - if err = paths.MakeDirIfNotExist(&dirPath); err != nil { + if err = paths.MakeDirIfNotExist(dirPath); err != nil { continue } if success, err = testOpen(p); err != nil { diff --git a/logging/funcs_winlogger_windows.go b/logging/funcs_winlogger_windows.go index c12b310..f7317d2 100644 --- a/logging/funcs_winlogger_windows.go +++ b/logging/funcs_winlogger_windows.go @@ -2,8 +2,11 @@ package logging import ( `errors` + + `golang.org/x/sys/windows/svc/eventlog` ) +// Setup sets up/configures a WinLogger and prepares it for use. func (l *WinLogger) Setup() { var err error @@ -44,6 +47,7 @@ func (l *WinLogger) Setup() { } +// Shutdown cleanly shuts down a WinLogger. func (l *WinLogger) Shutdown() { var err error @@ -58,12 +62,25 @@ func (l *WinLogger) Shutdown() { } +// GetPrefix returns the prefix used by this WinLogger. +func (l *WinLogger) GetPrefix() (prefix string) { + + prefix = l.Prefix + + return +} + +/* + DoDebug sets the debug state of this WinLogger. + Note that this merely acts as a *safety filter* for debug messages to avoid sensitive information being written to the log. +*/ func (l *WinLogger) DoDebug(d bool) { l.EnableDebug = d } +// SetPrefix sets the prefix for this WinLogger. func (l *WinLogger) SetPrefix(prefix string) { var err error @@ -95,13 +112,7 @@ func (l *WinLogger) SetPrefix(prefix string) { } -func (l *WinLogger) GetPrefix() (prefix string) { - - prefix = l.Prefix - - return -} - +// Alert writes an ALERT-level message to this WinLogger. func (l *WinLogger) Alert(s string, v ...interface{}) (err error) { var msg string @@ -118,6 +129,7 @@ func (l *WinLogger) Alert(s string, v ...interface{}) (err error) { return } +// Crit writes an CRITICAL-level message to this WinLogger. func (l *WinLogger) Crit(s string, v ...interface{}) (err error) { var msg string @@ -134,6 +146,7 @@ func (l *WinLogger) Crit(s string, v ...interface{}) (err error) { return } +// Debug writes a DEBUG-level message to this WinLogger. func (l *WinLogger) Debug(s string, v ...interface{}) (err error) { if !l.EnableDebug { @@ -155,6 +168,7 @@ func (l *WinLogger) Debug(s string, v ...interface{}) (err error) { } +// Emerg writes an EMERGENCY-level message to this WinLogger. func (l *WinLogger) Emerg(s string, v ...interface{}) (err error) { var msg string @@ -172,6 +186,7 @@ func (l *WinLogger) Emerg(s string, v ...interface{}) (err error) { } +// Err writes an ERROR-level message to this WinLogger. func (l *WinLogger) Err(s string, v ...interface{}) (err error) { var msg string @@ -188,6 +203,7 @@ func (l *WinLogger) Err(s string, v ...interface{}) (err error) { } +// Info writes an INFO-level message to this WinLogger. func (l *WinLogger) Info(s string, v ...interface{}) (err error) { var msg string @@ -204,6 +220,7 @@ func (l *WinLogger) Info(s string, v ...interface{}) (err error) { } +// Notice writes a NOTICE-level message to this WinLogger. func (l *WinLogger) Notice(s string, v ...interface{}) (err error) { var msg string @@ -221,6 +238,7 @@ func (l *WinLogger) Notice(s string, v ...interface{}) (err error) { } +// Warning writes a WARNING/WARN-level message to this WinLogger. func (l *WinLogger) Warning(s string, v ...interface{}) (err error) { var msg string diff --git a/logging/types.go b/logging/types.go index 9a150e3..58f0c21 100644 --- a/logging/types.go +++ b/logging/types.go @@ -5,30 +5,60 @@ import ( "os" ) +/* + Logger is one of the various loggers offered by this module. +*/ type Logger interface { - Alert(string, ...interface{}) error - Crit(string, ...interface{}) error - Debug(string, ...interface{}) error - Emerg(string, ...interface{}) error - Err(string, ...interface{}) error - Info(string, ...interface{}) error - Notice(string, ...interface{}) error - Warning(string, ...interface{}) error - DoDebug(bool) - SetPrefix(string) - GetPrefix() string + Alert(s string, v ...interface{}) (err error) + Crit(s string, v ...interface{}) (err error) + Debug(s string, v ...interface{}) (err error) + Emerg(s string, v ...interface{}) (err error) + Err(s string, v ...interface{}) (err error) + Info(s string, v ...interface{}) (err error) + Notice(s string, v ...interface{}) (err error) + Warning(s string, v ...interface{}) (err error) + DoDebug(d bool) + SetPrefix(p string) + GetPrefix() (p string) Setup() Shutdown() } +// StdLogger uses the log package in stdlib to perform all logging. type StdLogger struct { + // All log.Logger fields/methods are exposed. *log.Logger + /* + EnableDebug indicates if the debug filter should be disabled (true) or if the filter should be enabled (false). + This prevents potential data leak of sensitive information, as some loggers (e.g. FileLogger) will otherwise write all messages. + */ EnableDebug bool - Prefix string + // Prefix indicates the prefix for log entries; in shared logs, this helps differentiate the source. + Prefix string } +// FileLogger uses a StdLogger with a file handle writer to write to the file given at Path. type FileLogger struct { + // StdLogger is used for the log formation and handling. StdLogger - Path string + // Path is the path to the logfile. + Path string + // writer is used for the writing out of the log file. writer *os.File } + +// MultiLogger is used to contain one or more Loggers and present them all as a single Logger. +type MultiLogger struct { + /* + EnableDebug indicates if the debug filter should be disabled (true) or if the filter should be enabled (false). + This prevents potential data leak of sensitive information, as some loggers (e.g. FileLogger) will otherwise write all messages. + */ + EnableDebug bool + // Prefix indicates the prefix for log entries; in shared logs, this helps differentiate the source. + Prefix string + /* + Loggers contains a map of map[logname]Logger. It can be used to set log-specific options, or replace a Logger + with one of a different type or options. + */ + Loggers map[string]Logger +} diff --git a/multierr/doc.go b/multierr/doc.go new file mode 100644 index 0000000..e7610ce --- /dev/null +++ b/multierr/doc.go @@ -0,0 +1,58 @@ +/* +Package multierr provides a simple way of handling multiple errors and consolidating them into a single error. + +Example: + + package main + + import ( + `r00t2.io/goutils/multierr` + ) + + func main() { + + var err error + var errs []error + + errs = make([]error, 0) + + for _, i := range someSlice { + go func() { + if err = i.DoSomething(); err != nil { + errs = append(errs, err) + } + }() + } + + if errs != nil && len(errs) != 0 { + // err now contains multiple errors presented as a single error. + err = multierr.NewErrors(errs...) + } + } + +MultiError also has a shorthand, making the above much less verbose: + + package main + + import ( + `r00t2.io/goutils/multierr` + ) + + func main() { + + var err error + var multierror *multierr.MultiError = multierr.NewMultiError(nil) + + for _, i := range someSlice { + go func() { + if err = i.DoSomething(); err != nil { + multierror.AddError(err) + } + }() + } + // multierror now contains any/all errors above. + } + +In the above, the multierror assignment can still be used as an error. +*/ +package multierr diff --git a/multierr/funcs.go b/multierr/funcs.go new file mode 100644 index 0000000..939f3b9 --- /dev/null +++ b/multierr/funcs.go @@ -0,0 +1,85 @@ +package multierr + +import ( + `fmt` +) + +/* + NewErrors returns a new MultiError (as an error) based on/initialized with a slice of error.Error (errs). + Any nil errors are trimmed. + If there are no actual errors after trimming, err will be nil. +*/ +func NewErrors(errs ...error) (err error) { + + if errs == nil || len(errs) == 0 { + return + } + + var realErrs []error = make([]error, 0) + + for _, e := range errs { + if e == nil { + continue + } + realErrs = append(realErrs, e) + } + + if len(realErrs) == 0 { + return + } + + err = &MultiError{ + Errors: realErrs, + ErrorSep: "\n", + } + + return +} + +// NewMultiError will provide a MultiError (true type), optionally initialized with errors. +func NewMultiError(errs ...error) (m *MultiError) { + + if errs == nil { + errs = make([]error, 0) + } + + m = &MultiError{ + Errors: errs, + ErrorSep: "\n", + } + + return +} + +// Error returns a string representation of a MultiError (to conform with the error interface). +func (e *MultiError) Error() (errStr string) { + + var numErrs int + + if e == nil || len(e.Errors) == 0 { + return + } else { + numErrs = len(e.Errors) + } + + for idx, err := range e.Errors { + if (idx + 1) < numErrs { + errStr += fmt.Sprintf("%v%v", err.Error(), e.ErrorSep) + } else { + errStr += err.Error() + } + } + + return +} + +// AddError is a shorthand way of adding an error to a MultiError. +func (e *MultiError) AddError(err error) { + + if err == nil { + return + } + + e.Errors = append(e.Errors, err) + +} diff --git a/multierr/types.go b/multierr/types.go new file mode 100644 index 0000000..4cf8259 --- /dev/null +++ b/multierr/types.go @@ -0,0 +1,9 @@ +package multierr + +// MultiError is a type of error.Error that can contain multiple errors. +type MultiError struct { + // Errors is a slice of errors to combine/concatenate when .Error() is called. + Errors []error `json:"errors"` + // ErrorSep is a string to use to separate errors for .Error(). The default is "\n". + ErrorSep string `json:"separator"` +}