finalizing logging and multierror

This commit is contained in:
brent s. 2022-01-05 05:15:38 -05:00
parent 3975f8b11f
commit 0e01306637
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
24 changed files with 1057 additions and 60 deletions

4
go.mod
View File

@ -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
)

9
go.sum
View File

@ -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=

View File

@ -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
)

View File

@ -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",

View File

@ -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\<username>\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,

43
logging/doc.go Normal file
View File

@ -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

17
logging/errs.go Normal file
View File

@ -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")
)

18
logging/errs_linux.go Normal file
View File

@ -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))
)

View File

@ -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


View File

@ -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)

View File

@ -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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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.

View File

@ -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


View File

@ -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 {

View File

@ -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

View File

@ -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
}

58
multierr/doc.go Normal file
View File

@ -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

85
multierr/funcs.go Normal file
View File

@ -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)

}

9
multierr/types.go Normal file
View File

@ -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"`
}