6 Commits

Author SHA1 Message Date
brent saner
b87934e8a9 v1.5.0
Added structutils
2024-04-13 13:04:17 -04:00
70d6c2cbb3 updating logging TODO 2023-07-12 23:16:31 -04:00
a445a51c0d Adding GetDebug method to loggers. 2022-09-07 06:03:28 -04:00
a2a849600b Add docs for NullLogger. 2022-09-06 01:01:39 -04:00
94145fb4c7 Add NullLogger to MultiLogger. 2022-03-13 13:34:24 -04:00
81a2d308f0 Add NullLogger.
For when you need a Logger but don't want one. ;)
2022-03-13 13:29:31 -04:00
14 changed files with 633 additions and 94 deletions

View File

@@ -3,9 +3,14 @@
https://golang.org/pkg/runtime/#Caller 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. -- 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) - Suport remote loggers? (eventlog, syslog, systemd)
- JSON logger? YAML logger? XML logger? - JSON logger? YAML logger? XML logger?
- DOCS. - DOCS.
-- Done, but flesh out. -- Done, but flesh out.
- Implement io.Writer interfaces

View File

@@ -3,18 +3,19 @@ Package logging implements and presents various loggers under a unified interfac
These particular loggers (logging.Logger) available are: These particular loggers (logging.Logger) available are:
NullLogger
StdLogger StdLogger
FileLogger FileLogger
SystemDLogger (Linux only) SystemDLogger (Linux only)
SyslogLogger (Linux only) SyslogLogger (Linux only)
WinLogger (Windows only) WinLogger (Windows only)
There is a sixth type of logging.Logger, MultiLogger, that allows for multiple loggers to be written to with a single call. 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. 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. 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". Every logging.Logger type has the following methods that correspond to certain "levels".
Alert(s string, v ...interface{}) (err error) Alert(s string, v ...interface{}) (err error)
@@ -36,6 +37,7 @@ Note that in the case of a MultiLogger, err (if not nil) will be a (r00t2.io/gou
logging.Logger types also have the following methods: logging.Logger types also have the following methods:
DoDebug(d bool) (err error) DoDebug(d bool) (err error)
GetDebug() (d bool)
SetPrefix(p string) (err error) SetPrefix(p string) (err error)
GetPrefix() (p string, err error) GetPrefix() (p string, err error)
Setup() (err error) Setup() (err error)

View File

@@ -1,12 +1,12 @@
package logging package logging
import ( import (
`errors` "errors"
"fmt" "fmt"
`io/fs` "io/fs"
"log" "log"
"os" "os"
`strings` "strings"
) )
// Setup sets up/configures a FileLogger and prepares it for use. // Setup sets up/configures a FileLogger and prepares it for use.
@@ -65,6 +65,14 @@ func (l *FileLogger) DoDebug(d bool) (err error) {
return return
} }
// 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. SetPrefix sets the prefix for this FileLogger.
err will always be nil; it's there for interface-compat. err will always be nil; it's there for interface-compat.

View File

@@ -1,11 +1,11 @@
package logging package logging
import ( import (
`errors` "errors"
`fmt` "fmt"
`sync` "sync"
`r00t2.io/goutils/multierr` "r00t2.io/goutils/multierr"
) )
// Setup sets up/configures a MultiLogger (and all its MultiLogger.Loggers) and prepares it for use. // Setup sets up/configures a MultiLogger (and all its MultiLogger.Loggers) and prepares it for use.
@@ -114,6 +114,14 @@ func (m *MultiLogger) DoDebug(d bool) (err error) {
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). SetPrefix sets the prefix for this MultiLogger (and all its MultiLogger.Loggers).

View File

@@ -1,10 +1,10 @@
package logging package logging
import ( import (
`path` "path"
`github.com/google/uuid` "github.com/google/uuid"
`r00t2.io/sysutils/paths` "r00t2.io/sysutils/paths"
) )
/* /*
@@ -145,6 +145,40 @@ func (m *MultiLogger) AddFileLogger(identifier string, logFlags int, logfilePath
return 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. // RemoveLogger will let you remove a Logger from MultiLogger.Loggers.
func (m *MultiLogger) RemoveLogger(identifier string) (err error) { func (m *MultiLogger) RemoveLogger(identifier string) (err error) {

View File

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

View File

@@ -2,10 +2,10 @@ package logging
import ( import (
"fmt" "fmt"
`io` "io"
`log` "log"
`os` "os"
`strings` "strings"
) )
/* /*
@@ -80,6 +80,14 @@ func (l *StdLogger) DoDebug(d bool) (err error) {
return return
} }
// 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. SetPrefix sets the prefix for this StdLogger.
err will always be nil; it's there for interface-compat. err will always be nil; it's there for interface-compat.

View File

@@ -52,6 +52,14 @@ func (l *SystemDLogger) DoDebug(d bool) (err error) {
return return
} }
// 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. SetPrefix sets the prefix for this SystemDLogger.
err will always be nil; it's there for interface-compat. err will always be nil; it's there for interface-compat.

View File

@@ -5,7 +5,7 @@ import (
"log" "log"
"log/syslog" "log/syslog"
`r00t2.io/goutils/multierr` "r00t2.io/goutils/multierr"
) )
// Setup sets up/configures a SyslogLogger and prepares it for use. // Setup sets up/configures a SyslogLogger and prepares it for use.
@@ -95,6 +95,14 @@ func (l *SyslogLogger) DoDebug(d bool) (err error) {
return return
} }
// 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. // SetPrefix sets the prefix for this SyslogLogger.
func (l *SyslogLogger) SetPrefix(prefix string) (err error) { func (l *SyslogLogger) SetPrefix(prefix string) (err error) {

View File

@@ -1,15 +1,15 @@
package logging package logging
import ( import (
`errors` "errors"
`fmt` "fmt"
`os` "os"
`os/exec` "os/exec"
`syscall` "syscall"
`golang.org/x/sys/windows/registry` "golang.org/x/sys/windows/registry"
`golang.org/x/sys/windows/svc/eventlog` "golang.org/x/sys/windows/svc/eventlog"
`r00t2.io/sysutils/paths` "r00t2.io/sysutils/paths"
) )
/* /*
@@ -150,6 +150,14 @@ func (l *WinLogger) DoDebug(d bool) (err error) {
return 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. // SetPrefix sets the prefix for this WinLogger.
func (l *WinLogger) SetPrefix(prefix string) (err error) { func (l *WinLogger) SetPrefix(prefix string) (err error) {

View File

@@ -2,7 +2,7 @@ package logging
import ( import (
"log" "log"
`os` "os"
) )
/* /*
@@ -18,6 +18,7 @@ type Logger interface {
Notice(s string, v ...interface{}) (err error) Notice(s string, v ...interface{}) (err error)
Warning(s string, v ...interface{}) (err error) Warning(s string, v ...interface{}) (err error)
DoDebug(d bool) (err error) DoDebug(d bool) (err error)
GetDebug() (d bool)
SetPrefix(p string) (err error) SetPrefix(p string) (err error)
GetPrefix() (p string, err error) GetPrefix() (p string, err error)
Setup() (err error) Setup() (err error)
@@ -86,6 +87,9 @@ type FileLogger struct {
writer *os.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. // MultiLogger is used to contain one or more Loggers and present them all as a single Logger.
type MultiLogger struct { type MultiLogger struct {
/* /*

5
structutils/consts.go Normal file
View File

@@ -0,0 +1,5 @@
package structutils
const (
TagMapTrim tagMapOpt = iota
)

362
structutils/funcs.go Normal file
View File

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

5
structutils/types.go Normal file
View File

@@ -0,0 +1,5 @@
package structutils
type (
tagMapOpt uint8
)