6 Commits

30 changed files with 1854 additions and 225 deletions

46
bitmask/bitmasks.go Normal file
View File

@@ -0,0 +1,46 @@
package bitmask
// MaskBit is a flag container.
type MaskBit uint8
/*
NewMaskBit is a convenience function.
It will return a MaskBit with a (referenced) value of 0, so set your consts up accordingly.
It is highly recommended to set this default as a "None" flag (separate from your iotas!)
as shown in the example.
*/
func NewMaskBit() (m *MaskBit) {
m = new(MaskBit)
return
}
// HasFlag is true if m has MaskBit flag set/enabled.
func (m *MaskBit) HasFlag(flag MaskBit) (r bool) {
var b MaskBit = *m
if b&flag != 0 {
r = true
}
return
}
// AddFlag adds MaskBit flag to m.
func (m *MaskBit) AddFlag(flag MaskBit) {
*m |= flag
return
}
// ClearFlag removes MaskBit flag from m.
func (m *MaskBit) ClearFlag(flag MaskBit) {
*m &= flag
return
}
// ToggleFlag switches MaskBit flag in m to its inverse; if true, it is now false and vice versa.
func (m *MaskBit) ToggleFlag(flag MaskBit) {
*m ^= flag
return
}

45
bitmask/doc.go Normal file
View File

@@ -0,0 +1,45 @@
/*
Package bitmask handles a flag-like opt/bitmask system.
See https://yourbasic.org/golang/bitmask-flag-set-clear/ for more information.
To use this, set constants like thus:
package main
import (
"r00t2.io/goutils/bitmask"
)
const OPTNONE types.MaskBit = 0
const (
OPT1 types.MaskBit = 1 << iota
OPT2
OPT3
// ...
)
var MyMask *MaskBit
func main() {
MyMask = types.NewMaskBit
MyMask.AddFlag(OPT1)
MyMask.AddFlag(OPT3)
_ = MyMask
}
This would return true:
MyMask.HasFlag(OPT1)
As would this:
MyMask.HasFlag(OPT3)
But this would return false:
MyMask.HasFlag(OPT2)
*/
package bitmask

4
go.mod
View File

@@ -4,5 +4,7 @@ go 1.16
require ( require (
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf 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 h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 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= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
r00t2.io/sysutils v0.0.0-20210224054841-55ac47c86928 h1:aYEn20eguqsmqT3J9VjkzdhyPwmOVDGzzffcEfV18a4= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
r00t2.io/sysutils v0.0.0-20210224054841-55ac47c86928/go.mod h1:XzJkBF6SHAODEszJlOcjtGoTHwYnZZNmseA6PyOujes= 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

@@ -1,3 +1,11 @@
- Implement code line/func/etc. (only for debug?): - Implement code line/func/etc. (only for debug?):
https://stackoverflow.com/a/24809646 https://stackoverflow.com/a/24809646
https://golang.org/pkg/runtime/#Caller https://golang.org/pkg/runtime/#Caller
- Support simultaneous writing to multiple Loggers.
- Suport remote loggers? (eventlog, syslog, systemd)
- DOCS.
- Unit/Integration tests.

View File

@@ -1,29 +1,14 @@
package logging package logging
import ( import (
`log/syslog`
`os` `os`
`r00t2.io/goutils/types`
) )
const ( const (
devlog string = "/dev/log" // logPerm is the octal mode to use for testing the file.
logPerm os.FileMode = 0600 logPerm os.FileMode = 0600
// logPrefix is the default log prefix.
logPrefix string = "GOLANG PROGRAM" 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).
syslogFacility syslog.Priority = syslog.LOG_USER appendFlags int = os.O_APPEND | os.O_CREATE | os.O_WRONLY
)
// Flags for logger configuration
const (
LogUndefined types.MaskBit = 1 << iota
LogJournald
LogSyslog
LogFile
LogStdout
)
var (
defLogPaths = []string{"/var/log/golang/program.log", "~/.local/log/golang/program.log"}
) )

36
logging/consts_linux.go Normal file
View File

@@ -0,0 +1,36 @@
package logging
import (
`log/syslog`
`r00t2.io/goutils/bitmask`
)
const (
// 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. 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",
}
)

56
logging/consts_windows.go Normal file
View File

@@ -0,0 +1,56 @@
package logging
import (
`os`
`path/filepath`
`regexp`
`r00t2.io/goutils/bitmask`
)
// Flags for logger configuration. These are used internally.
const (
// LogUndefined indicates an undefined Logger type.
LogUndefined bitmask.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, (can be) used in GetLogger and MultiLogger.AddWinLogger.
var DefaultEventID *WinEventID = &WinEventID{
Alert: EventAlert,
Crit: EventCrit,
Debug: EventDebug,
Emerg: EventEmerg,
Err: EventErr,
Info: EventInfo,
Notice: EventNotice,
Warning: EventWarning,
}
// Default Event IDs for WinEventID.
const (
EventAlert uint32 = 1 << iota
EventCrit
EventDebug
EventEmerg
EventErr
EventInfo
EventNotice
EventWarning
)

47
logging/doc.go Normal file
View File

@@ -0,0 +1,47 @@
/*
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.
Note that for some Loggers, the prefix may be modified - "literal" loggers (StdLogger and FileLogger) will append a space to the end of the prefix.
If this is undesired (unlikely), you will need to modify (Logger).Prefix and run (Logger).Logger.SetPrefix(yourPrefixHere) for the respective logger.
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

@@ -1,126 +1,10 @@
package logging package logging
import ( import (
native "log"
"os" "os"
"path"
"r00t2.io/goutils/types"
sysd "github.com/coreos/go-systemd/journal"
"r00t2.io/sysutils/paths"
) )
var ( // testOpen attempts to open a file for writing to test for suitability as a LogFile path.
_ = sysd.Enabled()
_ = native.Logger{}
_ = os.Interrupt
)
// 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.
func GetLogger(enableDebug bool, prefix string, logpaths ...string) (logger Logger, err error) {
var logPath string
var logflags types.MaskBit
// Configure system-supported logger(s).
if sysd.Enabled() {
// Use Journald.
logflags.AddFlag(LogJournald)
} else {
// If we can detect syslog, use that. If not, try to use a file logger (+ stdout).
// Last ditch, stdout.
var hasSyslog bool
var stat os.FileInfo
var devlogPath string = devlog
if hasSyslog, stat, err = paths.RealPathExistsStat(&devlogPath); hasSyslog && err != nil {
return
}
if hasSyslog && !stat.Mode().IsRegular() {
logflags.AddFlag(LogSyslog)
} else {
var exists bool
var success bool
var ckLogPaths []string
logflags.AddFlag(LogStdout)
ckLogPaths = defLogPaths
if logpaths != nil {
ckLogPaths = logpaths
}
for _, p := range ckLogPaths {
if exists, _ = paths.RealPathExists(&p); exists {
if success, err = testOpen(p); err != nil {
continue
} else if !success {
continue
}
logflags.AddFlag(LogFile)
logPath = p
break
} else {
dirPath := path.Dir(p)
if err = paths.MakeDirIfNotExist(&dirPath); err != nil {
continue
}
if success, err = testOpen(p); err != nil {
continue
} else if !success {
continue
}
logflags.AddFlag(LogFile)
logPath = p
break
}
}
}
}
if logflags.HasFlag(LogJournald) {
logger = &SystemDLogger{
Prefix: logPrefix,
EnableDebug: enableDebug,
}
} else {
if logflags.HasFlag(LogSyslog) {
logger = &SyslogLogger{
Prefix: logPrefix,
EnableDebug: enableDebug,
}
} else {
if logflags.HasFlag(LogFile) {
logger = &FileLogger{
StdLogger: StdLogger{
Prefix: logPrefix,
EnableDebug: enableDebug,
},
Path: logPath,
}
} else {
logger = &StdLogger{
Prefix: logPrefix,
EnableDebug: enableDebug,
}
}
}
}
logger.Setup()
if prefix != "\x00" {
logger.SetPrefix(prefix)
}
logger.Info("logger initialized of type %T with prefix %v", logger, logger.GetPrefix())
return
}
func testOpen(path string) (success bool, err error) { func testOpen(path string) (success bool, err error) {
var f *os.File var f *os.File

View File

@@ -7,20 +7,27 @@ import (
"os" "os"
) )
// Setup sets up/configures a FileLogger and prepares it for use.
func (l *FileLogger) Setup() { func (l *FileLogger) Setup() {
var err error var err error
var multi io.Writer
l.Logger = log.Default() l.Logger = log.Default()
l.Logger.SetPrefix(l.Prefix) l.Logger.SetPrefix(l.Prefix + " ")
if l.writer, err = os.OpenFile(l.Path, appendFlags, logPerm); err != nil { if l.writer, err = os.OpenFile(l.Path, appendFlags, logPerm); err != nil {
log.Panicf("could not open log file \"%v\" for writing: %v\n", l.Path, err) log.Panicf("could not open log file \"%v\" for writing: %v\n", l.Path, err)
} }
// https://stackoverflow.com/a/36719588/733214 // https://stackoverflow.com/a/36719588/733214
multi := io.MultiWriter(os.Stdout, l.writer) if l.EnableStdOut {
multi = io.MultiWriter(os.Stdout, l.writer)
} else {
multi = l.writer
}
l.Logger.SetOutput(multi) l.Logger.SetOutput(multi)
} }
// Shutdown cleanly shuts down a FileLogger.
func (l *FileLogger) Shutdown() { func (l *FileLogger) Shutdown() {
var err error var err error
@@ -31,19 +38,27 @@ func (l *FileLogger) Shutdown() {
} }
// GetPrefix returns the prefix used by this FileLogger.
func (l *FileLogger) GetPrefix() string { func (l *FileLogger) GetPrefix() string {
return l.Prefix 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) { func (l *FileLogger) DoDebug(d bool) {
l.EnableDebug = d l.EnableDebug = d
} }
// SetPrefix sets the prefix for this FileLogger.
func (l *FileLogger) SetPrefix(prefix string) { func (l *FileLogger) SetPrefix(prefix string) {
l.Prefix = prefix
l.Logger.SetPrefix(prefix) 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) { func (l *FileLogger) Alert(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -59,6 +74,7 @@ func (l *FileLogger) Alert(s string, v ...interface{}) (err error) {
return return
} }
// Crit writes an CRITICAL-level message to this FileLogger.
func (l *FileLogger) Crit(s string, v ...interface{}) (err error) { func (l *FileLogger) Crit(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -74,6 +90,7 @@ func (l *FileLogger) Crit(s string, v ...interface{}) (err error) {
return return
} }
// Debug writes a DEBUG-level message to this FileLogger.
func (l *FileLogger) Debug(s string, v ...interface{}) (err error) { func (l *FileLogger) Debug(s string, v ...interface{}) (err error) {
if !l.EnableDebug { if !l.EnableDebug {
@@ -93,6 +110,7 @@ func (l *FileLogger) Debug(s string, v ...interface{}) (err error) {
return return
} }
// Emerg writes an EMERGENCY-level message to this FileLogger.
func (l *FileLogger) Emerg(s string, v ...interface{}) (err error) { func (l *FileLogger) Emerg(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -108,6 +126,7 @@ func (l *FileLogger) Emerg(s string, v ...interface{}) (err error) {
return return
} }
// Err writes an ERROR-level message to this FileLogger.
func (l *FileLogger) Err(s string, v ...interface{}) (err error) { func (l *FileLogger) Err(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -123,6 +142,7 @@ func (l *FileLogger) Err(s string, v ...interface{}) (err error) {
return return
} }
// Info writes an INFO-level message to this FileLogger.
func (l *FileLogger) Info(s string, v ...interface{}) (err error) { func (l *FileLogger) Info(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -138,6 +158,7 @@ func (l *FileLogger) Info(s string, v ...interface{}) (err error) {
return return
} }
// Notice writes a NOTICE-level message to this FileLogger.
func (l *FileLogger) Notice(s string, v ...interface{}) (err error) { func (l *FileLogger) Notice(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -153,6 +174,7 @@ func (l *FileLogger) Notice(s string, v ...interface{}) (err error) {
return return
} }
// Warning writes a WARNING/WARN-level message to this FileLogger.
func (l *FileLogger) Warning(s string, v ...interface{}) (err error) { func (l *FileLogger) Warning(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -168,6 +190,7 @@ func (l *FileLogger) Warning(s string, v ...interface{}) (err error) {
return return
} }
// renderWrite prepares/formats a log message to be written to this FileLogger.
func (l *FileLogger) renderWrite(msg, prio string) { func (l *FileLogger) renderWrite(msg, prio string) {
s := fmt.Sprintf("[%v] %v", prio, msg) s := fmt.Sprintf("[%v] %v", prio, msg)

132
logging/funcs_linux.go Normal file
View File

@@ -0,0 +1,132 @@
package logging
import (
native `log`
`os`
`path`
sysd `github.com/coreos/go-systemd/journal`
`r00t2.io/goutils/bitmask`
`r00t2.io/sysutils/paths`
)
var (
_ = sysd.Enabled()
_ = native.Logger{}
_ = os.Interrupt
)
/*
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 bitmask.MaskBit
// Configure system-supported logger(s).
if sysd.Enabled() {
// Use Journald.
logFlags.AddFlag(LogJournald)
} else {
// If we can detect syslog, use that. If not, try to use a file logger (+ stdout).
// Last ditch, stdout.
var hasSyslog bool
var stat os.FileInfo
var devlogPath string = devlog
if hasSyslog, stat, err = paths.RealPathExistsStat(&devlogPath); hasSyslog && err != nil {
return
}
if hasSyslog && !stat.Mode().IsRegular() {
logFlags.AddFlag(LogSyslog)
} else {
var exists bool
var success bool
var ckLogPaths []string
logFlags.AddFlag(LogStdout)
ckLogPaths = defLogPaths
if logPaths != nil {
ckLogPaths = logPaths
}
for _, p := range ckLogPaths {
if exists, _ = paths.RealPathExists(&p); exists {
if success, err = testOpen(p); err != nil {
continue
} else if !success {
continue
}
logFlags.AddFlag(LogFile)
logPath = p
break
} else {
dirPath := path.Dir(p)
if err = paths.MakeDirIfNotExist(dirPath); err != nil {
continue
}
if success, err = testOpen(p); err != nil {
continue
} else if !success {
continue
}
logFlags.AddFlag(LogFile)
logPath = p
break
}
}
}
}
if logFlags.HasFlag(LogJournald) {
logger = &SystemDLogger{
Prefix: logPrefix,
EnableDebug: enableDebug,
}
} else {
if logFlags.HasFlag(LogSyslog) {
logger = &SyslogLogger{
Prefix: logPrefix,
EnableDebug: enableDebug,
}
} else {
if logFlags.HasFlag(LogFile) {
logger = &FileLogger{
StdLogger: StdLogger{
Prefix: logPrefix,
EnableDebug: enableDebug,
},
Path: logPath,
}
} else {
logger = &StdLogger{
Prefix: logPrefix,
EnableDebug: enableDebug,
}
}
}
}
logger.Setup()
if prefix != "\x00" {
logger.SetPrefix(prefix)
}
logger.Info("logger initialized of type %T with prefix %v", logger, logger.GetPrefix())
return
}

View File

@@ -0,0 +1,337 @@
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{}) {
defer wg.Done()
if rplc != nil {
err = logObj.Alert(msg, rplc...)
} else {
err = logObj.Alert(msg)
}
if err != nil {
e.AddError(err)
err = nil
}
}(l, s, v)
}
wg.Wait()
if !e.IsEmpty() {
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{}) {
defer wg.Done()
if rplc != nil {
err = logObj.Crit(msg, rplc...)
} else {
err = logObj.Crit(msg)
}
if err != nil {
e.AddError(err)
err = nil
}
}(l, s, v)
}
wg.Wait()
if !e.IsEmpty() {
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{}) {
defer wg.Done()
if rplc != nil {
err = logObj.Debug(msg, rplc...)
} else {
err = logObj.Debug(msg)
}
if err != nil {
e.AddError(err)
err = nil
}
}(l, s, v)
}
wg.Wait()
if !e.IsEmpty() {
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{}) {
defer wg.Done()
if rplc != nil {
err = logObj.Emerg(msg, rplc...)
} else {
err = logObj.Emerg(msg)
}
if err != nil {
e.AddError(err)
err = nil
}
}(l, s, v)
}
wg.Wait()
if !e.IsEmpty() {
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{}) {
defer wg.Done()
if rplc != nil {
err = logObj.Err(msg, rplc...)
} else {
err = logObj.Err(msg)
}
if err != nil {
e.AddError(err)
err = nil
}
}(l, s, v)
}
wg.Wait()
if !e.IsEmpty() {
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{}) {
defer wg.Done()
if rplc != nil {
err = logObj.Info(msg, rplc...)
} else {
err = logObj.Info(msg)
}
if err != nil {
e.AddError(err)
err = nil
}
}(l, s, v)
}
wg.Wait()
if !e.IsEmpty() {
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{}) {
defer wg.Done()
if rplc != nil {
err = logObj.Notice(msg, rplc...)
} else {
err = logObj.Notice(msg)
}
if err != nil {
e.AddError(err)
err = nil
}
}(l, s, v)
}
wg.Wait()
if !e.IsEmpty() {
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{}) {
defer wg.Done()
if rplc != nil {
err = logObj.Warning(msg, rplc...)
} else {
err = logObj.Warning(msg)
}
if err != nil {
e.AddError(err)
err = nil
}
}(l, s, v)
}
wg.Wait()
if !e.IsEmpty() {
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,93 @@
package logging
import (
`github.com/google/uuid`
)
/*
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, 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 logPaths != nil {
l, err = GetLogger(m.EnableDebug, m.Prefix, eventIDs, logPaths...);
} else {
l, err = GetLogger(m.EnableDebug, m.Prefix, eventIDs);
}
if 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" "log"
) )
// Setup sets up/configures a StdLogger and prepares it for use.
func (l *StdLogger) Setup() { func (l *StdLogger) Setup() {
l.Logger = log.Default() l.Logger = log.Default()
l.Logger.SetPrefix(l.Prefix) l.Logger.SetPrefix(l.Prefix + " ")
} }
// Shutdown cleanly shuts down a StdLogger.
func (l *StdLogger) Shutdown() { func (l *StdLogger) Shutdown() {
// NOOP // NOOP
@@ -18,19 +20,29 @@ func (l *StdLogger) Shutdown() {
} }
// GetPrefix returns the prefix used by this StdLogger.
func (l *StdLogger) GetPrefix() (prefix string) {
prefix = l.Prefix
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) { func (l *StdLogger) DoDebug(d bool) {
l.EnableDebug = d l.EnableDebug = d
} }
// SetPrefix sets the prefix for this StdLogger.
func (l *StdLogger) SetPrefix(prefix string) { func (l *StdLogger) SetPrefix(prefix string) {
l.Prefix = prefix l.Prefix = prefix + " "
l.Logger.SetPrefix(prefix) l.Logger.SetPrefix(prefix + " ")
}
func (l *StdLogger) GetPrefix() string {
return l.Prefix
} }
// Alert writes an ALERT-level message to this StdLogger.
func (l *StdLogger) Alert(s string, v ...interface{}) (err error) { func (l *StdLogger) Alert(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -46,6 +58,7 @@ func (l *StdLogger) Alert(s string, v ...interface{}) (err error) {
return return
} }
// Crit writes an CRITICAL-level message to this StdLogger.
func (l *StdLogger) Crit(s string, v ...interface{}) (err error) { func (l *StdLogger) Crit(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -61,6 +74,7 @@ func (l *StdLogger) Crit(s string, v ...interface{}) (err error) {
return return
} }
// Debug writes a DEBUG-level message to this StdLogger.
func (l *StdLogger) Debug(s string, v ...interface{}) (err error) { func (l *StdLogger) Debug(s string, v ...interface{}) (err error) {
if !l.EnableDebug { if !l.EnableDebug {
@@ -80,6 +94,7 @@ func (l *StdLogger) Debug(s string, v ...interface{}) (err error) {
return return
} }
// Emerg writes an EMERGENCY-level message to this StdLogger.
func (l *StdLogger) Emerg(s string, v ...interface{}) (err error) { func (l *StdLogger) Emerg(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -95,6 +110,7 @@ func (l *StdLogger) Emerg(s string, v ...interface{}) (err error) {
return return
} }
// Err writes an ERROR-level message to this StdLogger.
func (l *StdLogger) Err(s string, v ...interface{}) (err error) { func (l *StdLogger) Err(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -110,6 +126,7 @@ func (l *StdLogger) Err(s string, v ...interface{}) (err error) {
return return
} }
// Info writes an INFO-level message to this StdLogger.
func (l *StdLogger) Info(s string, v ...interface{}) (err error) { func (l *StdLogger) Info(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -125,6 +142,7 @@ func (l *StdLogger) Info(s string, v ...interface{}) (err error) {
return return
} }
// Notice writes a NOTICE-level message to this StdLogger.
func (l *StdLogger) Notice(s string, v ...interface{}) (err error) { func (l *StdLogger) Notice(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -140,6 +158,7 @@ func (l *StdLogger) Notice(s string, v ...interface{}) (err error) {
return return
} }
// Warning writes a WARNING/WARN-level message to this StdLogger.
func (l *StdLogger) Warning(s string, v ...interface{}) (err error) { func (l *StdLogger) Warning(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -155,6 +174,7 @@ func (l *StdLogger) Warning(s string, v ...interface{}) (err error) {
return return
} }
// renderWrite prepares/formats a log message to be written to this StdLogger.
func (l *StdLogger) renderWrite(msg, prio string) { func (l *StdLogger) renderWrite(msg, prio string) {
s := fmt.Sprintf("[%v] %v", prio, msg) s := fmt.Sprintf("[%v] %v", prio, msg)

View File

@@ -7,6 +7,7 @@ import (
"github.com/coreos/go-systemd/journal" "github.com/coreos/go-systemd/journal"
) )
// Setup sets up/configures a SystemDLogger and prepares it for use.
func (l *SystemDLogger) Setup() { func (l *SystemDLogger) Setup() {
// NOOP // NOOP
@@ -14,6 +15,7 @@ func (l *SystemDLogger) Setup() {
} }
// Shutdown cleanly shuts down a SystemDLogger.
func (l *SystemDLogger) Shutdown() { func (l *SystemDLogger) Shutdown() {
// NOOP // NOOP
@@ -21,18 +23,28 @@ func (l *SystemDLogger) Shutdown() {
} }
// GetPrefix returns the prefix used by this SystemDLogger.
func (l *SystemDLogger) GetPrefix() (prefix string) {
prefix = l.Prefix
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) { func (l *SystemDLogger) DoDebug(d bool) {
l.EnableDebug = d l.EnableDebug = d
} }
// SetPrefix sets the prefix for this SystemDLogger.
func (l *SystemDLogger) SetPrefix(prefix string) { func (l *SystemDLogger) SetPrefix(prefix string) {
l.Prefix = prefix l.Prefix = prefix
} }
func (l *SystemDLogger) GetPrefix() string { // Alert writes an ALERT-level message to this SystemDLogger.
return l.Prefix
}
func (l *SystemDLogger) Alert(s string, v ...interface{}) (err error) { func (l *SystemDLogger) Alert(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -48,6 +60,7 @@ func (l *SystemDLogger) Alert(s string, v ...interface{}) (err error) {
return return
} }
// Crit writes an CRITICAL-level message to this SystemDLogger.
func (l *SystemDLogger) Crit(s string, v ...interface{}) (err error) { func (l *SystemDLogger) Crit(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -63,6 +76,7 @@ func (l *SystemDLogger) Crit(s string, v ...interface{}) (err error) {
return return
} }
// Debug writes a DEBUG-level message to this SystemDLogger.
func (l *SystemDLogger) Debug(s string, v ...interface{}) (err error) { func (l *SystemDLogger) Debug(s string, v ...interface{}) (err error) {
if !l.EnableDebug { if !l.EnableDebug {
@@ -82,6 +96,7 @@ func (l *SystemDLogger) Debug(s string, v ...interface{}) (err error) {
return return
} }
// Emerg writes an EMERGENCY-level message to this SystemDLogger.
func (l *SystemDLogger) Emerg(s string, v ...interface{}) (err error) { func (l *SystemDLogger) Emerg(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -97,6 +112,7 @@ func (l *SystemDLogger) Emerg(s string, v ...interface{}) (err error) {
return return
} }
// Err writes an ERROR-level message to this SystemDLogger.
func (l *SystemDLogger) Err(s string, v ...interface{}) (err error) { func (l *SystemDLogger) Err(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -112,6 +128,7 @@ func (l *SystemDLogger) Err(s string, v ...interface{}) (err error) {
return return
} }
// Info writes an INFO-level message to this SystemDLogger.
func (l *SystemDLogger) Info(s string, v ...interface{}) (err error) { func (l *SystemDLogger) Info(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -127,6 +144,7 @@ func (l *SystemDLogger) Info(s string, v ...interface{}) (err error) {
return return
} }
// Notice writes a NOTICE-level message to this SystemDLogger.
func (l *SystemDLogger) Notice(s string, v ...interface{}) (err error) { func (l *SystemDLogger) Notice(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -142,6 +160,7 @@ func (l *SystemDLogger) Notice(s string, v ...interface{}) (err error) {
return return
} }
// Warning writes a WARNING/WARN-level message to this SystemDLogger.
func (l *SystemDLogger) Warning(s string, v ...interface{}) (err error) { func (l *SystemDLogger) Warning(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -157,7 +176,9 @@ func (l *SystemDLogger) Warning(s string, v ...interface{}) (err error) {
return return
} }
// renderWrite prepares/formats a log message to be written to this SystemDLogger.
func (l *SystemDLogger) renderWrite(msg string, prio journal.Priority) { func (l *SystemDLogger) renderWrite(msg string, prio journal.Priority) {
// TODO: implement code line, etc. // TODO: implement code line, etc.
// https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html // https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
// CODE_FILE=, CODE_LINE=, CODE_FUNC= // CODE_FILE=, CODE_LINE=, CODE_FUNC=

View File

@@ -6,6 +6,7 @@ import (
"log/syslog" "log/syslog"
) )
// Setup sets up/configures a SyslogLogger and prepares it for use.
func (l *SyslogLogger) Setup() { func (l *SyslogLogger) Setup() {
var err error var err error
@@ -37,6 +38,7 @@ func (l *SyslogLogger) Setup() {
} }
// Shutdown cleanly shuts down a SyslogLogger.
func (l *SyslogLogger) Shutdown() { func (l *SyslogLogger) Shutdown() {
var err error var err error
@@ -49,19 +51,29 @@ func (l *SyslogLogger) Shutdown() {
} }
// GetPrefix returns the prefix used by this SyslogLogger.
func (l *SyslogLogger) GetPrefix() (prefix string) {
prefix = l.Prefix
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) { func (l *SyslogLogger) DoDebug(d bool) {
l.EnableDebug = d l.EnableDebug = d
} }
// SetPrefix sets the prefix for this SyslogLogger.
func (l *SyslogLogger) SetPrefix(prefix string) { func (l *SyslogLogger) SetPrefix(prefix string) {
l.Prefix = prefix l.Prefix = prefix
l.Setup() l.Setup()
} }
func (l *SyslogLogger) GetPrefix() string { // Alert writes an ALERT-level message to this SyslogLogger.
return l.Prefix
}
func (l *SyslogLogger) Alert(s string, v ...interface{}) (err error) { func (l *SyslogLogger) Alert(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -79,6 +91,7 @@ func (l *SyslogLogger) Alert(s string, v ...interface{}) (err error) {
return return
} }
// Crit writes an CRITICAL-level message to this SyslogLogger.
func (l *SyslogLogger) Crit(s string, v ...interface{}) (err error) { func (l *SyslogLogger) Crit(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -96,6 +109,7 @@ func (l *SyslogLogger) Crit(s string, v ...interface{}) (err error) {
return return
} }
// Debug writes a DEBUG-level message to this SyslogLogger.
func (l *SyslogLogger) Debug(s string, v ...interface{}) (err error) { func (l *SyslogLogger) Debug(s string, v ...interface{}) (err error) {
if !l.EnableDebug { if !l.EnableDebug {
@@ -117,6 +131,7 @@ func (l *SyslogLogger) Debug(s string, v ...interface{}) (err error) {
return return
} }
// Emerg writes an EMERGENCY-level message to this SyslogLogger.
func (l *SyslogLogger) Emerg(s string, v ...interface{}) (err error) { func (l *SyslogLogger) Emerg(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -134,6 +149,7 @@ func (l *SyslogLogger) Emerg(s string, v ...interface{}) (err error) {
return return
} }
// Err writes an ERROR-level message to this SyslogLogger.
func (l *SyslogLogger) Err(s string, v ...interface{}) (err error) { func (l *SyslogLogger) Err(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -151,6 +167,7 @@ func (l *SyslogLogger) Err(s string, v ...interface{}) (err error) {
return return
} }
// Info writes an INFO-level message to this SyslogLogger.
func (l *SyslogLogger) Info(s string, v ...interface{}) (err error) { func (l *SyslogLogger) Info(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -168,6 +185,7 @@ func (l *SyslogLogger) Info(s string, v ...interface{}) (err error) {
return return
} }
// Notice writes a NOTICE-level message to this SyslogLogger.
func (l *SyslogLogger) Notice(s string, v ...interface{}) (err error) { func (l *SyslogLogger) Notice(s string, v ...interface{}) (err error) {
var msg string var msg string
@@ -185,6 +203,7 @@ func (l *SyslogLogger) Notice(s string, v ...interface{}) (err error) {
return return
} }
// Warning writes a WARNING/WARN-level message to this SyslogLogger.
func (l *SyslogLogger) Warning(s string, v ...interface{}) (err error) { func (l *SyslogLogger) Warning(s string, v ...interface{}) (err error) {
var msg string var msg string

114
logging/funcs_windows.go Normal file
View File

@@ -0,0 +1,114 @@
package logging
import (
`errors`
`path`
`strings`
`r00t2.io/goutils/bitmask`
`r00t2.io/sysutils/paths`
)
/*
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.
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 bitmask.MaskBit
var exists bool
var success bool
var ckLogPaths []string
if strings.TrimSpace(source) == "" {
err = errors.New("invalid source for Windows logging")
return
}
// Configure system-supported logger(s). The Windows Event Logger (should) ALWAYS be available.
logFlags.AddFlag(LogWinLogger)
if eventIDs == nil {
eventIDs = DefaultEventID
}
if logPaths != nil {
ckLogPaths = logPaths
ckLogPaths = append(ckLogPaths, defLogPaths...)
for _, p := range ckLogPaths {
if exists, _ = paths.RealPathExists(&p); exists {
if success, err = testOpen(p); err != nil {
continue
} else if !success {
continue
}
logFlags.AddFlag(LogFile)
logPath = p
break
} else {
dirPath := path.Dir(p)
if err = paths.MakeDirIfNotExist(dirPath); err != nil {
continue
}
if success, err = testOpen(p); err != nil {
continue
} else if !success {
continue
}
logFlags.AddFlag(LogFile)
logPath = p
break
}
}
}
if logFlags.HasFlag(LogWinLogger) {
logger = &WinLogger{
Prefix: source,
EnableDebug: enableDebug,
eids: eventIDs,
}
} else {
if logFlags.HasFlag(LogFile) {
logger = &FileLogger{
StdLogger: StdLogger{
Prefix: source,
EnableDebug: enableDebug,
},
Path: logPath,
}
} else {
logger = &StdLogger{
Prefix: source,
EnableDebug: enableDebug,
}
}
}
logger.Setup()
logger.Info("logger initialized of type %T with source %v", logger, logger.GetPrefix())
return
}

View File

@@ -0,0 +1,257 @@
package logging
import (
`errors`
`fmt`
`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
/*
First a sanity check on the EventIDs.
Since we use eventcreate, all Event IDs must be 1 <= eid <= 1000.
*/
for _, eid := range []uint32{
l.eids.Alert,
l.eids.Crit,
l.eids.Debug,
l.eids.Emerg,
l.eids.Err,
l.eids.Info,
l.eids.Notice,
l.eids.Warning,
} {
if !((eid <= 1000) && (1 <= eid)) {
err = errors.New("event IDs must be between 1 and 1000 inclusive")
panic(err)
}
}
if err = eventlog.InstallAsEventCreate(l.Prefix, eventlog.Error|eventlog.Warning|eventlog.Info); err != nil {
if idx := ptrnSourceExists.FindStringIndex(err.Error()); idx == nil {
// It's an error we want to panic on.
panic(err)
} else {
// It already exists, so ignore the error.
err = nil
}
}
if l.elog, err = eventlog.Open(l.Prefix); err != nil {
panic(err)
}
}
// Shutdown cleanly shuts down a WinLogger.
func (l *WinLogger) Shutdown() {
var err error
if err = l.elog.Close(); err != nil {
panic(err)
}
if err = eventlog.Remove(l.Prefix); err != nil {
panic(err)
}
}
// 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
l.Prefix = prefix
// To properly change the prefix, we need to tear down the old event log and create a new one.
if err = l.elog.Close(); err != nil {
panic(err)
}
if err = eventlog.Remove(l.Prefix); err != nil {
panic(err)
}
if err = eventlog.InstallAsEventCreate(l.Prefix, eventlog.Error|eventlog.Warning|eventlog.Info); err != nil {
if idx := ptrnSourceExists.FindStringIndex(err.Error()); idx == nil {
// It's an error we want to panic on.
panic(err)
} else {
// It already exists, so ignore the error.
err = nil
}
}
if l.elog, err = eventlog.Open(l.Prefix); err != nil {
panic(err)
}
}
// Alert writes an ALERT-level message to this WinLogger.
func (l *WinLogger) Alert(s string, v ...interface{}) (err error) {
var msg string
if v != nil {
msg = fmt.Sprintf(s, v...)
} else {
msg = s
}
// Treat ALERT as Warning
err = l.elog.Warning(l.eids.Alert, msg)
return
}
// Crit writes an CRITICAL-level message to this WinLogger.
func (l *WinLogger) Crit(s string, v ...interface{}) (err error) {
var msg string
if v != nil {
msg = fmt.Sprintf(s, v...)
} else {
msg = s
}
// Treat CRIT as Error
err = l.elog.Error(l.eids.Crit, msg)
return
}
// Debug writes a DEBUG-level message to this WinLogger.
func (l *WinLogger) Debug(s string, v ...interface{}) (err error) {
if !l.EnableDebug {
return
}
var msg string
if v != nil {
msg = fmt.Sprintf(s, v...)
} else {
msg = s
}
// Treat DEBUG as Info
err = l.elog.Info(l.eids.Debug, msg)
return
}
// Emerg writes an EMERGENCY-level message to this WinLogger.
func (l *WinLogger) Emerg(s string, v ...interface{}) (err error) {
var msg string
if v != nil {
msg = fmt.Sprintf(s, v...)
} else {
msg = s
}
// Treat EMERG as Error
err = l.elog.Error(l.eids.Emerg, msg)
return
}
// Err writes an ERROR-level message to this WinLogger.
func (l *WinLogger) Err(s string, v ...interface{}) (err error) {
var msg string
if v != nil {
msg = fmt.Sprintf(s, v...)
} else {
msg = s
}
err = l.elog.Error(l.eids.Err, msg)
return
}
// Info writes an INFO-level message to this WinLogger.
func (l *WinLogger) Info(s string, v ...interface{}) (err error) {
var msg string
if v != nil {
msg = fmt.Sprintf(s, v...)
} else {
msg = s
}
err = l.elog.Info(l.eids.Info, msg)
return
}
// Notice writes a NOTICE-level message to this WinLogger.
func (l *WinLogger) Notice(s string, v ...interface{}) (err error) {
var msg string
if v != nil {
msg = fmt.Sprintf(s, v...)
} else {
msg = s
}
// Treat NOTICE as Info
err = l.elog.Info(l.eids.Notice, msg)
return
}
// Warning writes a WARNING/WARN-level message to this WinLogger.
func (l *WinLogger) Warning(s string, v ...interface{}) (err error) {
var msg string
if v != nil {
msg = fmt.Sprintf(s, v...)
} else {
msg = s
}
err = l.elog.Warning(l.eids.Warning, msg)
return
}

View File

@@ -2,52 +2,72 @@ package logging
import ( import (
"log" "log"
"log/syslog" `os`
"os"
) )
/*
Logger is one of the various loggers offered by this module.
*/
type Logger interface { type Logger interface {
Alert(string, ...interface{}) error Alert(s string, v ...interface{}) (err error)
Crit(string, ...interface{}) error Crit(s string, v ...interface{}) (err error)
Debug(string, ...interface{}) error Debug(s string, v ...interface{}) (err error)
Emerg(string, ...interface{}) error Emerg(s string, v ...interface{}) (err error)
Err(string, ...interface{}) error Err(s string, v ...interface{}) (err error)
Info(string, ...interface{}) error Info(s string, v ...interface{}) (err error)
Notice(string, ...interface{}) error Notice(s string, v ...interface{}) (err error)
Warning(string, ...interface{}) error Warning(s string, v ...interface{}) (err error)
DoDebug(bool) DoDebug(d bool)
SetPrefix(string) SetPrefix(p string)
GetPrefix() string GetPrefix() (p string)
Setup() Setup()
Shutdown() Shutdown()
} }
type SystemDLogger struct { /*
EnableDebug bool StdLogger uses the log package in stdlib to perform all logging. The default is to write to STDOUT.
Prefix string If you wish to modify the underling log.Logger object, you can access it directly via StdLogger.Logger.
} */
type SyslogLogger struct {
EnableDebug bool
Prefix string
alert,
crit,
debug,
emerg,
err,
info,
notice,
warning *syslog.Writer
}
type StdLogger struct { type StdLogger struct {
// All log.Logger fields/methods are exposed.
*log.Logger *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 EnableDebug bool
// Prefix indicates the prefix for log entries; in shared logs, this helps differentiate the source.
Prefix string Prefix string
} }
// FileLogger uses a StdLogger with a file handle writer to write to the file given at Path.
type FileLogger struct { type FileLogger struct {
// StdLogger is used for the log formation and handling. See StdLogger for more details.
StdLogger StdLogger
// Path is the path to the logfile.
Path string Path string
/*
EnableStdOut is true if the log will send to STDOUT as well as the file.
If false (default), it will only (silently) write to the log file.
You will need to run *FileLogger.Shutdown and then *FileLogger.Setup again if you wish to change this.
*/
EnableStdOut bool
// writer is used for the writing out of the log file.
writer *os.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
}

23
logging/types_linux.go Normal file
View File

@@ -0,0 +1,23 @@
package logging
import (
`log/syslog`
)
type SystemDLogger struct {
EnableDebug bool
Prefix string
}
type SyslogLogger struct {
EnableDebug bool
Prefix string
alert,
crit,
debug,
emerg,
err,
info,
notice,
warning *syslog.Writer
}

23
logging/types_windows.go Normal file
View File

@@ -0,0 +1,23 @@
package logging
import (
`golang.org/x/sys/windows/svc/eventlog`
)
type WinLogger struct {
EnableDebug bool
Prefix string
elog *eventlog.Log
eids *WinEventID
}
type WinEventID struct {
Alert,
Crit,
Debug,
Emerg,
Err,
Info,
Notice,
Warning uint32
}

63
multierr/doc.go Normal file
View File

@@ -0,0 +1,63 @@
/*
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 interface.
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. If calling in a function, you'll probably want to do:
// if !multierror.IsEmpty() {
// err = multierror
// }
}
In the above, the multierror assignment can still be used as an error.
*/
package multierr

110
multierr/funcs.go Normal file
View File

@@ -0,0 +1,110 @@
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) {
var realErrs []error = make([]error, 0)
if errs != nil {
for _, e := range errs {
if e == nil {
continue
}
realErrs = append(realErrs, e)
}
}
m = &MultiError{
Errors: realErrs,
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)
}
// Count returns the number of errors in a MultiError.
func (e *MultiError) Count() (n int) {
n = len(e.Errors)
return
}
// IsEmpty is a shorthand for testing if e.Errors is empty.
func (e *MultiError) IsEmpty() (empty bool) {
if e.Count() == 0 {
empty = true
}
return
}

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

View File

@@ -1,33 +0,0 @@
package types
type BitMask interface {
HasFlag(bit MaskBit) bool
AddFlag(bit MaskBit)
ClearFlag(bit MaskBit)
ToggleFlag(bit MaskBit)
}
// BitMasks
type MaskBit uint8
func (f MaskBit) HasFlag(flag MaskBit) (r bool) {
if f&flag != 0 {
r = true
}
return
}
func (f *MaskBit) AddFlag(flag MaskBit) {
*f |= flag
return
}
func (f *MaskBit) ClearFlag(flag MaskBit) {
*f &= flag
return
}
func (f *MaskBit) ToggleFlag(flag MaskBit) {
*f ^= flag
return
}