Compare commits

..

No commits in common. "master" and "v1.3.0" have entirely different histories.

32 changed files with 213 additions and 1244 deletions

View File

@ -1,12 +1,5 @@
package bitmask package bitmask
import (
"bytes"
"encoding/binary"
"errors"
"math/bits"
)
// MaskBit is a flag container. // MaskBit is a flag container.
type MaskBit uint type MaskBit uint
@ -56,7 +49,7 @@ func (m *MaskBit) AddFlag(flag MaskBit) {
// ClearFlag removes MaskBit flag from m. // ClearFlag removes MaskBit flag from m.
func (m *MaskBit) ClearFlag(flag MaskBit) { func (m *MaskBit) ClearFlag(flag MaskBit) {
*m &^= flag *m &= flag
return return
} }
@ -69,55 +62,6 @@ func (m *MaskBit) ToggleFlag(flag MaskBit) {
return 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. // Value returns the current raw uint value of a MaskBit.
func (m *MaskBit) Value() (v uint) { func (m *MaskBit) Value() (v uint) {

View File

@ -1,22 +1,11 @@
- macOS support beyond the legacy NIX stuff. it apparently uses something called "ULS", "Unified Logging System".
-- https://developer.apple.com/documentation/os/logging
-- https://developer.apple.com/documentation/os/generating-log-messages-from-your-code
-- no native Go support (yet)?
--- https://developer.apple.com/forums/thread/773369
- Implement code line/func/etc. (only for debug?): - Implement code line/func/etc. (only for debug?):
https://stackoverflow.com/a/24809646 https://stackoverflow.com/a/24809646
https://golang.org/pkg/runtime/#Caller https://golang.org/pkg/runtime/#Caller
-- 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

@ -12,16 +12,3 @@ const (
// appendFlags are the flags used for testing the file (and opening/writing). // appendFlags are the flags used for testing the file (and opening/writing).
appendFlags int = os.O_APPEND | os.O_CREATE | os.O_WRONLY appendFlags int = os.O_APPEND | os.O_CREATE | os.O_WRONLY
) )
const PriorityNone logPrio = 0
const (
PriorityEmergency logPrio = 1 << iota
PriorityAlert
PriorityCritical
PriorityError
PriorityWarning
PriorityNotice
PriorityInformational
PriorityDebug
)
const PriorityAll logPrio = PriorityEmergency | PriorityAlert | PriorityCritical | PriorityError | PriorityWarning | PriorityNotice | PriorityInformational | PriorityDebug

View File

@ -1,9 +0,0 @@
package logging
var (
// defLogPaths indicates default log paths.
defLogPaths = []string{
"/var/log/golang/program.log",
"~/Library/Logs/Golang/program.log",
}
)

View File

@ -1,5 +1,32 @@
package logging 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 ( var (
// defLogPaths indicates default log paths. // defLogPaths indicates default log paths.
defLogPaths = []string{ defLogPaths = []string{

View File

@ -1,34 +0,0 @@
//go:build !(windows || plan9 || wasip1 || js || ios)
// +build !windows,!plan9,!wasip1,!js,!ios
// I mean maybe it works for plan9 and ios, I don't know.
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.
// LogUndefined indicates an undefined Logger type.
const LogUndefined bitmask.MaskBit = iota
const (
// LogJournald flags a SystemDLogger Logger type.
LogJournald = 1 << iota
// LogSyslog flags a SyslogLogger Logger type.
LogSyslog
// LogFile flags a FileLogger Logger type.
LogFile
// LogStdout flags a StdLogger Logger type.
LogStdout
)

View File

@ -3,19 +3,18 @@ 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/macOS/other *NIX-like only) WinLogger (Windows 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. There is a sixth 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)
@ -37,7 +36,6 @@ 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,10 +1,18 @@
package logging package logging
import ( import (
"errors" `errors`
`fmt`
) )
var ( var (
// ErrNoSysD indicates that the user attempted to add a SystemDLogger to a MultiLogger but systemd is unavailable. // 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") ErrNoSysD error = errors.New("a systemd (journald) Logger was requested but systemd is unavailable on this system")
// ErrNoSyslog indicates that the user attempted to add a SyslogLogger to a MultiLogger but syslog's logger device is unavailable.
ErrNoSyslog error = errors.New("a Syslog Logger was requested but Syslog is unavailable on this system")
/*
ErrInvalidDevLog indicates that the user attempted to add a SyslogLogger to a MultiLogger but
the Syslog char device file is... not actually a char device file.
*/
ErrInvalidDevLog error = errors.New(fmt.Sprintf("a Syslog Logger was requested but %v is not a valid logger handle", devlog))
) )

View File

@ -1,19 +0,0 @@
//go:build !(windows || plan9 || wasip1 || js || ios)
// +build !windows,!plan9,!wasip1,!js,!ios
package logging
import (
"errors"
"fmt"
)
var (
// ErrNoSyslog indicates that the user attempted to add a SyslogLogger to a MultiLogger but syslog's logger device is unavailable.
ErrNoSyslog error = errors.New("a Syslog Logger was requested but Syslog is unavailable on this system")
/*
ErrInvalidDevLog indicates that the user attempted to add a SyslogLogger to a MultiLogger but
the Syslog char device file is... not actually a char device file.
*/
ErrInvalidDevLog error = errors.New(fmt.Sprintf("a Syslog Logger was requested but %v is not a valid logger handle", devlog))
)

View File

@ -1,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.
@ -43,8 +43,8 @@ func (l *FileLogger) Shutdown() (err error) {
} }
/* /*
GetPrefix returns the prefix used by this FileLogger. GetPrefix returns the prefix used by this FileLogger.
err will always be nil; it's there for interface-compat. err will always be nil; it's there for interface-compat.
*/ */
func (l *FileLogger) GetPrefix() (prefix string, err error) { func (l *FileLogger) GetPrefix() (prefix string, err error) {
@ -54,9 +54,9 @@ func (l *FileLogger) GetPrefix() (prefix string, err error) {
} }
/* /*
DoDebug sets the debug state of this FileLogger. 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. 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. err will always be nil; it's there for interface-compat.
*/ */
func (l *FileLogger) DoDebug(d bool) (err error) { func (l *FileLogger) DoDebug(d bool) (err error) {
@ -65,17 +65,9 @@ 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.
*/ */
func (l *FileLogger) SetPrefix(prefix string) (err error) { func (l *FileLogger) SetPrefix(prefix string) (err error) {
@ -220,14 +212,6 @@ func (l *FileLogger) Warning(s string, v ...interface{}) (err error) {
return return
} }
// ToLogger returns a stdlib log.Logger.
func (l *FileLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
stdLibLog = log.New(&logWriter{backend: l, prio: prio}, "", 0)
return
}
// renderWrite prepares/formats a log message to be written to this FileLogger. // renderWrite prepares/formats a log message to be written to this FileLogger.
func (l *FileLogger) renderWrite(msg, prio string) { func (l *FileLogger) renderWrite(msg, prio string) {

View File

@ -1,23 +0,0 @@
package logging
import (
`r00t2.io/goutils/bitmask`
)
// HasFlag provides a wrapper for functionality to the underlying bitmask.MaskBit.
func (l *logPrio) HasFlag(prio logPrio) (hasFlag bool) {
var m *bitmask.MaskBit
var p *bitmask.MaskBit
if l == nil {
return
}
m = bitmask.NewMaskBitExplicit(uint(*l))
p = bitmask.NewMaskBitExplicit(uint(prio))
hasFlag = m.HasFlag(*p)
return
}

View File

@ -1,74 +0,0 @@
package logging
import (
`r00t2.io/goutils/multierr`
)
// Write writes bytes b to the underlying Logger's priority level if the logWriter's priority level(s) match.
func (l *logWriter) Write(b []byte) (n int, err error) {
var s string
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
if b == nil {
return
}
s = string(b)
if l.prio.HasFlag(PriorityEmergency) {
if err = l.backend.Emerg(s); err != nil {
mErr.AddError(err)
err = nil
}
}
if l.prio.HasFlag(PriorityAlert) {
if err = l.backend.Alert(s); err != nil {
mErr.AddError(err)
err = nil
}
}
if l.prio.HasFlag(PriorityCritical) {
if err = l.backend.Crit(s); err != nil {
mErr.AddError(err)
err = nil
}
}
if l.prio.HasFlag(PriorityError) {
if err = l.backend.Err(s); err != nil {
mErr.AddError(err)
err = nil
}
}
if l.prio.HasFlag(PriorityWarning) {
if err = l.backend.Warning(s); err != nil {
mErr.AddError(err)
err = nil
}
}
if l.prio.HasFlag(PriorityNotice) {
if err = l.backend.Notice(s); err != nil {
mErr.AddError(err)
err = nil
}
}
if l.prio.HasFlag(PriorityInformational) {
if err = l.backend.Info(s); err != nil {
mErr.AddError(err)
err = nil
}
}
if l.prio.HasFlag(PriorityDebug) {
if err = l.backend.Debug(s); err != nil {
mErr.AddError(err)
err = nil
}
}
if !mErr.IsEmpty() {
err = mErr
return
}
return
}

View File

@ -1,12 +1,11 @@
package logging package logging
import ( import (
"errors" `errors`
"fmt" `fmt`
`log` `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.
@ -68,8 +67,8 @@ func (m *MultiLogger) Shutdown() (err error) {
} }
/* /*
GetPrefix returns the prefix used by this MultiLogger (and all its MultiLogger.Loggers). GetPrefix returns the prefix used by this MultiLogger (and all its MultiLogger.Loggers).
err will always be nil; it's there for interface-compat. err will always be nil; it's there for interface-compat.
*/ */
func (m *MultiLogger) GetPrefix() (prefix string, err error) { func (m *MultiLogger) GetPrefix() (prefix string, err error) {
@ -80,10 +79,10 @@ func (m *MultiLogger) GetPrefix() (prefix string, err error) {
} }
/* /*
DoDebug sets the debug state of this MultiLogger (and all its MultiLogger.Loggers). 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. 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. 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) { func (m *MultiLogger) DoDebug(d bool) (err error) {
@ -115,18 +114,10 @@ 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).
If you had a logger-specific Prefix set, you will need to re-set it to your desired prefix after running this method. 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) { func (m *MultiLogger) SetPrefix(prefix string) (err error) {
@ -371,11 +362,3 @@ func (m *MultiLogger) Warning(s string, v ...interface{}) (err error) {
return return
} }
// ToLogger returns a stdlib log.Logger.
func (m *MultiLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
stdLibLog = log.New(&logWriter{backend: m, prio: prio}, "", 0)
return
}

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,40 +145,6 @@ 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

@ -1,17 +1,20 @@
package logging package logging
import ( import (
sysd "github.com/coreos/go-systemd/journal" `os`
"github.com/google/uuid"
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. 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. identifier is a string to use to identify the added Logger in MultiLogger.Loggers.
If empty, one will be automatically generated. If empty, one will be automatically generated.
See the documentation for GetLogger for details on other arguments. See the documentation for GetLogger for details on other arguments.
*/ */
func (m *MultiLogger) AddDefaultLogger(identifier string, logFlags int, logPaths ...string) (err error) { func (m *MultiLogger) AddDefaultLogger(identifier string, logFlags int, logPaths ...string) (err error) {
@ -37,10 +40,10 @@ func (m *MultiLogger) AddDefaultLogger(identifier string, logFlags int, logPaths
} }
/* /*
AddSysdLogger adds a SystemDLogger to a MultiLogger. AddSysdLogger adds a SystemDLogger to a MultiLogger.
identifier is a string to use to identify the added SystemDLogger in MultiLogger.Loggers. identifier is a string to use to identify the added SystemDLogger in MultiLogger.Loggers.
If empty, one will be automatically generated. If empty, one will be automatically generated.
*/ */
func (m *MultiLogger) AddSysdLogger(identifier string) (err error) { func (m *MultiLogger) AddSysdLogger(identifier string) (err error) {
@ -77,3 +80,55 @@ func (m *MultiLogger) AddSysdLogger(identifier string) (err error) {
return 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
}

View File

@ -1,63 +0,0 @@
//go:build !(windows || plan9 || wasip1 || js || ios)
// +build !windows,!plan9,!wasip1,!js,!ios
package logging
import (
"os"
"github.com/google/uuid"
"r00t2.io/sysutils/paths"
)
/*
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
}

View File

@ -1,41 +0,0 @@
//go:build !(windows || plan9 || wasip1 || js || ios || linux)
// +build !windows,!plan9,!wasip1,!js,!ios,!linux
// Linux is excluded because it has its own.
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.
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
}

View File

@ -1,86 +0,0 @@
package logging
import (
`log`
)
// 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
}
// ToLogger returns a stdlib log.Logger (that doesn't actually write to anything).
func (l *NullLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
stdLibLog = log.New(&nullWriter{}, "", 0)
return
}

View File

@ -1,12 +0,0 @@
package logging
// nulLWriter writes... nothing. To avoid errors, however, in downstream code it pretends it does (n will *always* == len(b)).
func (nw *nullWriter) Write(b []byte) (n int, err error) {
if b == nil {
return
}
n = len(b)
return
}

View File

@ -1,138 +0,0 @@
//go:build !(windows || plan9 || wasip1 || js || ios || linux)
// +build !windows,!plan9,!wasip1,!js,!ios,!linux
// Linux is excluded because it has its own.
package logging
import (
native "log"
"os"
"path"
"r00t2.io/goutils/bitmask"
"r00t2.io/sysutils/paths"
)
var (
_ = 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 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(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
}

View File

@ -2,15 +2,15 @@ package logging
import ( import (
"fmt" "fmt"
"io" `io`
"log" `log`
"os" `os`
"strings" `strings`
) )
/* /*
Setup sets up/configures a StdLogger and prepares it for use. Setup sets up/configures a StdLogger and prepares it for use.
err will always be nil; it's there for interface-compat. err will always be nil; it's there for interface-compat.
*/ */
func (l *StdLogger) Setup() (err error) { func (l *StdLogger) Setup() (err error) {
@ -47,8 +47,8 @@ func (l *StdLogger) Setup() (err error) {
} }
/* /*
Shutdown cleanly shuts down a StdLogger. Shutdown cleanly shuts down a StdLogger.
err will always be nil; it's there for interface-compat. err will always be nil; it's there for interface-compat.
*/ */
func (l *StdLogger) Shutdown() (err error) { func (l *StdLogger) Shutdown() (err error) {
@ -58,8 +58,8 @@ func (l *StdLogger) Shutdown() (err error) {
} }
/* /*
GetPrefix returns the prefix used by this StdLogger. GetPrefix returns the prefix used by this StdLogger.
err will always be nil; it's there for interface-compat. err will always be nil; it's there for interface-compat.
*/ */
func (l *StdLogger) GetPrefix() (prefix string, err error) { func (l *StdLogger) GetPrefix() (prefix string, err error) {
@ -69,9 +69,9 @@ func (l *StdLogger) GetPrefix() (prefix string, err error) {
} }
/* /*
DoDebug sets the debug state of this StdLogger. 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. 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. err will always be nil; it's there for interface-compat.
*/ */
func (l *StdLogger) DoDebug(d bool) (err error) { func (l *StdLogger) DoDebug(d bool) (err error) {
@ -80,17 +80,9 @@ 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.
*/ */
func (l *StdLogger) SetPrefix(prefix string) (err error) { func (l *StdLogger) SetPrefix(prefix string) (err error) {
@ -244,11 +236,3 @@ func (l *StdLogger) renderWrite(msg, prio string) {
return return
} }
// ToLogger returns a stdlib log.Logger.
func (l *StdLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
stdLibLog = log.New(&logWriter{backend: l, prio: prio}, "", 0)
return
}

View File

@ -8,8 +8,8 @@ import (
) )
/* /*
Setup sets up/configures a SystemDLogger and prepares it for use. Setup sets up/configures a SystemDLogger and prepares it for use.
err will always be nil; it's there for interface-compat. err will always be nil; it's there for interface-compat.
*/ */
func (l *SystemDLogger) Setup() (err error) { func (l *SystemDLogger) Setup() (err error) {
@ -19,8 +19,8 @@ func (l *SystemDLogger) Setup() (err error) {
} }
/* /*
Shutdown cleanly shuts down a SystemDLogger. Shutdown cleanly shuts down a SystemDLogger.
err will always be nil; it's there for interface-compat. err will always be nil; it's there for interface-compat.
*/ */
func (l *SystemDLogger) Shutdown() (err error) { func (l *SystemDLogger) Shutdown() (err error) {
@ -30,8 +30,8 @@ func (l *SystemDLogger) Shutdown() (err error) {
} }
/* /*
GetPrefix returns the prefix used by this SystemDLogger. GetPrefix returns the prefix used by this SystemDLogger.
err will always be nil; it's there for interface-compat. err will always be nil; it's there for interface-compat.
*/ */
func (l *SystemDLogger) GetPrefix() (prefix string, err error) { func (l *SystemDLogger) GetPrefix() (prefix string, err error) {
@ -41,9 +41,9 @@ func (l *SystemDLogger) GetPrefix() (prefix string, err error) {
} }
/* /*
DoDebug sets the debug state of this SystemDLogger. 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. 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. err will always be nil; it's there for interface-compat.
*/ */
func (l *SystemDLogger) DoDebug(d bool) (err error) { func (l *SystemDLogger) DoDebug(d bool) (err error) {
@ -52,17 +52,9 @@ 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.
*/ */
func (l *SystemDLogger) SetPrefix(prefix string) (err error) { func (l *SystemDLogger) SetPrefix(prefix string) (err error) {
@ -223,11 +215,3 @@ func (l *SystemDLogger) renderWrite(msg string, prio journal.Priority) {
return return
} }
// ToLogger returns a stdlib log.Logger.
func (l *SystemDLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
stdLibLog = log.New(&logWriter{backend: l, prio: prio}, "", 0)
return
}

View File

@ -1,6 +1,3 @@
//go:build !(windows || plan9 || wasip1 || js || ios)
// +build !windows,!plan9,!wasip1,!js,!ios
package logging package logging
import ( import (
@ -8,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.
@ -76,8 +73,8 @@ func (l *SyslogLogger) Shutdown() (err error) {
} }
/* /*
GetPrefix returns the prefix used by this SyslogLogger. GetPrefix returns the prefix used by this SyslogLogger.
err will always be nil; it's there for interface-compat. err will always be nil; it's there for interface-compat.
*/ */
func (l *SyslogLogger) GetPrefix() (prefix string, err error) { func (l *SyslogLogger) GetPrefix() (prefix string, err error) {
@ -87,9 +84,9 @@ func (l *SyslogLogger) GetPrefix() (prefix string, err error) {
} }
/* /*
DoDebug sets the debug state of this SyslogLogger. 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. 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. err will always be nil; it's there for interface-compat.
*/ */
func (l *SyslogLogger) DoDebug(d bool) (err error) { func (l *SyslogLogger) DoDebug(d bool) (err error) {
@ -98,14 +95,6 @@ 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) {
@ -269,11 +258,3 @@ func (l *SyslogLogger) Warning(s string, v ...interface{}) (err error) {
return return
} }
// ToLogger returns a stdlib log.Logger.
func (l *SyslogLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
stdLibLog = log.New(&logWriter{backend: l, prio: prio}, "", 0)
return
}

View File

@ -1,27 +1,26 @@
package logging package logging
import ( import (
"errors" `errors`
"fmt" `fmt`
`log` `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`
) )
/* /*
Setup sets up/configures a WinLogger and prepares it for use. 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 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. 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 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), 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). eventlog.Install will be used (with the WinLogger.ExpandKey field).
Otherwise eventlog.InstallAsEventCreate will be used. Otherwise eventlog.InstallAsEventCreate will be used.
*/ */
func (l *WinLogger) Setup() (err error) { func (l *WinLogger) Setup() (err error) {
@ -109,8 +108,8 @@ func (l *WinLogger) Remove() (err error) {
} }
/* /*
Shutdown cleanly shuts down a WinLogger but keep the source registered. Use WinLogger.Remove 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. (or set WinLogger.RemoveOnClose to true before calling WinLogger.Shutdown) to remove the registered source.
*/ */
func (l *WinLogger) Shutdown() (err error) { func (l *WinLogger) Shutdown() (err error) {
@ -129,8 +128,8 @@ func (l *WinLogger) Shutdown() (err error) {
} }
/* /*
GetPrefix returns the prefix used by this WinLogger. GetPrefix returns the prefix used by this WinLogger.
err will always be nil; it's there for interface-compat. err will always be nil; it's there for interface-compat.
*/ */
func (l *WinLogger) GetPrefix() (prefix string, err error) { func (l *WinLogger) GetPrefix() (prefix string, err error) {
@ -140,9 +139,9 @@ func (l *WinLogger) GetPrefix() (prefix string, err error) {
} }
/* /*
DoDebug sets the debug state of this WinLogger. 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. 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. err will always be nil; it's there for interface-compat.
*/ */
func (l *WinLogger) DoDebug(d bool) (err error) { func (l *WinLogger) DoDebug(d bool) (err error) {
@ -151,14 +150,6 @@ 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) {
@ -343,11 +334,3 @@ func (l *WinLogger) Warning(s string, v ...interface{}) (err error) {
return return
} }
// ToLogger returns a stdlib log.Logger.
func (l *WinLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
stdLibLog = log.New(&logWriter{backend: l, prio: prio}, "", 0)
return
}

View File

@ -2,15 +2,11 @@ package logging
import ( import (
"log" "log"
"os" `os`
`r00t2.io/goutils/bitmask`
) )
type logPrio bitmask.MaskBit
/* /*
Logger is one of the various loggers offered by this module. Logger is one of the various loggers offered by this module.
*/ */
type Logger interface { type Logger interface {
Alert(s string, v ...interface{}) (err error) Alert(s string, v ...interface{}) (err error)
@ -22,17 +18,15 @@ 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)
Shutdown() (err error) Shutdown() (err error)
ToLogger(prio logPrio) (stdLibLog *log.Logger)
} }
/* /*
StdLogger uses the log package in stdlib to perform all logging. The default is to write to STDOUT. 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. If you wish to modify the underling log.Logger object, you can access it directly via StdLogger.Logger.
*/ */
type StdLogger struct { type StdLogger struct {
// All log.Logger fields/methods are exposed. // All log.Logger fields/methods are exposed.
@ -77,11 +71,11 @@ type StdLogger struct {
} }
/* /*
FileLogger uses a StdLogger with a file handle writer to write to the file given at Path. 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 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. 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.) (Alternatively, run FileLogger.Shutdown and replace your logger with a new FileLogger.)
*/ */
type FileLogger struct { type FileLogger struct {
// StdLogger is used for the log formation and handling. See StdLogger for more details. // StdLogger is used for the log formation and handling. See StdLogger for more details.
@ -92,9 +86,6 @@ 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 {
/* /*
@ -110,12 +101,3 @@ type MultiLogger struct {
*/ */
Loggers map[string]Logger Loggers map[string]Logger
} }
// logWriter is used as a log.Logger and is returned by <Logger>.ToLogger.
type logWriter struct {
backend Logger
prio logPrio
}
// nullWriter is used as a shortcut by NullLogger.ToLogger.
type nullWriter struct{}

View File

@ -1,9 +1,27 @@
package logging package logging
import (
`log/syslog`
)
/* /*
SystemDLogger (yes, I'm aware it's actually written as "systemd") writes to journald on systemd-enabled systems. SystemDLogger (yes, I'm aware it's actually written as "systemd") writes to journald on systemd-enabled systems.
*/ */
type SystemDLogger struct { type SystemDLogger struct {
EnableDebug bool EnableDebug bool
Prefix string 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
}

View File

@ -1,22 +0,0 @@
//go:build !(windows || plan9 || wasip1 || js || ios)
// +build !windows,!plan9,!wasip1,!js,!ios
package logging
import (
"log/syslog"
)
// 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
}

View File

@ -69,9 +69,6 @@ func (e *MultiError) Error() (errStr string) {
numErrs = len(e.Errors) numErrs = len(e.Errors)
} }
e.lock.Lock()
defer e.lock.Unlock()
for idx, err := range e.Errors { for idx, err := range e.Errors {
if (idx + 1) < numErrs { if (idx + 1) < numErrs {
errStr += fmt.Sprintf("%v%v", err.Error(), e.ErrorSep) errStr += fmt.Sprintf("%v%v", err.Error(), e.ErrorSep)
@ -90,9 +87,6 @@ func (e *MultiError) AddError(err error) {
return return
} }
e.lock.Lock()
defer e.lock.Unlock()
e.Errors = append(e.Errors, err) e.Errors = append(e.Errors, err)
} }

View File

@ -1,14 +1,9 @@
package multierr package multierr
import (
`sync`
)
// MultiError is a type of error.Error that can contain multiple errors. // MultiError is a type of error.Error that can contain multiple errors.
type MultiError struct { type MultiError struct {
// Errors is a slice of errors to combine/concatenate when .Error() is called. // Errors is a slice of errors to combine/concatenate when .Error() is called.
Errors []error `json:"errors"` Errors []error `json:"errors"`
// ErrorSep is a string to use to separate errors for .Error(). The default is "\n". // ErrorSep is a string to use to separate errors for .Error(). The default is "\n".
ErrorSep string `json:"separator"` ErrorSep string `json:"separator"`
lock sync.Mutex
} }

View File

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

View File

@ -1,362 +0,0 @@
/*
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
}

View File

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