Compare commits
17 Commits
Author | SHA1 | Date |
---|---|---|
brent saner | ae49f42c0c | |
brent saner | b87934e8a9 | |
brent s | 70d6c2cbb3 | |
brent s. | a445a51c0d | |
brent s. | a2a849600b | |
brent s. | 94145fb4c7 | |
brent s. | 81a2d308f0 | |
brent s. | c4b3c6441a | |
brent s. | 1c5abd4083 | |
brent s. | d98363c0d7 | |
brent s. | 39e0a1fd43 | |
brent s. | 3d0d420454 | |
brent s. | ef0a4d825d | |
brent s. | 4addc44c0f | |
brent s. | 0e01306637 | |
brent s. | 3975f8b11f | |
brent s | d5b1d449e5 |
|
@ -28,6 +28,11 @@
|
|||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
# But DO include the actual tests.
|
||||
!_test.go
|
||||
!*_test.go
|
||||
!*_test_*.go
|
||||
!*_test/
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
package bitmask
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// MaskBit is a flag container.
|
||||
type MaskBit uint
|
||||
|
||||
/*
|
||||
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
|
||||
}
|
||||
|
||||
// NewMaskBitExplicit is like NewMaskBit, but allows you to specify a non-zero (0x0) value.
|
||||
func NewMaskBitExplicit(value uint) (m *MaskBit) {
|
||||
|
||||
var v MaskBit = MaskBit(value)
|
||||
|
||||
m = &v
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/*
|
||||
Bytes returns the current value of a MasBit as a byte slice (big-endian).
|
||||
|
||||
If trim is false, b will (probably) be 4 bytes long if you're on a 32-bit size system,
|
||||
and b will (probably) be 8 bytes long if you're on a 64-bit size system. You can determine
|
||||
the size of the resulting slice via (math/)bits.UintSize / 8.
|
||||
|
||||
If trim is true, it will trim leading null bytes (if any). This will lead to an unpredictable
|
||||
byte slice length in b, but is most likely preferred for byte operations.
|
||||
|
||||
*/
|
||||
func (m *MaskBit) Bytes(trim bool) (b []byte) {
|
||||
|
||||
var b2 []byte
|
||||
var size int = bits.UintSize / 8
|
||||
var err error
|
||||
|
||||
b2 = make([]byte, size)
|
||||
|
||||
switch s := bits.UintSize; s {
|
||||
case 32:
|
||||
binary.BigEndian.PutUint32(b2[:], uint32(*m))
|
||||
case 64:
|
||||
binary.BigEndian.PutUint64(b2[:], uint64(*m))
|
||||
default:
|
||||
err = errors.New("unsupported Uint/system bit size")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if trim {
|
||||
b = bytes.TrimLeft(b2, "\x00")
|
||||
return
|
||||
} else {
|
||||
b = b2
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Copy returns a pointer to a (new) copy of a MaskBit.
|
||||
func (m *MaskBit) Copy() (newM *MaskBit) {
|
||||
|
||||
newM = new(MaskBit)
|
||||
*newM = *m
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Value returns the current raw uint value of a MaskBit.
|
||||
func (m *MaskBit) Value() (v uint) {
|
||||
|
||||
v = uint(*m)
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
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 bitmask.MaskBit = 0
|
||||
const (
|
||||
OPT1 bitmask.MaskBit = 1 << iota
|
||||
OPT2
|
||||
OPT3
|
||||
// ...
|
||||
)
|
||||
|
||||
var MyMask *bitmask.MaskBit
|
||||
|
||||
func main() {
|
||||
MyMask = bitmask.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)
|
||||
|
||||
If you need something with more flexibility (as always, at the cost of complexity),
|
||||
you may be interested in one of the following libraries:
|
||||
|
||||
. github.com/alvaroloes/enumer
|
||||
. github.com/abice/go-enum
|
||||
. github.com/jeffreyrichter/enum/enum
|
||||
|
||||
*/
|
||||
package bitmask
|
4
go.mod
4
go.mod
|
@ -4,5 +4,7 @@ go 1.16
|
|||
|
||||
require (
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
r00t2.io/sysutils v0.0.0-20210224054841-55ac47c86928
|
||||
github.com/google/uuid v1.3.0
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
|
||||
r00t2.io/sysutils v1.1.1
|
||||
)
|
||||
|
|
9
go.sum
9
go.sum
|
@ -1,5 +1,8 @@
|
|||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/jszwec/csvutil v1.5.0/go.mod h1:Rpu7Uu9giO9subDyMCIQfHVDuLrcaC36UA4YcJjGBkg=
|
||||
r00t2.io/sysutils v0.0.0-20210224054841-55ac47c86928 h1:aYEn20eguqsmqT3J9VjkzdhyPwmOVDGzzffcEfV18a4=
|
||||
r00t2.io/sysutils v0.0.0-20210224054841-55ac47c86928/go.mod h1:XzJkBF6SHAODEszJlOcjtGoTHwYnZZNmseA6PyOujes=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
r00t2.io/sysutils v1.1.1 h1:q2P5u50HIIRk6muCPo1Gpapy6sNT4oaB1l2O/C/mi3A=
|
||||
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=
|
||||
|
|
13
logging/TODO
13
logging/TODO
|
@ -1,3 +1,16 @@
|
|||
- Implement code line/func/etc. (only for debug?):
|
||||
https://stackoverflow.com/a/24809646
|
||||
https://golang.org/pkg/runtime/#Caller
|
||||
-- log.LlongFile and log.Lshortfile flags don't currently work properly for StdLogger/FileLogger; they refer to the file in logging package rather than the caller.
|
||||
|
||||
- StdLogger2; where stdout and stderr are both logged to depending on severity level.
|
||||
- make configurable via OR bitmask
|
||||
|
||||
- Suport remote loggers? (eventlog, syslog, systemd)
|
||||
|
||||
- JSON logger? YAML logger? XML logger?
|
||||
|
||||
- DOCS.
|
||||
-- Done, but flesh out.
|
||||
|
||||
- Implement io.Writer interfaces
|
||||
|
|
|
@ -1,29 +1,14 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
`log/syslog`
|
||||
`os`
|
||||
|
||||
`r00t2.io/goutils/types`
|
||||
)
|
||||
|
||||
const (
|
||||
devlog string = "/dev/log"
|
||||
// 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
|
||||
syslogFacility syslog.Priority = syslog.LOG_USER
|
||||
)
|
||||
|
||||
// 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"}
|
||||
// appendFlags are the flags used for testing the file (and opening/writing).
|
||||
appendFlags int = os.O_APPEND | os.O_CREATE | os.O_WRONLY
|
||||
)
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
)
|
|
@ -0,0 +1,39 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
`log`
|
||||
)
|
||||
|
||||
/*
|
||||
The following are strings written to the Logger in the various tests.
|
||||
The %v is populated with the name of the type of Logger.
|
||||
*/
|
||||
const (
|
||||
testAlert string = "This is a test ALERT-priority log message for logger %v."
|
||||
testCrit string = "This is a test CRITICAL-priority (CRIT) log message for logger %v."
|
||||
testDebug string = "This is a test DEBUG-priority log message for logger %v."
|
||||
testEmerg string = "This is a test EMERGENCY-priority (EMERG) log message for logger %v."
|
||||
testErr string = "This is a test ERROR-priority (ERR) log message for logger %v."
|
||||
testInfo string = "This is a test INFO-priority log message for logger %v."
|
||||
testNotice string = "This is a test NOTICE-priority log message for logger %v."
|
||||
testWarning string = "This is a test WARNING-priority log message for logger %v."
|
||||
)
|
||||
|
||||
// Prefixes to use for tests.
|
||||
const (
|
||||
// TestLogPrefix is used as the initial prefix.
|
||||
TestLogPrefix string = "LOGGING_TESTRUN"
|
||||
// TestLogAltPrefix is used as the alternative prefix to Logger.SetPrefix.
|
||||
TestLogAltPrefix string = "LOGGING_TESTRUN_ALT"
|
||||
)
|
||||
|
||||
const (
|
||||
// EnvVarKeepLog is the env var key/var name to use to suppress removal of FileLogger.Path after tests complete.
|
||||
EnvVarKeepLog string = "LOGGING_KEEP_TEMPLOG"
|
||||
)
|
||||
|
||||
const (
|
||||
// logFlags are used to set the log flags for StdLogger (and FileLogger.StdLogger).
|
||||
// logFlags int = log.Ldate | log.Lmicroseconds | log.Llongfile | log.LUTC
|
||||
logFlags int = log.Ldate | log.Lmicroseconds | log.Lshortfile | log.LUTC
|
||||
)
|
|
@ -0,0 +1,68 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
`os`
|
||||
`path/filepath`
|
||||
|
||||
`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.
|
||||
|
||||
Deprecated: this is handled differently now.
|
||||
*/
|
||||
// var ptrnSourceExists *regexp.Regexp = regexp.MustCompile(`registry\skey\salready\sexists$`)
|
||||
|
||||
const (
|
||||
EIDMin uint32 = 1
|
||||
EIDMax uint32 = 1000
|
||||
)
|
||||
|
||||
const (
|
||||
eventLogRegistryKey string = "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application"
|
||||
)
|
||||
|
||||
// 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 (DefaultEventID, specifically).
|
||||
const (
|
||||
EventAlert uint32 = 1 << iota
|
||||
EventCrit
|
||||
EventDebug
|
||||
EventEmerg
|
||||
EventErr
|
||||
EventInfo
|
||||
EventNotice
|
||||
EventWarning
|
||||
)
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
Package logging implements and presents various loggers under a unified interface, making them completely swappable.
|
||||
|
||||
These particular loggers (logging.Logger) available are:
|
||||
|
||||
NullLogger
|
||||
StdLogger
|
||||
FileLogger
|
||||
SystemDLogger (Linux only)
|
||||
SyslogLogger (Linux only)
|
||||
WinLogger (Windows only)
|
||||
|
||||
There is a seventh type of logging.Logger, MultiLogger, that allows for multiple loggers to be written to with a single call.
|
||||
As you may have guessed, NullLogger doesn't actually log anything but is fully "functional" as a logging.Logger.
|
||||
|
||||
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) (err error)
|
||||
GetDebug() (d bool)
|
||||
SetPrefix(p string) (err error)
|
||||
GetPrefix() (p string, err error)
|
||||
Setup() (err error)
|
||||
Shutdown() (err error)
|
||||
|
||||
In some cases, Logger.Setup and Logger.Shutdown are no-ops. In other cases, they perform necessary initialization/cleanup and closing of the logger.
|
||||
It is recommended to *always* run Setup and Shutdown before and after using, respectively, regardless of the actual logging.Logger type.
|
||||
*/
|
||||
package logging
|
|
@ -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")
|
||||
)
|
|
@ -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))
|
||||
)
|
|
@ -0,0 +1,15 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
`errors`
|
||||
`fmt`
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrBadBinPath is returned if installing a binary-registered Event Log source instead of using EventCreate.exe.
|
||||
ErrBadBinPath error = errors.New("evaluated binary path does not actually exist")
|
||||
// ErrBadPerms is returned if an access denied error is received when attempting to register, write to, close, etc. a source without proper perms.
|
||||
ErrBadPerms error = errors.New("access denied when attempting to register Event Log source")
|
||||
// ErrBadEid is returned if an event ID is within an invalid range.
|
||||
ErrBadEid error = errors.New(fmt.Sprintf("event IDs must be between %v and %v inclusive", EIDMin, EIDMax))
|
||||
)
|
123
logging/funcs.go
123
logging/funcs.go
|
@ -1,134 +1,21 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
native "log"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"r00t2.io/goutils/types"
|
||||
|
||||
sysd "github.com/coreos/go-systemd/journal"
|
||||
"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.
|
||||
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
|
||||
}
|
||||
|
||||
// testOpen attempts to open a file for writing to test for suitability as a LogFile path.
|
||||
func testOpen(path string) (success bool, err error) {
|
||||
|
||||
var f *os.File
|
||||
|
||||
// Example #2, https://golang.org/pkg/os/#OpenFile
|
||||
if f, err = os.OpenFile(path, appendFlags, logPerm); err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
if err = f.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
success = true
|
||||
|
||||
|
|
|
@ -1,49 +1,94 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (l *FileLogger) Setup() {
|
||||
// Setup sets up/configures a FileLogger and prepares it for use.
|
||||
func (l *FileLogger) Setup() (err error) {
|
||||
|
||||
var err error
|
||||
|
||||
l.Logger = log.Default()
|
||||
l.Logger.SetPrefix(l.Prefix)
|
||||
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)
|
||||
// This uses a shared handle across the import. We don't want that.
|
||||
// l.Logger = log.Default()
|
||||
if l.Prefix != "" {
|
||||
l.Prefix = strings.TrimRight(l.Prefix, " ") + " "
|
||||
// l.Logger.SetPrefix(l.Prefix)
|
||||
}
|
||||
// https://stackoverflow.com/a/36719588/733214
|
||||
multi := io.MultiWriter(os.Stdout, l.writer)
|
||||
l.Logger.SetOutput(multi)
|
||||
if l.writer, err = os.OpenFile(l.Path, appendFlags, logPerm); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
l.Logger = log.New(l.writer, l.Prefix, l.LogFlags)
|
||||
// l.Logger.SetOutput(multi)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *FileLogger) Shutdown() {
|
||||
|
||||
var err error
|
||||
// Shutdown cleanly shuts down a FileLogger.
|
||||
func (l *FileLogger) Shutdown() (err error) {
|
||||
|
||||
if err = l.writer.Close(); err != nil {
|
||||
log.Panicf("could not close log file \"%v\": %v\n", l.Path, err)
|
||||
if !errors.Is(err, fs.ErrClosed) {
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *FileLogger) GetPrefix() string {
|
||||
return l.Prefix
|
||||
/*
|
||||
GetPrefix returns the prefix used by this FileLogger.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *FileLogger) GetPrefix() (prefix string, err error) {
|
||||
|
||||
prefix = l.Prefix
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *FileLogger) DoDebug(d bool) {
|
||||
/*
|
||||
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.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *FileLogger) DoDebug(d bool) (err error) {
|
||||
|
||||
l.EnableDebug = d
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *FileLogger) SetPrefix(prefix string) {
|
||||
// GetDebug returns the debug status of this FileLogger.
|
||||
func (l *FileLogger) GetDebug() (d bool) {
|
||||
|
||||
d = l.EnableDebug
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
SetPrefix sets the prefix for this FileLogger.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *FileLogger) SetPrefix(prefix string) (err error) {
|
||||
|
||||
l.Prefix = prefix
|
||||
l.Logger.SetPrefix(prefix)
|
||||
if prefix != "" {
|
||||
l.Prefix = strings.TrimRight(l.Prefix, " ") + " "
|
||||
}
|
||||
l.Logger.SetPrefix(l.Prefix)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Alert writes an ALERT-level message to this FileLogger.
|
||||
func (l *FileLogger) Alert(s string, v ...interface{}) (err error) {
|
||||
|
||||
var msg string
|
||||
|
@ -59,6 +104,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 +120,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 +140,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 +156,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 +172,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 +188,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 +204,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 +220,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)
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
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.
|
||||
|
||||
logConfigFlags is the corresponding flag(s) OR'd for StdLogger.LogFlags / FileLogger.StdLogger.LogFlags if either is selected. See StdLogger.LogFlags and
|
||||
https://pkg.go.dev/log#pkg-constants for details.
|
||||
|
||||
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, logConfigFlags int, logPaths ...string) (logger Logger, err error) {
|
||||
|
||||
var logPath string
|
||||
var logFlags bitmask.MaskBit
|
||||
var currentPrefix string
|
||||
|
||||
// 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,
|
||||
LogFlags: logConfigFlags,
|
||||
},
|
||||
Path: logPath,
|
||||
}
|
||||
} else {
|
||||
logger = &StdLogger{
|
||||
Prefix: logPrefix,
|
||||
EnableDebug: enableDebug,
|
||||
LogFlags: logConfigFlags,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if prefix != "\x00" {
|
||||
if err = logger.SetPrefix(prefix); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err = logger.Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if currentPrefix, err = logger.GetPrefix(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("logger initialized of type %T with prefix %v", logger, currentPrefix)
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,270 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
`fmt`
|
||||
`os`
|
||||
`testing`
|
||||
)
|
||||
|
||||
/*
|
||||
TestSysDLogger tests functionality for SystemDLogger.
|
||||
*/
|
||||
func TestSysDLogger(t *testing.T) {
|
||||
|
||||
var l *SystemDLogger
|
||||
var ltype string = "SystemDLogger"
|
||||
var prefix string
|
||||
var err error
|
||||
|
||||
l = &SystemDLogger{
|
||||
EnableDebug: true,
|
||||
Prefix: TestLogPrefix,
|
||||
}
|
||||
|
||||
if err = l.Setup(); err != nil {
|
||||
t.Fatalf("error when running Setup: %v", err.Error())
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l)
|
||||
|
||||
if err = l.Alert(testAlert, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Crit(testCrit, ltype); err != nil {
|
||||
t.Fatalf("error for Crit: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Debug(testDebug, ltype); err != nil {
|
||||
t.Fatalf("error for Debug: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Emerg(testEmerg, ltype); err != nil {
|
||||
t.Fatalf("error for Emerg: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Err(testErr, ltype); err != nil {
|
||||
t.Fatalf("error for Err: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Info(testInfo, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Notice(testNotice, ltype); err != nil {
|
||||
t.Fatalf("error for Notice: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Warning(testWarning, ltype); err != nil {
|
||||
t.Fatalf("error for Warning: %v", err.Error())
|
||||
}
|
||||
|
||||
if prefix, err = l.GetPrefix(); err != nil {
|
||||
t.Fatalf("error when fetching prefix: %v", err.Error())
|
||||
}
|
||||
|
||||
if prefix != TestLogPrefix {
|
||||
t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix)
|
||||
}
|
||||
if err = l.SetPrefix(TestLogAltPrefix); err != nil {
|
||||
t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error())
|
||||
} else {
|
||||
_ = l.SetPrefix(TestLogPrefix)
|
||||
}
|
||||
|
||||
if err = l.DoDebug(false); err != nil {
|
||||
t.Fatalf("error when changing debug to false: %v", err.Error())
|
||||
} else if l.EnableDebug {
|
||||
t.Fatalf("did not properly set Debug filter state")
|
||||
} else {
|
||||
_ = l.DoDebug(true)
|
||||
}
|
||||
|
||||
if err = l.Shutdown(); err != nil {
|
||||
t.Fatalf("Error when running Shutdown: %v", err.Error())
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed all logging targets.", ltype)
|
||||
}
|
||||
|
||||
/*
|
||||
TestSyslogLogger tests functionality for SyslogLogger.
|
||||
*/
|
||||
func TestSyslogLogger(t *testing.T) {
|
||||
|
||||
var l *SyslogLogger
|
||||
var ltype string = "SyslogLogger"
|
||||
var prefix string
|
||||
var err error
|
||||
|
||||
l = &SyslogLogger{
|
||||
EnableDebug: true,
|
||||
Prefix: TestLogPrefix,
|
||||
}
|
||||
|
||||
if err = l.Setup(); err != nil {
|
||||
t.Fatalf("error when running Setup: %v", err.Error())
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l)
|
||||
|
||||
if err = l.Alert(testAlert, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Crit(testCrit, ltype); err != nil {
|
||||
t.Fatalf("error for Crit: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Debug(testDebug, ltype); err != nil {
|
||||
t.Fatalf("error for Debug: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Emerg(testEmerg, ltype); err != nil {
|
||||
t.Fatalf("error for Emerg: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Err(testErr, ltype); err != nil {
|
||||
t.Fatalf("error for Err: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Info(testInfo, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Notice(testNotice, ltype); err != nil {
|
||||
t.Fatalf("error for Notice: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Warning(testWarning, ltype); err != nil {
|
||||
t.Fatalf("error for Warning: %v", err.Error())
|
||||
}
|
||||
|
||||
if prefix, err = l.GetPrefix(); err != nil {
|
||||
t.Fatalf("error when fetching prefix: %v", err.Error())
|
||||
}
|
||||
|
||||
if prefix != TestLogPrefix {
|
||||
t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix)
|
||||
}
|
||||
if err = l.SetPrefix(TestLogAltPrefix); err != nil {
|
||||
t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error())
|
||||
} else {
|
||||
_ = l.SetPrefix(TestLogPrefix)
|
||||
}
|
||||
|
||||
if err = l.DoDebug(false); err != nil {
|
||||
t.Fatalf("error when changing debug to false: %v", err.Error())
|
||||
} else if l.EnableDebug {
|
||||
t.Fatalf("did not properly set Debug filter state")
|
||||
} else {
|
||||
_ = l.DoDebug(true)
|
||||
}
|
||||
|
||||
if err = l.Shutdown(); err != nil {
|
||||
t.Fatalf("Error when running Shutdown: %v", err.Error())
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed all logging targets.", ltype)
|
||||
}
|
||||
|
||||
// TestDefaultLogger tests GetLogger.
|
||||
func TestDefaultLogger(t *testing.T) {
|
||||
|
||||
var l Logger
|
||||
var tempfile *os.File
|
||||
var tempfilePath string
|
||||
var keepLog bool
|
||||
var ltype string
|
||||
var prefix string
|
||||
var testPrefix string
|
||||
var err error
|
||||
|
||||
if tempfile, err = os.CreateTemp("", ".LOGGINGTEST_*"); err != nil {
|
||||
t.Fatalf("error when creating temporary log file '%v': %v", tempfile.Name(), err.Error())
|
||||
}
|
||||
tempfilePath = tempfile.Name()
|
||||
// We can close the handler immediately; we don't need it since the FileLogger opens its own.
|
||||
if err = tempfile.Close(); err != nil {
|
||||
t.Fatalf("error when closing handler for temporary log file '%v': %v", tempfile.Name(), err.Error())
|
||||
}
|
||||
|
||||
if l, err = GetLogger(true, TestLogPrefix, logFlags, tempfilePath); err != nil {
|
||||
t.Fatalf("error when spawning default Linux logger via GetLogger: %v", err.Error())
|
||||
}
|
||||
|
||||
ltype = fmt.Sprintf("%T", l)
|
||||
|
||||
t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l)
|
||||
|
||||
if err = l.Alert(testAlert, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Crit(testCrit, ltype); err != nil {
|
||||
t.Fatalf("error for Crit: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Debug(testDebug, ltype); err != nil {
|
||||
t.Fatalf("error for Debug: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Emerg(testEmerg, ltype); err != nil {
|
||||
t.Fatalf("error for Emerg: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Err(testErr, ltype); err != nil {
|
||||
t.Fatalf("error for Err: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Info(testInfo, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Notice(testNotice, ltype); err != nil {
|
||||
t.Fatalf("error for Notice: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Warning(testWarning, ltype); err != nil {
|
||||
t.Fatalf("error for Warning: %v", err.Error())
|
||||
}
|
||||
|
||||
if prefix, err = l.GetPrefix(); err != nil {
|
||||
t.Fatalf("error when fetching prefix: %v", err.Error())
|
||||
}
|
||||
|
||||
if ltype == "StdLogger" || ltype == "FileLogger" { // StdLogger (and thus FileLogger) adds a space at the end.
|
||||
testPrefix = TestLogPrefix + " "
|
||||
} else {
|
||||
testPrefix = TestLogPrefix
|
||||
}
|
||||
|
||||
if prefix != testPrefix {
|
||||
t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix)
|
||||
}
|
||||
if err = l.SetPrefix(TestLogAltPrefix); err != nil {
|
||||
t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error())
|
||||
} else {
|
||||
_ = l.SetPrefix(TestLogPrefix)
|
||||
}
|
||||
|
||||
if err = l.DoDebug(false); err != nil {
|
||||
t.Fatalf("error when changing debug to false: %v", err.Error())
|
||||
} else {
|
||||
_ = l.DoDebug(true)
|
||||
}
|
||||
|
||||
if err = l.Shutdown(); err != nil {
|
||||
t.Fatalf("Error when running Shutdown: %v", err.Error())
|
||||
}
|
||||
|
||||
_, keepLog = os.LookupEnv(EnvVarKeepLog)
|
||||
|
||||
if !keepLog {
|
||||
if err = os.Remove(tempfilePath); err != nil {
|
||||
t.Fatalf("error when removing temporary log file '%v': %v", tempfilePath, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed all logging targets.", ltype)
|
||||
}
|
|
@ -0,0 +1,372 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"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() (err error) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var errs *multierr.MultiError = multierr.NewMultiError(nil)
|
||||
|
||||
for logName, l := range m.Loggers {
|
||||
wg.Add(1)
|
||||
go func(logger Logger, lName string) {
|
||||
var err2 error
|
||||
defer wg.Done()
|
||||
if err2 = logger.Setup(); err2 != nil {
|
||||
errs.AddError(errors.New(fmt.Sprintf("error on Setup for logger %v; follows (may be out of order):", lName)))
|
||||
errs.AddError(err2)
|
||||
err2 = nil
|
||||
}
|
||||
}(l, logName)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if errs.Count() > 0 {
|
||||
err = errs
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Shutdown cleanly shuts down a MultiLogger (and all its MultiLogger.Loggers).
|
||||
func (m *MultiLogger) Shutdown() (err error) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var errs *multierr.MultiError = multierr.NewMultiError(nil)
|
||||
|
||||
for logName, l := range m.Loggers {
|
||||
wg.Add(1)
|
||||
go func(logger Logger, lName string) {
|
||||
var err2 error
|
||||
defer wg.Done()
|
||||
if err2 = logger.Shutdown(); err2 != nil {
|
||||
errs.AddError(errors.New(fmt.Sprintf("error on Shutdown for logger %v; follows (may be out of order):", lName)))
|
||||
errs.AddError(err2)
|
||||
err2 = nil
|
||||
}
|
||||
}(l, logName)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if errs.Count() > 0 {
|
||||
err = errs
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
GetPrefix returns the prefix used by this MultiLogger (and all its MultiLogger.Loggers).
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (m *MultiLogger) GetPrefix() (prefix string, err error) {
|
||||
|
||||
prefix = m.Prefix
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
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) (err error) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var errs *multierr.MultiError = multierr.NewMultiError(nil)
|
||||
|
||||
m.EnableDebug = d
|
||||
|
||||
for logName, l := range m.Loggers {
|
||||
wg.Add(1)
|
||||
go func(logger Logger, lName string) {
|
||||
var err2 error
|
||||
defer wg.Done()
|
||||
if err2 = l.DoDebug(d); err2 != nil {
|
||||
errs.AddError(errors.New(fmt.Sprintf("error on DoDebug for logger %v; follows (may be out of order):", lName)))
|
||||
errs.AddError(err2)
|
||||
err2 = nil
|
||||
}
|
||||
}(l, logName)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if errs.Count() > 0 {
|
||||
err = errs
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetDebug returns the debug status of this MultiLogger.
|
||||
func (m *MultiLogger) GetDebug() (d bool) {
|
||||
|
||||
d = m.EnableDebug
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
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) (err error) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var errs *multierr.MultiError = multierr.NewMultiError(nil)
|
||||
|
||||
m.Prefix = prefix
|
||||
|
||||
for logName, l := range m.Loggers {
|
||||
wg.Add(1)
|
||||
go func(logger Logger, lName string) {
|
||||
var err2 error
|
||||
defer wg.Done()
|
||||
if err2 = l.SetPrefix(prefix); err != nil {
|
||||
errs.AddError(errors.New(fmt.Sprintf("error on SetPrefix for logger %v; follows (may be out of order):", lName)))
|
||||
errs.AddError(err2)
|
||||
err2 = nil
|
||||
}
|
||||
}(l, logName)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if errs.Count() > 0 {
|
||||
err = errs
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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 logName, l := range m.Loggers {
|
||||
wg.Add(1)
|
||||
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||
defer wg.Done()
|
||||
if err = logObj.Alert(msg, rplc...); err != nil {
|
||||
e.AddError(errors.New(fmt.Sprintf("error on Alert for logger %v; follows (may be out of order):", lName)))
|
||||
e.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
}(l, s, logName, 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 logName, l := range m.Loggers {
|
||||
wg.Add(1)
|
||||
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||
defer wg.Done()
|
||||
if err = logObj.Crit(msg, rplc...); err != nil {
|
||||
e.AddError(errors.New(fmt.Sprintf("error on Crit for logger %v; follows (may be out of order):", lName)))
|
||||
e.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
}(l, s, logName, 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 logName, l := range m.Loggers {
|
||||
wg.Add(1)
|
||||
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||
defer wg.Done()
|
||||
if err = logObj.Debug(msg, rplc...); err != nil {
|
||||
e.AddError(errors.New(fmt.Sprintf("error on Debug for logger %v; follows (may be out of order):", lName)))
|
||||
e.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
}(l, s, logName, 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 logName, l := range m.Loggers {
|
||||
wg.Add(1)
|
||||
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||
defer wg.Done()
|
||||
if err = logObj.Emerg(msg, rplc...); err != nil {
|
||||
e.AddError(errors.New(fmt.Sprintf("error on Emerg for logger %v; follows (may be out of order):", lName)))
|
||||
e.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
}(l, s, logName, 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 logName, l := range m.Loggers {
|
||||
wg.Add(1)
|
||||
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||
defer wg.Done()
|
||||
if err = logObj.Err(msg, rplc...); err != nil {
|
||||
e.AddError(errors.New(fmt.Sprintf("error on Err for logger %v; follows (may be out of order):", lName)))
|
||||
e.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
}(l, s, logName, 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 logName, l := range m.Loggers {
|
||||
wg.Add(1)
|
||||
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||
defer wg.Done()
|
||||
if err = logObj.Info(msg, rplc...); err != nil {
|
||||
e.AddError(errors.New(fmt.Sprintf("error on Info for logger %v; follows (may be out of order):", lName)))
|
||||
e.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
}(l, s, logName, 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 logName, l := range m.Loggers {
|
||||
wg.Add(1)
|
||||
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||
defer wg.Done()
|
||||
if err = logObj.Notice(msg, rplc...); err != nil {
|
||||
e.AddError(errors.New(fmt.Sprintf("error on Notice for logger %v; follows (may be out of order):", lName)))
|
||||
e.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
}(l, s, logName, 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 logName, l := range m.Loggers {
|
||||
wg.Add(1)
|
||||
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||
defer wg.Done()
|
||||
if err = logObj.Warning(msg, rplc...); err != nil {
|
||||
e.AddError(errors.New(fmt.Sprintf("error on Warning for logger %v; follows (may be out of order):", lName)))
|
||||
e.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
}(l, s, logName, v...)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if !e.IsEmpty() {
|
||||
err = e
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
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: logPrefix,
|
||||
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.
|
||||
|
||||
enableStdOut indicates that messages should be logged to STDOUT;
|
||||
it is *strongly encouraged* to set at least one of enableStdOut or enableStdErr to true.
|
||||
|
||||
enableStdErr indicates that messages should be logged to STDERR;
|
||||
it is *strongly encouraged* to set at least one of enableStdErr or enableStdOut to true.
|
||||
|
||||
See GetLogger's logConfigFlags argument and StdLogger.LogFlags for details on logFlags.
|
||||
*/
|
||||
func (m *MultiLogger) AddStdLogger(identifier string, enableStdOut, enableStdErr bool, logFlags int) (err error) {
|
||||
|
||||
var exists bool
|
||||
var prefix string
|
||||
|
||||
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,
|
||||
LogFlags: logFlags,
|
||||
EnableStdOut: enableStdOut,
|
||||
EnableStdErr: enableStdErr,
|
||||
}
|
||||
if err = m.Loggers[identifier].Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if prefix, err = m.Loggers[identifier].GetPrefix(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.Loggers[identifier].Debug("logger initialized of type %T with prefix %v", m.Loggers[identifier], prefix)
|
||||
|
||||
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 string, logFlags int, logfilePath string) (err error) {
|
||||
|
||||
var exists bool
|
||||
var success bool
|
||||
var dirPath string
|
||||
var prefix string
|
||||
|
||||
if identifier == "" {
|
||||
identifier = uuid.New().String()
|
||||
}
|
||||
|
||||
if _, exists = m.Loggers[identifier]; exists {
|
||||
err = ErrExistingLogger
|
||||
return
|
||||
}
|
||||
|
||||
if exists, err = paths.RealPathExists(&logfilePath); err != nil {
|
||||
return
|
||||
} else if !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,
|
||||
LogFlags: logFlags,
|
||||
},
|
||||
Path: logfilePath,
|
||||
}
|
||||
if err = m.Loggers[identifier].Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if prefix, err = m.Loggers[identifier].GetPrefix(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.Loggers[identifier].Debug("logger initialized of type %T with prefix %v", m.Loggers[identifier], prefix)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
AddNullLogger adds a NullLogger to a MultiLogger.
|
||||
|
||||
identifier is a string to use to identify the added NullLogger in MultiLogger.Loggers.
|
||||
If empty, one will be automatically generated.
|
||||
*/
|
||||
func (m *MultiLogger) AddNullLogger(identifier string) (err error) {
|
||||
|
||||
var exists bool
|
||||
var prefix string
|
||||
|
||||
if identifier == "" {
|
||||
identifier = uuid.New().String()
|
||||
}
|
||||
|
||||
if _, exists = m.Loggers[identifier]; exists {
|
||||
err = ErrExistingLogger
|
||||
return
|
||||
}
|
||||
|
||||
m.Loggers[identifier] = &NullLogger{}
|
||||
if err = m.Loggers[identifier].Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if prefix, err = m.Loggers[identifier].GetPrefix(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.Loggers[identifier].Debug("logger initialized of type %T with prefix %v", m.Loggers[identifier], prefix)
|
||||
|
||||
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
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
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.
|
||||
|
||||
See the documentation for GetLogger for details on other arguments.
|
||||
*/
|
||||
func (m *MultiLogger) AddDefaultLogger(identifier string, logFlags int, 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, logFlags, 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
|
||||
var prefix string
|
||||
|
||||
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,
|
||||
}
|
||||
if err = m.Loggers[identifier].Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if prefix, err = m.Loggers[identifier].GetPrefix(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.Loggers[identifier].Debug("logger initialized of type %T with prefix %v", m.Loggers[identifier], prefix)
|
||||
|
||||
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
|
||||
var prefix string
|
||||
|
||||
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,
|
||||
}
|
||||
if err = m.Loggers[identifier].Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if prefix, err = m.Loggers[identifier].GetPrefix(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.Loggers[identifier].Debug("logger initialized of type %T with prefix %v", m.Loggers[identifier], prefix)
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
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).
|
||||
|
||||
See the documentation for GetLogger for details on other arguments.
|
||||
|
||||
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, logFlags int, 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, logFlags, logPaths...)
|
||||
} else {
|
||||
l, err = GetLogger(m.EnableDebug, m.Prefix, eventIDs, logFlags)
|
||||
}
|
||||
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 string, eventIDs *WinEventID) (err error) {
|
||||
|
||||
var exists bool
|
||||
var prefix string
|
||||
|
||||
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: m.Prefix,
|
||||
EnableDebug: m.EnableDebug,
|
||||
EIDs: eventIDs,
|
||||
}
|
||||
if err = m.Loggers[identifier].Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if prefix, err = m.Loggers[identifier].GetPrefix(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.Loggers[identifier].Info("logger initialized of type %T with prefix %v", m.Loggers[identifier], prefix)
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package logging
|
||||
|
||||
// Setup does nothing at all; it's here for interface compat. 🙃
|
||||
func (l *NullLogger) Setup() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// DoDebug does nothing at all; it's here for interface compat. 🙃
|
||||
func (l *NullLogger) DoDebug(d bool) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetDebug returns the debug status of this NullLogger. It will always return true. 🙃
|
||||
func (n *NullLogger) GetDebug() (d bool) {
|
||||
|
||||
d = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetPrefix does nothing at all; it's here for interface compat. 🙃
|
||||
func (l *NullLogger) SetPrefix(p string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetPrefix does nothing at all; it's here for interface compat. 🙃
|
||||
func (l *NullLogger) GetPrefix() (p string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Shutdown does nothing at all; it's here for interface compat. 🙃
|
||||
func (l *NullLogger) Shutdown() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Alert does nothing at all; it's here for interface compat. 🙃
|
||||
func (l *NullLogger) Alert(s string, v ...interface{}) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Crit does nothing at all; it's here for interface compat. 🙃
|
||||
func (l *NullLogger) Crit(s string, v ...interface{}) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Debug does nothing at all; it's here for interface compat. 🙃
|
||||
func (l *NullLogger) Debug(s string, v ...interface{}) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Emerg does nothing at all; it's here for interface compat. 🙃
|
||||
func (l *NullLogger) Emerg(s string, v ...interface{}) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Err does nothing at all; it's here for interface compat. 🙃
|
||||
func (l *NullLogger) Err(s string, v ...interface{}) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Info does nothing at all; it's here for interface compat. 🙃
|
||||
func (l *NullLogger) Info(s string, v ...interface{}) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Notice does nothing at all; it's here for interface compat. 🙃
|
||||
func (l *NullLogger) Notice(s string, v ...interface{}) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Warning does nothing at all; it's here for interface compat. 🙃
|
||||
func (l *NullLogger) Warning(s string, v ...interface{}) (err error) {
|
||||
return
|
||||
}
|
|
@ -2,35 +2,108 @@ package logging
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (l *StdLogger) Setup() {
|
||||
/*
|
||||
Setup sets up/configures a StdLogger and prepares it for use.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *StdLogger) Setup() (err error) {
|
||||
|
||||
l.Logger = log.Default()
|
||||
l.Logger.SetPrefix(l.Prefix)
|
||||
var multi io.Writer
|
||||
|
||||
// This uses a shared handle across the import. We don't want that.
|
||||
// l.Logger = log.Default()
|
||||
if l.Prefix != "" {
|
||||
l.Prefix = strings.TrimRight(l.Prefix, " ") + " "
|
||||
// l.Logger.SetPrefix(l.Prefix)
|
||||
}
|
||||
// (stdlib).log.std is returned by log.Default(), which uses os.Stderr but we have flags for that.
|
||||
// https://stackoverflow.com/a/36719588/733214
|
||||
switch {
|
||||
case l.EnableStdErr && l.EnableStdOut:
|
||||
multi = io.MultiWriter(os.Stdout, os.Stderr)
|
||||
case l.EnableStdErr:
|
||||
multi = os.Stderr
|
||||
case l.EnableStdOut:
|
||||
multi = os.Stdout
|
||||
default:
|
||||
multi = nil
|
||||
}
|
||||
if multi != nil {
|
||||
l.Logger = log.New(multi, l.Prefix, l.LogFlags)
|
||||
} else {
|
||||
// This honestly should throw an error.
|
||||
l.Logger = &log.Logger{}
|
||||
l.Logger.SetPrefix(l.Prefix)
|
||||
l.Logger.SetFlags(l.LogFlags)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *StdLogger) Shutdown() {
|
||||
/*
|
||||
Shutdown cleanly shuts down a StdLogger.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *StdLogger) Shutdown() (err error) {
|
||||
|
||||
// NOOP
|
||||
_ = ""
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *StdLogger) DoDebug(d bool) {
|
||||
/*
|
||||
GetPrefix returns the prefix used by this StdLogger.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *StdLogger) GetPrefix() (prefix string, err error) {
|
||||
|
||||
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.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *StdLogger) DoDebug(d bool) (err error) {
|
||||
|
||||
l.EnableDebug = d
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *StdLogger) SetPrefix(prefix string) {
|
||||
// GetDebug returns the debug status of this StdLogger.
|
||||
func (l *StdLogger) GetDebug() (d bool) {
|
||||
|
||||
d = l.EnableDebug
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
SetPrefix sets the prefix for this StdLogger.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *StdLogger) SetPrefix(prefix string) (err error) {
|
||||
|
||||
l.Prefix = prefix
|
||||
l.Logger.SetPrefix(prefix)
|
||||
}
|
||||
|
||||
func (l *StdLogger) GetPrefix() string {
|
||||
return l.Prefix
|
||||
if prefix != "" {
|
||||
l.Prefix = strings.TrimRight(l.Prefix, " ") + " "
|
||||
}
|
||||
l.Logger.SetPrefix(l.Prefix)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Alert writes an ALERT-level message to this StdLogger.
|
||||
func (l *StdLogger) Alert(s string, v ...interface{}) (err error) {
|
||||
|
||||
var msg string
|
||||
|
@ -46,6 +119,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
|
||||
|
@ -61,6 +135,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 {
|
||||
|
@ -80,6 +155,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
|
||||
|
@ -95,6 +171,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
|
||||
|
@ -110,6 +187,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
|
||||
|
@ -125,6 +203,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
|
||||
|
@ -140,6 +219,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
|
||||
|
@ -155,6 +235,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)
|
||||
|
|
|
@ -7,32 +7,71 @@ import (
|
|||
"github.com/coreos/go-systemd/journal"
|
||||
)
|
||||
|
||||
func (l *SystemDLogger) Setup() {
|
||||
/*
|
||||
Setup sets up/configures a SystemDLogger and prepares it for use.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *SystemDLogger) Setup() (err error) {
|
||||
|
||||
// NOOP
|
||||
_ = ""
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *SystemDLogger) Shutdown() {
|
||||
/*
|
||||
Shutdown cleanly shuts down a SystemDLogger.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *SystemDLogger) Shutdown() (err error) {
|
||||
|
||||
// NOOP
|
||||
_ = ""
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *SystemDLogger) DoDebug(d bool) {
|
||||
/*
|
||||
GetPrefix returns the prefix used by this SystemDLogger.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *SystemDLogger) GetPrefix() (prefix string, err error) {
|
||||
|
||||
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.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *SystemDLogger) DoDebug(d bool) (err error) {
|
||||
|
||||
l.EnableDebug = d
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *SystemDLogger) SetPrefix(prefix string) {
|
||||
// GetDebug returns the debug status of this SystemDLogger.
|
||||
func (l *SystemDLogger) GetDebug() (d bool) {
|
||||
|
||||
d = l.EnableDebug
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
SetPrefix sets the prefix for this SystemDLogger.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *SystemDLogger) SetPrefix(prefix string) (err error) {
|
||||
|
||||
l.Prefix = prefix
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *SystemDLogger) GetPrefix() string {
|
||||
return l.Prefix
|
||||
}
|
||||
|
||||
// Alert writes an ALERT-level message to this SystemDLogger.
|
||||
func (l *SystemDLogger) Alert(s string, v ...interface{}) (err error) {
|
||||
|
||||
var msg string
|
||||
|
@ -48,6 +87,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
|
||||
|
@ -63,6 +103,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 {
|
||||
|
@ -82,6 +123,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
|
||||
|
@ -97,6 +139,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
|
||||
|
@ -112,6 +155,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
|
||||
|
@ -127,6 +171,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
|
||||
|
@ -142,6 +187,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
|
||||
|
@ -157,7 +203,9 @@ 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.
|
||||
// https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
|
||||
// CODE_FILE=, CODE_LINE=, CODE_FUNC=
|
|
@ -4,64 +4,123 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"log/syslog"
|
||||
|
||||
"r00t2.io/goutils/multierr"
|
||||
)
|
||||
|
||||
func (l *SyslogLogger) Setup() {
|
||||
// Setup sets up/configures a SyslogLogger and prepares it for use.
|
||||
func (l *SyslogLogger) Setup() (err error) {
|
||||
|
||||
var err error
|
||||
var errs *multierr.MultiError = multierr.NewMultiError(nil)
|
||||
|
||||
if l.alert, err = syslog.New(syslog.LOG_ALERT|syslogFacility, l.Prefix); err != nil {
|
||||
log.Panicln("could not open log for Alert")
|
||||
errs.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
if l.crit, err = syslog.New(syslog.LOG_CRIT|syslogFacility, l.Prefix); err != nil {
|
||||
log.Panicln("could not open log for Crit")
|
||||
errs.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
if l.debug, err = syslog.New(syslog.LOG_DEBUG|syslogFacility, l.Prefix); err != nil {
|
||||
log.Panicln("could not open log for Debug")
|
||||
errs.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
if l.emerg, err = syslog.New(syslog.LOG_EMERG|syslogFacility, l.Prefix); err != nil {
|
||||
log.Panicln("could not open log for Emerg")
|
||||
errs.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
if l.err, err = syslog.New(syslog.LOG_ERR|syslogFacility, l.Prefix); err != nil {
|
||||
log.Panicln("could not open log for Err")
|
||||
errs.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
if l.info, err = syslog.New(syslog.LOG_INFO|syslogFacility, l.Prefix); err != nil {
|
||||
log.Panicln("could not open log for Info")
|
||||
errs.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
if l.notice, err = syslog.New(syslog.LOG_NOTICE|syslogFacility, l.Prefix); err != nil {
|
||||
log.Panicln("could not open log for Notice")
|
||||
errs.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
if l.warning, err = syslog.New(syslog.LOG_WARNING|syslogFacility, l.Prefix); err != nil {
|
||||
log.Panicln("could not open log for Warning")
|
||||
errs.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
|
||||
if errs.Count() > 0 {
|
||||
err = errs
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *SyslogLogger) Shutdown() {
|
||||
// Shutdown cleanly shuts down a SyslogLogger.
|
||||
func (l *SyslogLogger) Shutdown() (err error) {
|
||||
|
||||
var err error
|
||||
var errs *multierr.MultiError = multierr.NewMultiError(nil)
|
||||
|
||||
for _, i := range []*syslog.Writer{l.alert, l.crit, l.debug, l.emerg, l.err, l.info, l.notice, l.warning} {
|
||||
if err = i.Close(); err != nil {
|
||||
log.Panicf("could not close log %#v\n", i)
|
||||
errs.AddError(err)
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
if errs.Count() > 0 {
|
||||
err = errs
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *SyslogLogger) DoDebug(d bool) {
|
||||
/*
|
||||
GetPrefix returns the prefix used by this SyslogLogger.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *SyslogLogger) GetPrefix() (prefix string, err error) {
|
||||
|
||||
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.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *SyslogLogger) DoDebug(d bool) (err error) {
|
||||
|
||||
l.EnableDebug = d
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *SyslogLogger) SetPrefix(prefix string) {
|
||||
// GetDebug returns the debug status of this SyslogLogger.
|
||||
func (l *SyslogLogger) GetDebug() (d bool) {
|
||||
|
||||
d = l.EnableDebug
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetPrefix sets the prefix for this SyslogLogger.
|
||||
func (l *SyslogLogger) SetPrefix(prefix string) (err error) {
|
||||
|
||||
l.Prefix = prefix
|
||||
l.Setup()
|
||||
}
|
||||
|
||||
func (l *SyslogLogger) GetPrefix() string {
|
||||
return l.Prefix
|
||||
|
||||
// We need to close the current loggers first.
|
||||
if err = l.Shutdown(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = l.Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Alert writes an ALERT-level message to this SyslogLogger.
|
||||
func (l *SyslogLogger) Alert(s string, v ...interface{}) (err error) {
|
||||
|
||||
var msg string
|
||||
|
@ -79,6 +138,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
|
||||
|
@ -96,6 +156,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 {
|
||||
|
@ -117,6 +178,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
|
||||
|
@ -134,6 +196,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
|
||||
|
@ -151,6 +214,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
|
||||
|
@ -168,6 +232,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
|
||||
|
@ -185,6 +250,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
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
`os`
|
||||
`testing`
|
||||
)
|
||||
|
||||
/*
|
||||
TestStdLogger tests functionality for StdLogger.
|
||||
*/
|
||||
func TestStdLogger(t *testing.T) {
|
||||
|
||||
var l *StdLogger
|
||||
var ltype string = "StdLogger"
|
||||
var prefix string
|
||||
var err error
|
||||
|
||||
l = &StdLogger{
|
||||
EnableDebug: true,
|
||||
Prefix: TestLogPrefix,
|
||||
LogFlags: logFlags,
|
||||
EnableStdOut: false,
|
||||
EnableStdErr: true,
|
||||
}
|
||||
|
||||
if err = l.Setup(); err != nil {
|
||||
t.Fatalf("error when running Setup: %v", err.Error())
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l)
|
||||
|
||||
if err = l.Alert(testAlert, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Crit(testCrit, ltype); err != nil {
|
||||
t.Fatalf("error for Crit: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Debug(testDebug, ltype); err != nil {
|
||||
t.Fatalf("error for Debug: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Emerg(testEmerg, ltype); err != nil {
|
||||
t.Fatalf("error for Emerg: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Err(testErr, ltype); err != nil {
|
||||
t.Fatalf("error for Err: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Info(testInfo, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Notice(testNotice, ltype); err != nil {
|
||||
t.Fatalf("error for Notice: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Warning(testWarning, ltype); err != nil {
|
||||
t.Fatalf("error for Warning: %v", err.Error())
|
||||
}
|
||||
|
||||
if prefix, err = l.GetPrefix(); err != nil {
|
||||
t.Fatalf("error when fetching prefix: %v", err.Error())
|
||||
}
|
||||
|
||||
if prefix != (TestLogPrefix + " ") { // StdLogger adds a space at the end.
|
||||
t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix)
|
||||
}
|
||||
if err = l.SetPrefix(TestLogAltPrefix); err != nil {
|
||||
t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error())
|
||||
} else {
|
||||
_ = l.SetPrefix(TestLogPrefix)
|
||||
}
|
||||
|
||||
if err = l.DoDebug(false); err != nil {
|
||||
t.Fatalf("error when changing debug to false: %v", err.Error())
|
||||
} else if l.EnableDebug {
|
||||
t.Fatalf("did not properly set Debug filter state")
|
||||
} else {
|
||||
_ = l.DoDebug(true)
|
||||
}
|
||||
|
||||
if err = l.Shutdown(); err != nil {
|
||||
t.Fatalf("Error when running Shutdown: %v", err.Error())
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed all logging targets.", ltype)
|
||||
}
|
||||
|
||||
/*
|
||||
TestFileLogger tests functionality for FileLogger.
|
||||
If the appropriate env var is set (see the EnvVarKeepLog constant), the temporary log file that is created will not be cleaned up.
|
||||
*/
|
||||
func TestFileLogger(t *testing.T) {
|
||||
|
||||
var l *FileLogger
|
||||
var ltype string = "FileLogger"
|
||||
var prefix string
|
||||
var tempfile *os.File
|
||||
var tempfilePath string
|
||||
var keepLog bool
|
||||
var err error
|
||||
|
||||
if tempfile, err = os.CreateTemp("", ".LOGGINGTEST_*"); err != nil {
|
||||
t.Fatalf("error when creating temporary log file '%v': %v", tempfile.Name(), err.Error())
|
||||
}
|
||||
tempfilePath = tempfile.Name()
|
||||
// We can close the handler immediately; we don't need it since the FileLogger opens its own.
|
||||
if err = tempfile.Close(); err != nil {
|
||||
t.Fatalf("error when closing handler for temporary log file '%v': %v", tempfile.Name(), err.Error())
|
||||
}
|
||||
|
||||
l = &FileLogger{
|
||||
StdLogger: StdLogger{
|
||||
EnableDebug: true,
|
||||
Prefix: TestLogPrefix,
|
||||
LogFlags: logFlags,
|
||||
},
|
||||
Path: tempfilePath,
|
||||
}
|
||||
|
||||
if err = l.Setup(); err != nil {
|
||||
t.Fatalf("error when running Setup: %v", err.Error())
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l)
|
||||
|
||||
if err = l.Alert(testAlert, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Crit(testCrit, ltype); err != nil {
|
||||
t.Fatalf("error for Crit: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Debug(testDebug, ltype); err != nil {
|
||||
t.Fatalf("error for Debug: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Emerg(testEmerg, ltype); err != nil {
|
||||
t.Fatalf("error for Emerg: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Err(testErr, ltype); err != nil {
|
||||
t.Fatalf("error for Err: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Info(testInfo, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Notice(testNotice, ltype); err != nil {
|
||||
t.Fatalf("error for Notice: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Warning(testWarning, ltype); err != nil {
|
||||
t.Fatalf("error for Warning: %v", err.Error())
|
||||
}
|
||||
|
||||
if prefix, err = l.GetPrefix(); err != nil {
|
||||
t.Fatalf("error when fetching prefix: %v", err.Error())
|
||||
}
|
||||
|
||||
if prefix != (TestLogPrefix + " ") { // StdLogger (and thus FileLogger) adds a space at the end.
|
||||
t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix)
|
||||
}
|
||||
if err = l.SetPrefix(TestLogAltPrefix); err != nil {
|
||||
t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error())
|
||||
} else {
|
||||
_ = l.SetPrefix(TestLogPrefix)
|
||||
}
|
||||
|
||||
if err = l.DoDebug(false); err != nil {
|
||||
t.Fatalf("error when changing debug to false: %v", err.Error())
|
||||
} else if l.EnableDebug {
|
||||
t.Fatalf("did not properly set Debug filter state")
|
||||
} else {
|
||||
_ = l.DoDebug(true)
|
||||
}
|
||||
|
||||
if err = l.Shutdown(); err != nil {
|
||||
t.Fatalf("Error when running Shutdown: %v", err.Error())
|
||||
}
|
||||
|
||||
_, keepLog = os.LookupEnv(EnvVarKeepLog)
|
||||
|
||||
if !keepLog {
|
||||
if err = os.Remove(tempfilePath); err != nil {
|
||||
t.Fatalf("error when removing temporary log file '%v': %v", tempfilePath, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed all logging targets.", ltype)
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
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.
|
||||
|
||||
logConfigFlags is the corresponding flag(s) OR'd for StdLogger.LogFlags / FileLogger.StdLogger.LogFlags if either is selected. See StdLogger.LogFlags and
|
||||
https://pkg.go.dev/log#pkg-constants for details.
|
||||
|
||||
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, logConfigFlags int, logPaths ...string) (logger Logger, err error) {
|
||||
|
||||
var logPath string
|
||||
var logFlags bitmask.MaskBit
|
||||
var exists bool
|
||||
var success bool
|
||||
var ckLogPaths []string
|
||||
var prefix 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,
|
||||
LogFlags: logConfigFlags,
|
||||
},
|
||||
Path: logPath,
|
||||
}
|
||||
} else {
|
||||
logger = &StdLogger{
|
||||
Prefix: source,
|
||||
EnableDebug: enableDebug,
|
||||
LogFlags: logConfigFlags,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err = logger.Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
if source != "\x00" {
|
||||
if err = logger.SetPrefix(source); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if prefix, err = logger.GetPrefix(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("logger initialized of type %T with source %v", logger, prefix)
|
||||
|
||||
return
|
||||
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
`fmt`
|
||||
`os`
|
||||
`testing`
|
||||
)
|
||||
|
||||
/*
|
||||
TestWinLogger tests functionality for WinLogger.
|
||||
You will probably need to run it with an Administrator shell.
|
||||
*/
|
||||
func TestWinLogger(t *testing.T) {
|
||||
|
||||
var l *WinLogger
|
||||
var ltype string = "WinLogger"
|
||||
var prefix string
|
||||
var exists bool
|
||||
var err error
|
||||
|
||||
l = &WinLogger{
|
||||
EnableDebug: true,
|
||||
Prefix: TestLogPrefix,
|
||||
RemoveOnClose: true,
|
||||
EIDs: DefaultEventID,
|
||||
}
|
||||
|
||||
if exists, err = l.Exists(); err != nil {
|
||||
t.Fatalf("error when checking for existence of registered Event Log source '%v': %v", TestLogPrefix, err.Error())
|
||||
} else {
|
||||
t.Logf("Prefix (source) '%v' exists before setup: %v", TestLogPrefix, exists)
|
||||
}
|
||||
|
||||
if err = l.Setup(); err != nil {
|
||||
t.Fatalf("error when running Setup: %v", err.Error())
|
||||
}
|
||||
|
||||
if exists, err = l.Exists(); err != nil {
|
||||
t.Fatalf("error when checking for existence of registered Event Log source '%v': %v", TestLogPrefix, err.Error())
|
||||
} else {
|
||||
t.Logf("Prefix (source) '%v' exists after setup: %v", TestLogPrefix, exists)
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l)
|
||||
|
||||
if err = l.Alert(testAlert, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Crit(testCrit, ltype); err != nil {
|
||||
t.Fatalf("error for Crit: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Debug(testDebug, ltype); err != nil {
|
||||
t.Fatalf("error for Debug: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Emerg(testEmerg, ltype); err != nil {
|
||||
t.Fatalf("error for Emerg: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Err(testErr, ltype); err != nil {
|
||||
t.Fatalf("error for Err: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Info(testInfo, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Notice(testNotice, ltype); err != nil {
|
||||
t.Fatalf("error for Notice: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Warning(testWarning, ltype); err != nil {
|
||||
t.Fatalf("error for Warning: %v", err.Error())
|
||||
}
|
||||
|
||||
if prefix, err = l.GetPrefix(); err != nil {
|
||||
t.Fatalf("error when fetching prefix: %v", err.Error())
|
||||
}
|
||||
|
||||
if prefix != TestLogPrefix {
|
||||
t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix)
|
||||
}
|
||||
if err = l.SetPrefix(TestLogAltPrefix); err != nil {
|
||||
t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error())
|
||||
} else {
|
||||
_ = l.SetPrefix(TestLogPrefix)
|
||||
}
|
||||
|
||||
if err = l.DoDebug(false); err != nil {
|
||||
t.Fatalf("error when changing debug to false: %v", err.Error())
|
||||
} else if l.EnableDebug {
|
||||
t.Fatalf("did not properly set Debug filter state")
|
||||
} else {
|
||||
_ = l.DoDebug(true)
|
||||
}
|
||||
|
||||
if err = l.Shutdown(); err != nil {
|
||||
t.Fatalf("Error when running Shutdown: %v", err.Error())
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed all logging targets.", ltype)
|
||||
}
|
||||
|
||||
// TestDefaultLogger tests GetLogger.
|
||||
func TestDefaultLogger(t *testing.T) {
|
||||
|
||||
var l Logger
|
||||
var tempfile *os.File
|
||||
var tempfilePath string
|
||||
var keepLog bool
|
||||
var ltype string
|
||||
var prefix string
|
||||
var testPrefix string
|
||||
var err error
|
||||
|
||||
if tempfile, err = os.CreateTemp("", ".LOGGINGTEST_*"); err != nil {
|
||||
t.Fatalf("error when creating temporary log file '%v': %v", tempfile.Name(), err.Error())
|
||||
}
|
||||
tempfilePath = tempfile.Name()
|
||||
// We can close the handler immediately; we don't need it since the FileLogger opens its own.
|
||||
if err = tempfile.Close(); err != nil {
|
||||
t.Fatalf("error when closing handler for temporary log file '%v': %v", tempfile.Name(), err.Error())
|
||||
}
|
||||
|
||||
if l, err = GetLogger(true, TestLogPrefix, DefaultEventID, logFlags, tempfilePath); err != nil {
|
||||
t.Fatalf("error when spawning default Windows logger via GetLogger: %v", err.Error())
|
||||
}
|
||||
|
||||
ltype = fmt.Sprintf("%T", l)
|
||||
|
||||
t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l)
|
||||
|
||||
if err = l.Alert(testAlert, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Crit(testCrit, ltype); err != nil {
|
||||
t.Fatalf("error for Crit: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Debug(testDebug, ltype); err != nil {
|
||||
t.Fatalf("error for Debug: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Emerg(testEmerg, ltype); err != nil {
|
||||
t.Fatalf("error for Emerg: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Err(testErr, ltype); err != nil {
|
||||
t.Fatalf("error for Err: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Info(testInfo, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Notice(testNotice, ltype); err != nil {
|
||||
t.Fatalf("error for Notice: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Warning(testWarning, ltype); err != nil {
|
||||
t.Fatalf("error for Warning: %v", err.Error())
|
||||
}
|
||||
|
||||
if prefix, err = l.GetPrefix(); err != nil {
|
||||
t.Fatalf("error when fetching prefix: %v", err.Error())
|
||||
}
|
||||
|
||||
if ltype == "StdLogger" || ltype == "FileLogger" { // StdLogger (and thus FileLogger) adds a space at the end.
|
||||
testPrefix = TestLogPrefix + " "
|
||||
} else {
|
||||
testPrefix = TestLogPrefix
|
||||
}
|
||||
|
||||
if prefix != testPrefix {
|
||||
t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix)
|
||||
}
|
||||
if err = l.SetPrefix(TestLogAltPrefix); err != nil {
|
||||
t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error())
|
||||
} else {
|
||||
_ = l.SetPrefix(TestLogPrefix)
|
||||
}
|
||||
|
||||
if err = l.DoDebug(false); err != nil {
|
||||
t.Fatalf("error when changing debug to false: %v", err.Error())
|
||||
} else {
|
||||
_ = l.DoDebug(true)
|
||||
}
|
||||
|
||||
if err = l.Shutdown(); err != nil {
|
||||
t.Fatalf("Error when running Shutdown: %v", err.Error())
|
||||
}
|
||||
|
||||
_, keepLog = os.LookupEnv(EnvVarKeepLog)
|
||||
|
||||
if !keepLog {
|
||||
if err = os.Remove(tempfilePath); err != nil {
|
||||
t.Fatalf("error when removing temporary log file '%v': %v", tempfilePath, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed all logging targets.", ltype)
|
||||
}
|
|
@ -0,0 +1,344 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"golang.org/x/sys/windows/svc/eventlog"
|
||||
"r00t2.io/sysutils/paths"
|
||||
)
|
||||
|
||||
/*
|
||||
Setup sets up/configures a WinLogger and prepares it for use.
|
||||
This will fail with an Access Denied (the first time, at least) unless running with elevated permissions unless WinLogger.Prefix is
|
||||
a registered Event Log source.
|
||||
|
||||
If a failure occurs while trying to open the log with the given WinLogger.Prefix ("source"), a new Event Log source will be registered.
|
||||
If WinLogger.Executable is not empty at the time of calling WinLogger.Setup (or WinLogger.ForceService is true),
|
||||
eventlog.Install will be used (with the WinLogger.ExpandKey field).
|
||||
Otherwise eventlog.InstallAsEventCreate will be used.
|
||||
*/
|
||||
func (l *WinLogger) Setup() (err error) {
|
||||
|
||||
/*
|
||||
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 <= EIDMax) && (EIDMin <= eid)) {
|
||||
err = ErrBadEid
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = l.Install(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if l.elog, err = eventlog.Open(l.Prefix); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Install installs/registers the WinLogger Event Log interface. You most likely do not need to run this directly.
|
||||
func (l *WinLogger) Install() (err error) {
|
||||
|
||||
var exists bool
|
||||
var doNotCreate bool
|
||||
var useEventCreate bool = true
|
||||
|
||||
if doNotCreate, err = l.Exists(); err != nil {
|
||||
return
|
||||
} else if !doNotCreate {
|
||||
if l.Executable != "" {
|
||||
if l.Executable, err = exec.LookPath(l.Executable); err != nil {
|
||||
return
|
||||
}
|
||||
useEventCreate = false
|
||||
} else if l.ForceService {
|
||||
if l.Executable, err = exec.LookPath(os.Args[0]); err != nil {
|
||||
return
|
||||
}
|
||||
useEventCreate = false
|
||||
}
|
||||
if !useEventCreate {
|
||||
if exists, err = paths.RealPathExists(&l.Executable); err != nil {
|
||||
return
|
||||
} else if !exists {
|
||||
err = ErrBadBinPath
|
||||
return
|
||||
}
|
||||
if err = eventlog.Install(l.Prefix, l.Executable, l.ExpandKey, eventlog.Error|eventlog.Warning|eventlog.Info); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err = eventlog.InstallAsEventCreate(l.Prefix, eventlog.Error|eventlog.Warning|eventlog.Info); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// Remove uninstalls a registered WinLogger source.
|
||||
func (l *WinLogger) Remove() (err error) {
|
||||
|
||||
if err = eventlog.Remove(l.Prefix); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Shutdown cleanly shuts down a WinLogger but keep the source registered. Use WinLogger.Remove
|
||||
(or set WinLogger.RemoveOnClose to true before calling WinLogger.Shutdown) to remove the registered source.
|
||||
*/
|
||||
func (l *WinLogger) Shutdown() (err error) {
|
||||
|
||||
if err = l.elog.Close(); err != nil {
|
||||
// TODO: check for no access or file not exists syscall errors?
|
||||
return
|
||||
}
|
||||
|
||||
if l.RemoveOnClose {
|
||||
if err = l.Remove(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
GetPrefix returns the prefix used by this WinLogger.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *WinLogger) GetPrefix() (prefix string, err error) {
|
||||
|
||||
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.
|
||||
err will always be nil; it's there for interface-compat.
|
||||
*/
|
||||
func (l *WinLogger) DoDebug(d bool) (err error) {
|
||||
|
||||
l.EnableDebug = d
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetDebug returns the debug status of this WinLogger.
|
||||
func (l *WinLogger) GetDebug() (d bool) {
|
||||
|
||||
d = l.EnableDebug
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetPrefix sets the prefix for this WinLogger.
|
||||
func (l *WinLogger) SetPrefix(prefix string) (err error) {
|
||||
|
||||
// To properly change the prefix, we need to tear down the old event log and create a new one.
|
||||
if err = l.Shutdown(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
l.Prefix = prefix
|
||||
|
||||
if err = l.Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Exists indicates if the WinLogger.Prefix is a registered source or not.
|
||||
func (l *WinLogger) Exists() (e bool, err error) {
|
||||
|
||||
var regKey registry.Key
|
||||
var subKey registry.Key
|
||||
|
||||
if regKey, err = registry.OpenKey(registry.LOCAL_MACHINE, eventLogRegistryKey, registry.READ); err != nil {
|
||||
return
|
||||
}
|
||||
defer regKey.Close()
|
||||
|
||||
if subKey, err = registry.OpenKey(regKey, l.Prefix, registry.READ); err != nil {
|
||||
if errors.Is(err, syscall.ERROR_FILE_NOT_FOUND) {
|
||||
e = false
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
defer subKey.Close()
|
||||
|
||||
e = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
`os`
|
||||
`testing`
|
||||
)
|
||||
|
||||
// TestMultiLogger tests GetMultiLogger and MultiLogger methods.
|
||||
func TestMultiLogger(t *testing.T) {
|
||||
|
||||
var l *MultiLogger
|
||||
var tempfile *os.File
|
||||
var tempfilePath string
|
||||
var keepLog bool
|
||||
var ltype string = "MultiLogger"
|
||||
var prefix string
|
||||
var testPrefix string
|
||||
var err error
|
||||
|
||||
if tempfile, err = os.CreateTemp("", ".LOGGINGTEST_*"); err != nil {
|
||||
t.Fatalf("error when creating temporary log file '%v': %v", tempfile.Name(), err.Error())
|
||||
}
|
||||
tempfilePath = tempfile.Name()
|
||||
// We can close the handler immediately; we don't need it since the FileLogger opens its own.
|
||||
if err = tempfile.Close(); err != nil {
|
||||
t.Fatalf("error when closing handler for temporary log file '%v': %v", tempfile.Name(), err.Error())
|
||||
}
|
||||
|
||||
l = GetMultiLogger(true, TestLogPrefix)
|
||||
|
||||
if err = l.AddStdLogger("StdLogger", false, true, logFlags); err != nil {
|
||||
t.Fatalf("error when adding StdLogger to MultiLogger: %v", err.Error())
|
||||
}
|
||||
if err = l.AddFileLogger("FileLogger", logFlags, tempfilePath); err != nil {
|
||||
t.Fatalf("error when adding FileLogger to MultiLogger: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.AddDefaultLogger("DefaultLogger", logFlags, tempfilePath); err != nil {
|
||||
t.Fatalf("error when adding default logger to MultiLogger: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.AddSysdLogger("SystemDLogger"); err != nil {
|
||||
t.Fatalf("error when adding SystemDLogger to MultiLogger: %v", err.Error())
|
||||
}
|
||||
if err = l.AddSyslogLogger("SyslogLogger"); err != nil {
|
||||
t.Fatalf("error when adding SyslogLogger to MultiLogger: %v", err.Error())
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l)
|
||||
|
||||
if err = l.Alert(testAlert, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Crit(testCrit, ltype); err != nil {
|
||||
t.Fatalf("error for Crit: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Debug(testDebug, ltype); err != nil {
|
||||
t.Fatalf("error for Debug: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Emerg(testEmerg, ltype); err != nil {
|
||||
t.Fatalf("error for Emerg: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Err(testErr, ltype); err != nil {
|
||||
t.Fatalf("error for Err: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Info(testInfo, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Notice(testNotice, ltype); err != nil {
|
||||
t.Fatalf("error for Notice: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Warning(testWarning, ltype); err != nil {
|
||||
t.Fatalf("error for Warning: %v", err.Error())
|
||||
}
|
||||
|
||||
if prefix, err = l.GetPrefix(); err != nil {
|
||||
t.Fatalf("error when fetching prefix: %v", err.Error())
|
||||
}
|
||||
|
||||
if ltype == "StdLogger" || ltype == "FileLogger" { // StdLogger (and thus FileLogger) adds a space at the end.
|
||||
testPrefix = TestLogPrefix + " "
|
||||
} else {
|
||||
testPrefix = TestLogPrefix
|
||||
}
|
||||
|
||||
if prefix != testPrefix {
|
||||
t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix)
|
||||
}
|
||||
if err = l.SetPrefix(TestLogAltPrefix); err != nil {
|
||||
t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error())
|
||||
} else {
|
||||
_ = l.SetPrefix(TestLogPrefix)
|
||||
}
|
||||
|
||||
if err = l.DoDebug(false); err != nil {
|
||||
t.Fatalf("error when changing debug to false: %v", err.Error())
|
||||
} else {
|
||||
_ = l.DoDebug(true)
|
||||
}
|
||||
|
||||
if err = l.Shutdown(); err != nil {
|
||||
t.Fatalf("Error when running Shutdown: %v", err.Error())
|
||||
}
|
||||
|
||||
_, keepLog = os.LookupEnv(EnvVarKeepLog)
|
||||
|
||||
if !keepLog {
|
||||
if err = os.Remove(tempfilePath); err != nil {
|
||||
t.Fatalf("error when removing temporary log file '%v': %v", tempfilePath, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed all logging targets.", ltype)
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
`os`
|
||||
`testing`
|
||||
)
|
||||
|
||||
// TestMultiLogger tests GetMultiLogger and MultiLogger methods.
|
||||
func TestMultiLogger(t *testing.T) {
|
||||
|
||||
var l *MultiLogger
|
||||
var tempfile *os.File
|
||||
var tempfilePath string
|
||||
var keepLog bool
|
||||
var ltype string = "MultiLogger"
|
||||
var prefix string
|
||||
var testPrefix string
|
||||
var err error
|
||||
|
||||
if tempfile, err = os.CreateTemp("", ".LOGGINGTEST_*"); err != nil {
|
||||
t.Fatalf("error when creating temporary log file '%v': %v", tempfile.Name(), err.Error())
|
||||
}
|
||||
tempfilePath = tempfile.Name()
|
||||
// We can close the handler immediately; we don't need it since the FileLogger opens its own.
|
||||
if err = tempfile.Close(); err != nil {
|
||||
t.Fatalf("error when closing handler for temporary log file '%v': %v", tempfile.Name(), err.Error())
|
||||
}
|
||||
|
||||
l = GetMultiLogger(true, TestLogPrefix)
|
||||
|
||||
if err = l.AddStdLogger("StdLogger", false, true, logFlags); err != nil {
|
||||
t.Fatalf("error when adding StdLogger to MultiLogger: %v", err.Error())
|
||||
}
|
||||
if err = l.AddFileLogger("FileLogger", logFlags, tempfilePath); err != nil {
|
||||
t.Fatalf("error when adding FileLogger to MultiLogger: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.AddDefaultLogger("DefaultLogger", DefaultEventID, logFlags, tempfilePath); err != nil {
|
||||
t.Fatalf("error when adding default logger to MultiLogger: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.AddWinLogger("WinLogger", DefaultEventID); err != nil {
|
||||
t.Fatalf("error when adding WinLogger to MultiLogger: %v", err.Error())
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed Setup. Logger: %#v", ltype, l)
|
||||
|
||||
if err = l.Alert(testAlert, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Crit(testCrit, ltype); err != nil {
|
||||
t.Fatalf("error for Crit: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Debug(testDebug, ltype); err != nil {
|
||||
t.Fatalf("error for Debug: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Emerg(testEmerg, ltype); err != nil {
|
||||
t.Fatalf("error for Emerg: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Err(testErr, ltype); err != nil {
|
||||
t.Fatalf("error for Err: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Info(testInfo, ltype); err != nil {
|
||||
t.Fatalf("error for Alert: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Notice(testNotice, ltype); err != nil {
|
||||
t.Fatalf("error for Notice: %v", err.Error())
|
||||
}
|
||||
|
||||
if err = l.Warning(testWarning, ltype); err != nil {
|
||||
t.Fatalf("error for Warning: %v", err.Error())
|
||||
}
|
||||
|
||||
if prefix, err = l.GetPrefix(); err != nil {
|
||||
t.Fatalf("error when fetching prefix: %v", err.Error())
|
||||
}
|
||||
|
||||
if ltype == "StdLogger" || ltype == "FileLogger" { // StdLogger (and thus FileLogger) adds a space at the end.
|
||||
testPrefix = TestLogPrefix + " "
|
||||
} else {
|
||||
testPrefix = TestLogPrefix
|
||||
}
|
||||
|
||||
if prefix != testPrefix {
|
||||
t.Fatalf("true prefix ('%v') does not match TestLogPrefix ('%v')", prefix, TestLogPrefix)
|
||||
}
|
||||
if err = l.SetPrefix(TestLogAltPrefix); err != nil {
|
||||
t.Fatalf("error when setting prefix to %v: %v", TestLogAltPrefix, err.Error())
|
||||
} else {
|
||||
_ = l.SetPrefix(TestLogPrefix)
|
||||
}
|
||||
|
||||
if err = l.DoDebug(false); err != nil {
|
||||
t.Fatalf("error when changing debug to false: %v", err.Error())
|
||||
} else {
|
||||
_ = l.DoDebug(true)
|
||||
}
|
||||
|
||||
if err = l.Shutdown(); err != nil {
|
||||
t.Fatalf("Error when running Shutdown: %v", err.Error())
|
||||
}
|
||||
|
||||
_, keepLog = os.LookupEnv(EnvVarKeepLog)
|
||||
|
||||
if !keepLog {
|
||||
if err = os.Remove(tempfilePath); err != nil {
|
||||
t.Fatalf("error when removing temporary log file '%v': %v", tempfilePath, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("Logger %v passed all logging targets.", ltype)
|
||||
}
|
122
logging/types.go
122
logging/types.go
|
@ -2,52 +2,106 @@ package logging
|
|||
|
||||
import (
|
||||
"log"
|
||||
"log/syslog"
|
||||
"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
|
||||
Setup()
|
||||
Shutdown()
|
||||
}
|
||||
|
||||
type SystemDLogger struct {
|
||||
EnableDebug bool
|
||||
Prefix string
|
||||
}
|
||||
|
||||
type SyslogLogger struct {
|
||||
EnableDebug bool
|
||||
Prefix string
|
||||
alert,
|
||||
crit,
|
||||
debug,
|
||||
emerg,
|
||||
err,
|
||||
info,
|
||||
notice,
|
||||
warning *syslog.Writer
|
||||
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) (err error)
|
||||
GetDebug() (d bool)
|
||||
SetPrefix(p string) (err error)
|
||||
GetPrefix() (p string, err error)
|
||||
Setup() (err error)
|
||||
Shutdown() (err error)
|
||||
}
|
||||
|
||||
/*
|
||||
StdLogger uses the log package in stdlib to perform all logging. The default is to write to STDOUT.
|
||||
If you wish to modify the underling log.Logger object, you can access it directly via StdLogger.Logger.
|
||||
*/
|
||||
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
|
||||
/*
|
||||
LogFlags control some of the formatting options presented as an OR'd value.
|
||||
See https://pkg.go.dev/log#pkg-constants for flag details.
|
||||
e.g.:
|
||||
*StdLogger.LogFlags = log.Ldate | log.Lmicroseconds | log.Llongfile | log.LUTC // a very detailed log output
|
||||
*StdLogger.LogFlags = log.Ldate | log.Ltime // the flags used by log.Default() (also available as simply log.LstdFlags)
|
||||
The default is 0; no flags (no output except prefix if non-empty and message).
|
||||
You will need to run *StdLogger.Shutdown and then *StdLogger.Setup again if you wish to change this.
|
||||
*/
|
||||
LogFlags int
|
||||
/*
|
||||
EnableStdOut is true if the log will send to STDOUT.
|
||||
If false (default), no output will be written to STDOUT.
|
||||
You will need to run StdLogger.Shutdown and then StdLogger.Setup again if you wish to change this.
|
||||
|
||||
If EnableStdOut is false and EnableStdErr is false, no logging output will occur by default
|
||||
and StdLogger.Logger will be largely useless.
|
||||
It will be up to you to modify the underlying log.Logger to behave as you want.
|
||||
*/
|
||||
EnableStdOut bool
|
||||
/*
|
||||
EnableStdErr is true if the log will send to STDERR.
|
||||
If false (default), no output will be written to STDERR.
|
||||
You will need to run StdLogger.Shutdown and then StdLogger.Setup again if you wish to change this.
|
||||
|
||||
If EnableStdErr is false and EnableStdOut is false, no logging output will occur by default
|
||||
and StdLogger.Logger will be largely useless.
|
||||
It will be up to you to modify the underlying log.Logger to behave as you want.
|
||||
*/
|
||||
EnableStdErr bool
|
||||
}
|
||||
|
||||
/*
|
||||
FileLogger uses a StdLogger with a file handle writer to write to the file given at Path.
|
||||
|
||||
NOTE: If you wish to change the FileLogger.StdLogger.LogFlags, do *not* run FileLogger.StdLogger.Setup after doing so as this
|
||||
will instead create a logger detached from the file handler. Instead, be sure to call FileLogger.Setup.
|
||||
(Alternatively, run FileLogger.Shutdown and replace your logger with a new FileLogger.)
|
||||
*/
|
||||
type FileLogger struct {
|
||||
// StdLogger is used for the log formation and handling. See StdLogger for more details.
|
||||
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
|
||||
}
|
||||
|
||||
// NullLogger is used mainly for test implementations, mockup code, etc. It does absolutely nothing with all messages sent to it.
|
||||
type NullLogger struct{}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
`log/syslog`
|
||||
)
|
||||
|
||||
/*
|
||||
SystemDLogger (yes, I'm aware it's actually written as "systemd") writes to journald on systemd-enabled systems.
|
||||
*/
|
||||
type SystemDLogger struct {
|
||||
EnableDebug bool
|
||||
Prefix string
|
||||
}
|
||||
|
||||
// SyslogLogger writes to syslog on syslog-enabled systems.
|
||||
type SyslogLogger struct {
|
||||
EnableDebug bool
|
||||
Prefix string
|
||||
alert,
|
||||
crit,
|
||||
debug,
|
||||
emerg,
|
||||
err,
|
||||
info,
|
||||
notice,
|
||||
warning *syslog.Writer
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
`golang.org/x/sys/windows/svc/eventlog`
|
||||
)
|
||||
|
||||
// WinLogger is used for logging to the Windows Event Log. These entries are viewable in the Event Viewer application, under "Windows Logs > Application".
|
||||
type WinLogger 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 is used as the Event Log "Source". It's named as Prefix to retain compatability with methods in the Logger interface.
|
||||
*/
|
||||
Prefix string
|
||||
/*
|
||||
Executable is used as the path for the executable implementing this logger.
|
||||
If non-empty, it enables the "service" mode of Event Log (intended for "installed" software that's expected
|
||||
to exist as a specific path reliably).
|
||||
It can be a file within the PATHs or an absolute/relative path; an attempt to resolve the actual path will be made. If this fails or the file
|
||||
does not exist, an error will be raised.
|
||||
*/
|
||||
Executable string
|
||||
/*
|
||||
ExpandKey is only used if Executable is non-empty and valid and/or ForceService is true.
|
||||
If true, the WinLogger will be installed/registered with the REG_EXPAND_SZ mode - otherwise it will be installed as REG_SZ.
|
||||
See the definition for the two at https://docs.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types for further details.
|
||||
If you're unsure which you want, it's probably REG_SZ (WinLogger.ExpandKey == false), which is the default.
|
||||
*/
|
||||
ExpandKey bool
|
||||
/*
|
||||
ForceService, if true, will enforce WinLogger to be used as if Executable is populated and valid (it will use os.Args[0] as the Executable path).
|
||||
If Executable is empty but ForceService is true and os.Args[0] is empty or invalid (not a real path, etc.), an error will be raised.
|
||||
*/
|
||||
ForceService bool
|
||||
// RemoveOnClose should be true if the logger should be removed/unregistered from the Registry upon calling WinLogger.Shutdown.
|
||||
RemoveOnClose bool
|
||||
// elog is the actual writer to the Event Log.
|
||||
elog *eventlog.Log
|
||||
// EIDs is used to look up what event ID to use when writing to a WinLogger.elog.
|
||||
EIDs *WinEventID
|
||||
}
|
||||
|
||||
/*
|
||||
WinEventID is a collection of Event IDs to use for a WinLogger.
|
||||
Because Event Log only supports three entry types (informational, warning, or error),
|
||||
these event IDs allow you to filter the messages in a slightly more granular way. They map to their corresponding method name/Logger level.
|
||||
However, this means that a WinLogger does not support custom event IDs (and thus you cannot assign individual event IDs to specific errors).
|
||||
This is the price of convenience.
|
||||
|
||||
An additional method set may be added in the future to support this, but this is currently an unplanned feature.
|
||||
|
||||
Event IDs *must* be between the constants EIDMin and EIDMax (inclusive) unless the WinLogger is used in "service" mode
|
||||
(see WinLogger.Executable and WinLogger.ForceService).
|
||||
|
||||
If you need recommended defaults, you may want to use the Event* constants (e.g. EventAlert, EventDebug, etc.)
|
||||
or even use the pre-populated DefaultEventID (which is assigned the above Event* constants).
|
||||
*/
|
||||
type WinEventID struct {
|
||||
Alert,
|
||||
Crit,
|
||||
Debug,
|
||||
Emerg,
|
||||
Err,
|
||||
Info,
|
||||
Notice,
|
||||
Warning uint32
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package structutils
|
||||
|
||||
const (
|
||||
TagMapTrim tagMapOpt = iota
|
||||
)
|
|
@ -0,0 +1,362 @@
|
|||
/*
|
||||
GoUtils - a library to assist with various Golang-related functions
|
||||
Copyright (C) 2020 Brent Saner
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package structutils
|
||||
|
||||
import (
|
||||
`reflect`
|
||||
`strings`
|
||||
)
|
||||
|
||||
/*
|
||||
TagToBoolMap takes struct field `field` and tag name `tagName`,
|
||||
optionally with options `opts`, and returns a map of the tag values.
|
||||
The tag value string is assumed to be in the form of:
|
||||
option[,option,option...]
|
||||
and returns a map[string]bool (map[option]true).
|
||||
|
||||
If field does not have tag tagName, m will be nil.
|
||||
|
||||
See the TagMap* constants for opts.
|
||||
*/
|
||||
func TagToBoolMap(field reflect.StructField, tagName string, opts ...tagMapOpt) (m map[string]bool) {
|
||||
|
||||
var s string
|
||||
var optSplit []string
|
||||
var tagOpts map[tagMapOpt]bool = getTagMapOpts(opts)
|
||||
|
||||
s = field.Tag.Get(tagName)
|
||||
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return
|
||||
}
|
||||
|
||||
optSplit = strings.Split(s, ",")
|
||||
if optSplit == nil || len(optSplit) == 0 {
|
||||
return
|
||||
}
|
||||
m = make(map[string]bool)
|
||||
for _, o := range optSplit {
|
||||
if tagOpts[TagMapTrim] {
|
||||
o = strings.TrimSpace(o)
|
||||
}
|
||||
m[o] = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
TagToBoolMapWithValue is like TagToBoolMap but additionally assumes the first value is an "identifier".
|
||||
The tag value string is assumed to be in the form of:
|
||||
value,option[,option,option...]
|
||||
and returns a map[string]bool (map[option]true) with the value.
|
||||
*/
|
||||
func TagToBoolMapWithValue(field reflect.StructField, tagName string, opts ...tagMapOpt) (value string, m map[string]bool) {
|
||||
|
||||
var s string
|
||||
var optSplit []string
|
||||
var tagOpts map[tagMapOpt]bool = getTagMapOpts(opts)
|
||||
|
||||
s = field.Tag.Get(tagName)
|
||||
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return
|
||||
}
|
||||
|
||||
optSplit = strings.Split(s, ",")
|
||||
if optSplit == nil || len(optSplit) == 0 {
|
||||
return
|
||||
}
|
||||
m = make(map[string]bool)
|
||||
for idx, o := range optSplit {
|
||||
if idx == 0 {
|
||||
if tagOpts[TagMapTrim] {
|
||||
o = strings.TrimSpace(o)
|
||||
}
|
||||
value = o
|
||||
continue
|
||||
}
|
||||
if tagOpts[TagMapTrim] {
|
||||
o = strings.TrimSpace(o)
|
||||
}
|
||||
m[o] = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
TagToMixedMap combines TagToBoolMap and TagToStringMap.
|
||||
It takes struct field `field` and tag name `tagName`,
|
||||
and returns all single-value options in mapBool, and all key/value options in mapString.
|
||||
|
||||
If field does not have tag tagName, m will be nil.
|
||||
|
||||
See the TagMap* constants for opts.
|
||||
*/
|
||||
func TagToMixedMap(field reflect.StructField, tagName string, opts ...tagMapOpt) (mapBool map[string]bool, mapString map[string]string) {
|
||||
|
||||
var s string
|
||||
var valStr string
|
||||
var split []string
|
||||
var kvSplit []string
|
||||
var valSplit []string
|
||||
var k string
|
||||
var v string
|
||||
var tagOpts map[tagMapOpt]bool = getTagMapOpts(opts)
|
||||
|
||||
s = field.Tag.Get(tagName)
|
||||
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return
|
||||
}
|
||||
|
||||
split = strings.Split(s, ",")
|
||||
if split == nil || len(split) == 0 {
|
||||
return
|
||||
}
|
||||
mapBool = make(map[string]bool)
|
||||
mapString = make(map[string]string)
|
||||
for _, valStr = range split {
|
||||
if strings.Contains(valStr, "=") {
|
||||
kvSplit = strings.SplitN(valStr, "=", 2)
|
||||
if kvSplit == nil || len(kvSplit) == 0 {
|
||||
continue
|
||||
}
|
||||
k = valSplit[0]
|
||||
switch len(valSplit) {
|
||||
case 1:
|
||||
v = ""
|
||||
case 2:
|
||||
v = kvSplit[1]
|
||||
}
|
||||
if tagOpts[TagMapTrim] {
|
||||
k = strings.TrimSpace(k)
|
||||
v = strings.TrimSpace(v)
|
||||
}
|
||||
mapString[k] = v
|
||||
} else {
|
||||
if tagOpts[TagMapTrim] {
|
||||
valStr = strings.TrimSpace(valStr)
|
||||
}
|
||||
mapBool[valStr] = true
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
TagToMixedMapWithValue combines TagToBoolMapWithValue and TagToStringMapWithValue.
|
||||
It takes struct field `field` and tag name `tagName`,
|
||||
and returns all single-value options in mapBool, and all key/value options in mapString
|
||||
along with the first single-value option as value..
|
||||
|
||||
If field does not have tag tagName, m will be nil.
|
||||
|
||||
See the TagMap* constants for opts.
|
||||
*/
|
||||
func TagToMixedMapWithValue(field reflect.StructField, tagName string, opts ...tagMapOpt) (value string, mapBool map[string]bool, mapString map[string]string) {
|
||||
|
||||
var s string
|
||||
var idx int
|
||||
var valStr string
|
||||
var split []string
|
||||
var kvSplit []string
|
||||
var valSplit []string
|
||||
var k string
|
||||
var v string
|
||||
var tagOpts map[tagMapOpt]bool = getTagMapOpts(opts)
|
||||
|
||||
s = field.Tag.Get(tagName)
|
||||
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return
|
||||
}
|
||||
|
||||
split = strings.Split(s, ",")
|
||||
if split == nil || len(split) == 0 {
|
||||
return
|
||||
}
|
||||
mapBool = make(map[string]bool)
|
||||
mapString = make(map[string]string)
|
||||
for idx, valStr = range split {
|
||||
if idx == 0 {
|
||||
if tagOpts[TagMapTrim] {
|
||||
valStr = strings.TrimSpace(valStr)
|
||||
}
|
||||
value = valStr
|
||||
continue
|
||||
}
|
||||
if strings.Contains(valStr, "=") {
|
||||
kvSplit = strings.SplitN(valStr, "=", 2)
|
||||
if kvSplit == nil || len(kvSplit) == 0 {
|
||||
continue
|
||||
}
|
||||
k = valSplit[0]
|
||||
switch len(valSplit) {
|
||||
case 1:
|
||||
v = ""
|
||||
case 2:
|
||||
v = kvSplit[1]
|
||||
}
|
||||
if tagOpts[TagMapTrim] {
|
||||
k = strings.TrimSpace(k)
|
||||
v = strings.TrimSpace(v)
|
||||
}
|
||||
mapString[k] = v
|
||||
} else {
|
||||
if tagOpts[TagMapTrim] {
|
||||
valStr = strings.TrimSpace(valStr)
|
||||
}
|
||||
mapBool[valStr] = true
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
TagToStringMap takes struct field `field` and tag name `tagName`,
|
||||
optionally with options `opts`, and returns a map of the tag values.
|
||||
The tag value string is assumed to be in the form of:
|
||||
key=value[,key=value,key=value...]
|
||||
and returns a map[string]string (map[key]value).
|
||||
It is proccessed in order; later duplicate keys overwrite previous ones.
|
||||
|
||||
If field does not have tag tagName, m will be nil.
|
||||
|
||||
If only a key is provided with no value, the value in the map will be an empty string.
|
||||
(e.g. "foo,bar=baz" => map[string]string{"foo": "", "bar: "baz"}
|
||||
|
||||
See the TagMap* constants for opts.
|
||||
*/
|
||||
func TagToStringMap(field reflect.StructField, tagName string, opts ...tagMapOpt) (m map[string]string) {
|
||||
|
||||
var s string
|
||||
var kvSplit []string
|
||||
var valSplit []string
|
||||
var k string
|
||||
var v string
|
||||
var tagOpts map[tagMapOpt]bool = getTagMapOpts(opts)
|
||||
|
||||
s = field.Tag.Get(tagName)
|
||||
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return
|
||||
}
|
||||
|
||||
kvSplit = strings.Split(s, ",")
|
||||
if kvSplit == nil || len(kvSplit) == 0 {
|
||||
return
|
||||
}
|
||||
for _, kv := range kvSplit {
|
||||
valSplit = strings.SplitN(kv, "=", 2)
|
||||
if valSplit == nil || len(valSplit) == 0 {
|
||||
continue
|
||||
}
|
||||
k = valSplit[0]
|
||||
switch len(valSplit) {
|
||||
case 1:
|
||||
v = ""
|
||||
case 2:
|
||||
v = valSplit[1]
|
||||
// It's not possible to have more than 2.
|
||||
}
|
||||
if m == nil {
|
||||
m = make(map[string]string)
|
||||
}
|
||||
if tagOpts[TagMapTrim] {
|
||||
k = strings.TrimSpace(k)
|
||||
v = strings.TrimSpace(v)
|
||||
}
|
||||
m[k] = v
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
TagToStringMapWithValue is like TagToStringMap but additionally assumes the first value is an "identifier".
|
||||
The tag value string is assumed to be in the form of:
|
||||
value,key=value[,key=value,key=value...]
|
||||
and returns a map[string]string (map[key]value) with the value.
|
||||
*/
|
||||
func TagToStringMapWithValue(field reflect.StructField, tagName string, opts ...tagMapOpt) (value string, m map[string]string) {
|
||||
|
||||
var s string
|
||||
var kvSplit []string
|
||||
var valSplit []string
|
||||
var k string
|
||||
var v string
|
||||
var tagOpts map[tagMapOpt]bool = getTagMapOpts(opts)
|
||||
|
||||
s = field.Tag.Get(tagName)
|
||||
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return
|
||||
}
|
||||
|
||||
kvSplit = strings.Split(s, ",")
|
||||
if kvSplit == nil || len(kvSplit) == 0 {
|
||||
return
|
||||
}
|
||||
for idx, kv := range kvSplit {
|
||||
if idx == 0 {
|
||||
if tagOpts[TagMapTrim] {
|
||||
kv = strings.TrimSpace(kv)
|
||||
}
|
||||
value = kv
|
||||
continue
|
||||
}
|
||||
valSplit = strings.SplitN(kv, "=", 2)
|
||||
if valSplit == nil || len(valSplit) == 0 {
|
||||
continue
|
||||
}
|
||||
k = valSplit[0]
|
||||
switch len(valSplit) {
|
||||
case 1:
|
||||
v = ""
|
||||
case 2:
|
||||
v = valSplit[1]
|
||||
// It's not possible to have more than 2.
|
||||
}
|
||||
if m == nil {
|
||||
m = make(map[string]string)
|
||||
}
|
||||
if tagOpts[TagMapTrim] {
|
||||
k = strings.TrimSpace(k)
|
||||
v = strings.TrimSpace(v)
|
||||
}
|
||||
m[k] = v
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getTagMapOpts(opts []tagMapOpt) (optMap map[tagMapOpt]bool) {
|
||||
|
||||
optMap = make(map[tagMapOpt]bool)
|
||||
|
||||
if opts == nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package structutils
|
||||
|
||||
type (
|
||||
tagMapOpt uint8
|
||||
)
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue