Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
965657d1b2
|
||
|
|
970acd0ee4
|
||
|
|
2222cea7fb
|
||
|
|
688abd0874
|
||
|
|
a1f87d6b51
|
||
|
|
07951f1f03
|
||
|
|
bae0abe960
|
||
|
|
368ae0cb8e
|
||
|
|
154170c0e5
|
||
|
|
d9bd928edb
|
||
|
|
dc2ed32352
|
||
|
|
e734e847c4
|
||
|
|
2203de4e32
|
||
|
|
a0c6df14aa
|
||
|
|
fd720f2b34
|
||
|
|
3c543a05e7
|
||
|
|
e5191383a7
|
||
|
|
ae49f42c0c
|
||
|
|
b87934e8a9
|
||
|
70d6c2cbb3
|
|||
|
a445a51c0d
|
|||
|
a2a849600b
|
|||
|
94145fb4c7
|
|||
|
81a2d308f0
|
|||
|
c4b3c6441a
|
|||
|
1c5abd4083
|
|||
|
d98363c0d7
|
|||
|
39e0a1fd43
|
|||
|
3d0d420454
|
19
.encoding.TODO/bit/docs.go
Normal file
19
.encoding.TODO/bit/docs.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Package bit aims to provide feature parity with stdlib's [encoding/hex].
|
||||||
|
|
||||||
|
It's a ludicrous tragedy that hex/base16, base32, base64 all have libraries for converting
|
||||||
|
to/from string representations... but there's nothing for binary ('01010001' etc.) whatsoever.
|
||||||
|
|
||||||
|
This package also provides some extra convenience functions and types in an attempt to provide
|
||||||
|
an abstracted bit-level fidelity in Go. A [Bit] is a bool type, in which that underlying bool
|
||||||
|
being false represents a 0 and that underlying bool being true represents a 1.
|
||||||
|
|
||||||
|
Note that a [Bit] or arbitrary-length or non-octal-aligned [][Bit] may take up more bytes in memory
|
||||||
|
than expected; a [Bit] will actually always occupy a single byte -- thus representing
|
||||||
|
`00000000 00000000` as a [][Bit] or [16][Bit] will actually occupy *sixteen bytes* in memory,
|
||||||
|
NOT 2 bytes (nor, obviously, [2][Byte])!
|
||||||
|
It is recommended instead to use a [Bits] instead of a [Bit] slice or array, as it will try to properly align to the
|
||||||
|
smallest memory allocation possible (at the cost of a few extra CPU cycles on adding/removing one or more [Bit]).
|
||||||
|
It will properly retain any appended, prepended, leading, or trailing bits that do not currently align to a byte.
|
||||||
|
*/
|
||||||
|
package bit
|
||||||
14
.encoding.TODO/bit/funcs.go
Normal file
14
.encoding.TODO/bit/funcs.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package bit
|
||||||
|
|
||||||
|
// TODO: Provide analogues of encoding/hex, encoding/base64, etc. functions etc.
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: Also provide interfaces for the following:
|
||||||
|
|
||||||
|
* https://pkg.go.dev/encoding#BinaryAppender
|
||||||
|
* https://pkg.go.dev/encoding#BinaryMarshaler
|
||||||
|
* https://pkg.go.dev/encoding#BinaryUnmarshaler
|
||||||
|
* https://pkg.go.dev/encoding#TextAppender
|
||||||
|
* https://pkg.go.dev/encoding#TextMarshaler
|
||||||
|
* https://pkg.go.dev/encoding#TextUnmarshaler
|
||||||
|
*/
|
||||||
34
.encoding.TODO/bit/types.go
Normal file
34
.encoding.TODO/bit/types.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package bit
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Bit aims to provide a native-like type for a single bit (Golang operates on the smallest fidelity level of *byte*/uint8).
|
||||||
|
Bit bool
|
||||||
|
|
||||||
|
// Bits is an arbitrary length of bits.
|
||||||
|
Bits struct {
|
||||||
|
/*
|
||||||
|
leading is a series of Bit that do not cleanly align to the beginning of Bits.b.
|
||||||
|
They will always be the bits at the *beginning* of the sequence.
|
||||||
|
len(Bits.leading) will *never* be more than 7;
|
||||||
|
it's converted into a byte, prepended to Bits.b, and cleared if it reaches that point.
|
||||||
|
*/
|
||||||
|
leading []Bit
|
||||||
|
// b is the condensed/memory-aligned alternative to an [][8]Bit (or []Bit, or [][]Bit, etc.).
|
||||||
|
b []byte
|
||||||
|
/*
|
||||||
|
remaining is a series of Bit that do not cleanly align to the end of Bits.b.
|
||||||
|
They will always be the bits at the *end* of the sequence.
|
||||||
|
len(Bits.remaining) will *never* be more than 7;
|
||||||
|
it's converted into a byte, appended to Bits.b, and cleared if it reaches that point.
|
||||||
|
*/
|
||||||
|
remaining []Bit
|
||||||
|
// fixedLen, if 0, represents a "slice". If >= 1, it represents an "array".
|
||||||
|
fixedLen uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Byte is this package's representation of a byte. It's primarily for convenience.
|
||||||
|
Byte byte
|
||||||
|
|
||||||
|
// Bytes is defined as a type for convenience single-call functions.
|
||||||
|
Bytes []Byte
|
||||||
|
)
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -28,6 +28,11 @@
|
|||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.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
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
|||||||
171
bitmask/bitmask.go
Normal file
171
bitmask/bitmask.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
THIS WILL RETURN FALSE FOR OR'd FLAGS.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
flagA MaskBit = 0x01
|
||||||
|
flagB MaskBit = 0x02
|
||||||
|
flagComposite = flagA | flagB
|
||||||
|
|
||||||
|
m *MaskBit = NewMaskBitExplicit(uint(flagA))
|
||||||
|
|
||||||
|
m.HasFlag(flagComposite) will return false even though flagComposite is an OR
|
||||||
|
that contains flagA.
|
||||||
|
Use [MaskBit.IsOneOf] instead if you do not desire this behavior,
|
||||||
|
and instead want to test composite flag *membership*.
|
||||||
|
(MaskBit.IsOneOf will also return true for non-composite equality.)
|
||||||
|
|
||||||
|
To be more clear, if MaskBit flag is a composite MaskBit (e.g. flagComposite above),
|
||||||
|
HasFlag will only return true of ALL bits in flag are also set in MaskBit m.
|
||||||
|
*/
|
||||||
|
func (m *MaskBit) HasFlag(flag MaskBit) (r bool) {
|
||||||
|
|
||||||
|
var b MaskBit = *m
|
||||||
|
|
||||||
|
if b&flag == flag {
|
||||||
|
r = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsOneOf is like a "looser" form of [MaskBit.HasFlag]
|
||||||
|
in that it allows for testing composite membership.
|
||||||
|
|
||||||
|
See [MaskBit.HasFlag] for more information.
|
||||||
|
|
||||||
|
If composite is *not* an OR'd MaskBit (i.e.
|
||||||
|
it falls directly on a boundary -- 0, 1, 2, 4, 8, 16, etc.),
|
||||||
|
then IsOneOf will behave exactly like HasFlag.
|
||||||
|
|
||||||
|
If m is a composite MaskBit (it usually is) and composite is ALSO a composite MaskBit,
|
||||||
|
IsOneOf will return true if ANY of the flags set in m is set in composite.
|
||||||
|
*/
|
||||||
|
func (m *MaskBit) IsOneOf(composite MaskBit) (r bool) {
|
||||||
|
|
||||||
|
var b MaskBit = *m
|
||||||
|
|
||||||
|
if b&composite != 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
|
||||||
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package bitmask
|
|
||||||
|
|
||||||
// MaskBit is a flag container.
|
|
||||||
type MaskBit uint8
|
|
||||||
|
|
||||||
/*
|
|
||||||
NewMaskBit is a convenience function.
|
|
||||||
It will return a MaskBit with a (referenced) value of 0, so set your consts up accordingly.
|
|
||||||
It is highly recommended to set this default as a "None" flag (separate from your iotas!)
|
|
||||||
as shown in the example.
|
|
||||||
*/
|
|
||||||
func NewMaskBit() (m *MaskBit) {
|
|
||||||
|
|
||||||
m = new(MaskBit)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasFlag is true if m has MaskBit flag set/enabled.
|
|
||||||
func (m *MaskBit) HasFlag(flag MaskBit) (r bool) {
|
|
||||||
|
|
||||||
var b MaskBit = *m
|
|
||||||
|
|
||||||
if b&flag != 0 {
|
|
||||||
r = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddFlag adds MaskBit flag to m.
|
|
||||||
func (m *MaskBit) AddFlag(flag MaskBit) {
|
|
||||||
*m |= flag
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearFlag removes MaskBit flag from m.
|
|
||||||
func (m *MaskBit) ClearFlag(flag MaskBit) {
|
|
||||||
*m &= flag
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleFlag switches MaskBit flag in m to its inverse; if true, it is now false and vice versa.
|
|
||||||
func (m *MaskBit) ToggleFlag(flag MaskBit) {
|
|
||||||
*m ^= flag
|
|
||||||
return
|
|
||||||
}
|
|
||||||
129
bitmask/doc.go
129
bitmask/doc.go
@@ -1,9 +1,35 @@
|
|||||||
/*
|
/*
|
||||||
Package bitmask handles a flag-like opt/bitmask system.
|
Package bitmask handles a flag-like opt/bitmask system.
|
||||||
|
|
||||||
See https://yourbasic.org/golang/bitmask-flag-set-clear/ for more information.
|
See https://yourbasic.org/golang/bitmask-flag-set-clear/ for basic information on what bitmasks are and why they're useful.
|
||||||
|
|
||||||
To use this, set constants like thus:
|
Specifically, in the case of Go, they allow you to essentially manage many, many, many "booleans" as part of a single value.
|
||||||
|
|
||||||
|
A single bool value in Go takes up 8 bits/1 byte, unavoidably.
|
||||||
|
|
||||||
|
However, a [bitmask.MaskBit] is backed by a uint which (depending on your platform) is either 32 bits/4 bytes or 64 bits/8 bytes.
|
||||||
|
|
||||||
|
"But wait, that takes up more memory though!"
|
||||||
|
|
||||||
|
Yep, but bitmasking lets you store a "boolean" AT EACH BIT - it operates on
|
||||||
|
whether a bit in a byte/set of bytes at a given position is 0 or 1.
|
||||||
|
|
||||||
|
Which means on 32-bit platforms, a [MaskBit] can have up to 4294967295 "booleans" in a single value (0 to (2^32)-1).
|
||||||
|
|
||||||
|
On 64-bit platforms, a [MaskBit] can have up to 18446744073709551615 "booleans" in a single value (0 to (2^64)-1).
|
||||||
|
|
||||||
|
If you tried to do that with Go bool values, that'd take up 4294967295 bytes (4 GiB)
|
||||||
|
or 18446744073709551615 bytes (16 EiB - yes, that's [exbibytes]) of RAM for 32-bit/64-bit platforms respectively.
|
||||||
|
|
||||||
|
"But that has to be so slow to unpack that!"
|
||||||
|
|
||||||
|
Nope. It's not using compression or anything, the CPU is just comparing bit "A" vs. bit "B" 32/64 times. That's super easy work for a CPU.
|
||||||
|
|
||||||
|
There's a reason Doom used bitmasking for the "dmflags" value in its server configs.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
To use this library, set constants like thus:
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
@@ -11,18 +37,18 @@ To use this, set constants like thus:
|
|||||||
"r00t2.io/goutils/bitmask"
|
"r00t2.io/goutils/bitmask"
|
||||||
)
|
)
|
||||||
|
|
||||||
const OPTNONE types.MaskBit = 0
|
const OPTNONE bitmask.MaskBit = 0
|
||||||
const (
|
const (
|
||||||
OPT1 types.MaskBit = 1 << iota
|
OPT1 bitmask.MaskBit = 1 << iota
|
||||||
OPT2
|
OPT2
|
||||||
OPT3
|
OPT3
|
||||||
// ...
|
// ...
|
||||||
)
|
)
|
||||||
|
|
||||||
var MyMask *MaskBit
|
var MyMask *bitmask.MaskBit
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
MyMask = types.NewMaskBit
|
MyMask = bitmask.NewMaskBit()
|
||||||
|
|
||||||
MyMask.AddFlag(OPT1)
|
MyMask.AddFlag(OPT1)
|
||||||
MyMask.AddFlag(OPT3)
|
MyMask.AddFlag(OPT3)
|
||||||
@@ -41,5 +67,96 @@ As would this:
|
|||||||
But this would return false:
|
But this would return false:
|
||||||
|
|
||||||
MyMask.HasFlag(OPT2)
|
MyMask.HasFlag(OPT2)
|
||||||
|
|
||||||
|
# Technical Caveats
|
||||||
|
|
||||||
|
TARGETING
|
||||||
|
|
||||||
|
When implementing, you should always set MyMask (from Usage section above) as the actual value.
|
||||||
|
For example, if you are checking a permissions set for a user that has the value, say, 6
|
||||||
|
|
||||||
|
var userPerms uint = 6 // 0x0000000000000006
|
||||||
|
|
||||||
|
and your library has the following permission bits defined:
|
||||||
|
|
||||||
|
const PermsNone bitmask.MaskBit = 0
|
||||||
|
const (
|
||||||
|
PermsList bitmask.MaskBit = 1 << iota // 1
|
||||||
|
PermsRead // 2
|
||||||
|
PermsWrite // 4
|
||||||
|
PermsExec // 8
|
||||||
|
PermsAdmin // 16
|
||||||
|
)
|
||||||
|
|
||||||
|
And you want to see if the user has the PermsRead flag set, you would do:
|
||||||
|
|
||||||
|
userPermMask = bitmask.NewMaskBitExplicit(userPerms)
|
||||||
|
if userPermMask.HasFlag(PermsRead) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
NOT:
|
||||||
|
|
||||||
|
userPermMask = bitmask.NewMaskBitExplicit(PermsRead)
|
||||||
|
// Nor:
|
||||||
|
// userPermMask = PermsRead
|
||||||
|
if userPermMask.HasFlag(userPerms) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
This will be terribly, horribly wrong, cause incredibly unexpected results,
|
||||||
|
and quite possibly cause massive security issues. Don't do it.
|
||||||
|
|
||||||
|
COMPOSITES
|
||||||
|
|
||||||
|
If you want to define a set of flags that are a combination of other flags,
|
||||||
|
your inclination would be to bitwise-OR them together:
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagA bitmask.MaskBit = 1 << iota // 1
|
||||||
|
flagB // 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagAB bitmask.MaskBit = flagA | flagB // 3
|
||||||
|
)
|
||||||
|
|
||||||
|
Which is fine and dandy. But if you then have:
|
||||||
|
|
||||||
|
var myMask *bitmask.MaskBit = bitmask.NewMaskBit()
|
||||||
|
|
||||||
|
myMask.AddFlag(flagA)
|
||||||
|
|
||||||
|
You may expect this call to [MaskBit.HasFlag]:
|
||||||
|
|
||||||
|
myMask.HasFlag(flagAB)
|
||||||
|
|
||||||
|
to be true, since flagA is "in" flagAB.
|
||||||
|
It will return false - HasFlag does strict comparisons.
|
||||||
|
It will only return true if you then ALSO do:
|
||||||
|
|
||||||
|
// This would require setting flagA first.
|
||||||
|
// The order of setting flagA/flagB doesn't matter,
|
||||||
|
// but you must have both set for HasFlag(flagAB) to return true.
|
||||||
|
myMask.AddFlag(flagB)
|
||||||
|
|
||||||
|
or if you do:
|
||||||
|
|
||||||
|
// This can be done with or without additionally setting flagA.
|
||||||
|
myMask.AddFlag(flagAB)
|
||||||
|
|
||||||
|
Instead, if you want to see if a mask has membership within a composite flag,
|
||||||
|
you can use [MaskBit.IsOneOf].
|
||||||
|
|
||||||
|
# Other Options
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
[exbibytes]: https://simple.wikipedia.org/wiki/Exbibyte
|
||||||
*/
|
*/
|
||||||
package bitmask
|
package bitmask
|
||||||
|
|||||||
15
go.mod
15
go.mod
@@ -1,10 +1,15 @@
|
|||||||
module r00t2.io/goutils
|
module r00t2.io/goutils
|
||||||
|
|
||||||
go 1.16
|
go 1.24.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
github.com/coreos/go-systemd/v22 v22.5.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.6.0
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
|
golang.org/x/sys v0.34.0
|
||||||
r00t2.io/sysutils v1.1.1
|
r00t2.io/sysutils v1.14.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/djherbis/times v1.6.0 // indirect
|
||||||
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
21
go.sum
21
go.sum
@@ -1,8 +1,13 @@
|
|||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
|
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
r00t2.io/sysutils v1.1.1 h1:q2P5u50HIIRk6muCPo1Gpapy6sNT4oaB1l2O/C/mi3A=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
r00t2.io/sysutils v1.14.0/go.mod h1:ZJ7gZxFVQ7QIokQ5fPZr7wl0XO5Iu+LqtE8j3ciRINw=
|
||||||
|
|||||||
4
iox/docs.go
Normal file
4
iox/docs.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
Package iox includes extensions to the stdlib `io` module.
|
||||||
|
*/
|
||||||
|
package iox
|
||||||
9
iox/errs.go
Normal file
9
iox/errs.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package iox
|
||||||
|
|
||||||
|
import (
|
||||||
|
`errors`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBufTooSmall error = errors.New("buffer too small; buffer size must be > 0")
|
||||||
|
)
|
||||||
41
iox/funcs.go
Normal file
41
iox/funcs.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package iox
|
||||||
|
|
||||||
|
import (
|
||||||
|
`io`
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
CopyBufN is a mix between io.CopyN and io.CopyBuffer.
|
||||||
|
|
||||||
|
Despite what the docs may suggest, io.CopyN does NOT *read* n bytes from src AND write n bytes to dst.
|
||||||
|
Instead, it always reads 32 KiB from src, and writes n bytes to dst.
|
||||||
|
|
||||||
|
There are, of course, cases where this is deadfully undesired.
|
||||||
|
|
||||||
|
One can, of course, use io.CopyBuffer, but this is a bit annoying since you then have to provide a buffer yourself.
|
||||||
|
|
||||||
|
This convenience-wraps io.CopyBuffer to have a similar signature to io.CopyN but properly uses n for both reading and writing.
|
||||||
|
*/
|
||||||
|
func CopyBufN(dst io.Writer, src io.Reader, n int64) (written int64, err error) {
|
||||||
|
|
||||||
|
var b []byte
|
||||||
|
|
||||||
|
if n <= 0 {
|
||||||
|
err = ErrBufTooSmall
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b = make([]byte, n)
|
||||||
|
|
||||||
|
written, err = io.CopyBuffer(dst, src, b)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyBufWith allows for specifying a buffer allocator function, otherwise acts as CopyBufN.
|
||||||
|
func CopyBufWith(dst io.Writer, src io.Reader, bufFunc func() (b []byte)) (written int64, err error) {
|
||||||
|
|
||||||
|
written, err = io.CopyBuffer(dst, src, bufFunc())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
8
iox/types.go
Normal file
8
iox/types.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package iox
|
||||||
|
|
||||||
|
type (
|
||||||
|
// RuneWriter matches the behavior of *(bytes.Buffer).WriteRune and *(bufio.Writer).WriteRune
|
||||||
|
RuneWriter interface {
|
||||||
|
WriteRune(r rune) (n int, err error)
|
||||||
|
}
|
||||||
|
)
|
||||||
22
logging/TODO
22
logging/TODO
@@ -1,11 +1,25 @@
|
|||||||
|
- 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
|
||||||
|
|
||||||
|
- The log destinations for e.g. consts_nix.go et. al. probably should be unexported types.
|
||||||
|
|
||||||
|
- add a `log/slog` logging.Logger?
|
||||||
|
|
||||||
- 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.
|
||||||
|
-- ZeroLog seems to be able to do it, take a peek there.
|
||||||
|
|
||||||
- Support simultaneous writing to multiple Loggers.
|
- 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, journald)
|
||||||
|
|
||||||
|
- JSON logger? YAML logger? XML logger?
|
||||||
|
|
||||||
- DOCS.
|
- DOCS.
|
||||||
|
-- Done, but flesh out.
|
||||||
- Unit/Integration tests.
|
|
||||||
|
|||||||
@@ -12,3 +12,16 @@ 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
|
||||||
|
|||||||
9
logging/consts_darwin.go
Normal file
9
logging/consts_darwin.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package logging
|
||||||
|
|
||||||
|
var (
|
||||||
|
// defLogPaths indicates default log paths.
|
||||||
|
defLogPaths = []string{
|
||||||
|
"/var/log/golang/program.log",
|
||||||
|
"~/Library/Logs/Golang/program.log",
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,32 +1,5 @@
|
|||||||
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{
|
||||||
|
|||||||
34
logging/consts_nix.go
Normal file
34
logging/consts_nix.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
//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. This will, for hopefully obvious reasons, only work on Linux systemd systems.
|
||||||
|
LogJournald bitmask.MaskBit = 1 << iota
|
||||||
|
// LogSyslog flags a SyslogLogger Logger type.
|
||||||
|
LogSyslog
|
||||||
|
// LogFile flags a FileLogger Logger type.
|
||||||
|
LogFile
|
||||||
|
// LogStdout flags a StdLogger Logger type.
|
||||||
|
LogStdout
|
||||||
|
)
|
||||||
39
logging/consts_test.go
Normal file
39
logging/consts_test.go
Normal file
@@ -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
|
||||||
|
)
|
||||||
@@ -3,15 +3,14 @@ package logging
|
|||||||
import (
|
import (
|
||||||
`os`
|
`os`
|
||||||
`path/filepath`
|
`path/filepath`
|
||||||
`regexp`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Flags for logger configuration. These are used internally.
|
// Flags for logger configuration. These are used internally.
|
||||||
|
// LogUndefined indicates an undefined Logger type.
|
||||||
|
const LogUndefined bitmask.MaskBit = 0
|
||||||
const (
|
const (
|
||||||
// LogUndefined indicates an undefined Logger type.
|
|
||||||
LogUndefined types.MaskBit = 1 << iota
|
|
||||||
// LogWinLogger indicates a WinLogger Logger type (Event Log).
|
// LogWinLogger indicates a WinLogger Logger type (Event Log).
|
||||||
LogWinLogger
|
LogWinLogger bitmask.MaskBit = 1 << iota
|
||||||
// LogFile flags a FileLogger Logger type.
|
// LogFile flags a FileLogger Logger type.
|
||||||
LogFile
|
LogFile
|
||||||
// LogStdout flags a StdLogger Logger type.
|
// LogStdout flags a StdLogger Logger type.
|
||||||
@@ -26,8 +25,21 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// ptrnSourceExists is a regex pattern to check for a registry entry (Event Log entry) already existing.
|
/*
|
||||||
var ptrnSourceExists *regexp.Regexp = regexp.MustCompile(`registry\skey\salready\sexists$`)
|
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.
|
// Default WinEventID, (can be) used in GetLogger and MultiLogger.AddWinLogger.
|
||||||
var DefaultEventID *WinEventID = &WinEventID{
|
var DefaultEventID *WinEventID = &WinEventID{
|
||||||
@@ -41,7 +53,7 @@ var DefaultEventID *WinEventID = &WinEventID{
|
|||||||
Warning: EventWarning,
|
Warning: EventWarning,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default Event IDs for WinEventID.
|
// Default Event IDs for WinEventID (DefaultEventID, specifically).
|
||||||
const (
|
const (
|
||||||
EventAlert uint32 = 1 << iota
|
EventAlert uint32 = 1 << iota
|
||||||
EventCrit
|
EventCrit
|
||||||
|
|||||||
@@ -3,18 +3,22 @@ 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:
|
||||||
|
|
||||||
StdLogger
|
NullLogger
|
||||||
FileLogger
|
StdLogger
|
||||||
SystemDLogger (Linux only)
|
FileLogger
|
||||||
SyslogLogger (Linux only)
|
SystemDLogger (Linux only)
|
||||||
WinLogger (Windows only)
|
SyslogLogger (Linux/macOS/other *NIX-like 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.
|
||||||
|
(This is similar to stdlib's io.MultiWriter()'s return value, but with priority awareness and fmt string support).
|
||||||
|
|
||||||
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.
|
As you may have guessed, NullLogger doesn't actually log anything but is fully "functional" as a logging.Logger (similar to io.discard/io.Discard()'s return).
|
||||||
|
|
||||||
|
Note that for some Loggers, the prefix may be modified after the Logger has already initialized.
|
||||||
|
"Literal" loggers (StdLogger and FileLogger) will append a space to the end of the prefix by default.
|
||||||
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)
|
||||||
@@ -35,13 +39,26 @@ 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)
|
DoDebug(d bool) (err error)
|
||||||
SetPrefix(p string)
|
GetDebug() (d bool)
|
||||||
GetPrefix() (p string)
|
SetPrefix(p string) (err error)
|
||||||
Setup()
|
GetPrefix() (p string, err error)
|
||||||
Shutdown()
|
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.
|
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.
|
It is recommended to *always* run Setup and Shutdown before and after using, respectively, regardless of the actual logging.Logger type.
|
||||||
|
|
||||||
|
Lastly, all logging.Loggers have a ToLogger() method. This returns a *log.Logger (from stdlib log), which also conforms to io.Writer inherently.
|
||||||
|
In addition. all have a ToRaw() method, which extends a Logger even further and returns an unexported type (*logging.logWriter) compatible with:
|
||||||
|
|
||||||
|
- io.ByteWriter
|
||||||
|
- io.Writer
|
||||||
|
- io.WriteCloser (Shutdown() on the Logger backend is called during Close(), rendering the underlying Logger unsafe to use afterwards)
|
||||||
|
- io.StringWriter
|
||||||
|
|
||||||
|
and, if stdlib io ever defines an e.g. RuneWriter (WriteRune(r rune) (n int, err error)), it will conform to that too (see (r00t2.io/goutils/iox).RuneWriter).
|
||||||
|
Obviously this and io.ByteWriter are fairly silly, as they're intended to be high-speed throughput-optimized methods, but if you wanted to e.g.
|
||||||
|
log every single byte on a wire as a separate log message, go ahead; I'm not your dad.
|
||||||
*/
|
*/
|
||||||
package logging
|
package logging
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package logging
|
package logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`errors`
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -12,6 +12,8 @@ var (
|
|||||||
exists with too restrictive perms to write/append to, and/or could not be created.
|
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")
|
ErrInvalidFile error = errors.New("a FileLogger was requested but the file does not exist and cannot be created")
|
||||||
|
// ErrInvalidRune is returned if a rune was expected but it is not a valid UTF-8 codepoint.
|
||||||
|
ErrInvalidRune error = errors.New("specified rune is not valid UTF-8 codepoint")
|
||||||
// ErrNoEntry indicates that the user attempted to MultiLogger.RemoveLogger a Logger but one by that identifier does not exist.
|
// 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")
|
ErrNoEntry error = errors.New("the Logger specified to be removed does not exist")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
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))
|
|
||||||
)
|
)
|
||||||
|
|||||||
19
logging/errs_nix.go
Normal file
19
logging/errs_nix.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//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))
|
||||||
|
)
|
||||||
15
logging/errs_windows.go
Normal file
15
logging/errs_windows.go
Normal file
@@ -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))
|
||||||
|
)
|
||||||
@@ -1,18 +1,45 @@
|
|||||||
package logging
|
package logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
ToLog returns a stdlib *log.Logger from a logging.Logger. It simply wraps the (logging.Logger).ToLogger() methods.
|
||||||
|
|
||||||
|
prio is an OR'd logPrio of the Priority* constants.
|
||||||
|
*/
|
||||||
|
func ToLog(l Logger, prio logPrio) (stdLibLog *log.Logger) {
|
||||||
|
|
||||||
|
stdLibLog = l.ToLogger(prio)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToRaw returns a *logWriter from a logging.Logger. It is an alternative to the (logging.Logger).ToRaw() methods.
|
||||||
|
func ToRaw(l Logger, prio logPrio) (raw *logWriter) {
|
||||||
|
|
||||||
|
raw = &logWriter{
|
||||||
|
backend: l,
|
||||||
|
prio: prio,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// testOpen attempts to open a file for writing to test for suitability as a LogFile path.
|
// testOpen attempts to open a file for writing to test for suitability as a LogFile path.
|
||||||
func testOpen(path string) (success bool, err error) {
|
func testOpen(path string) (success bool, err error) {
|
||||||
|
|
||||||
var f *os.File
|
var f *os.File
|
||||||
|
|
||||||
// Example #2, https://golang.org/pkg/os/#OpenFile
|
// Example #2, https://golang.org/pkg/os/#OpenFile
|
||||||
if f, err = os.OpenFile(path, appendFlags, logPerm); err != nil {
|
if f, err = os.OpenFile(path, appendFlags, logPerm); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
if err = f.Close(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
success = true
|
success = true
|
||||||
|
|
||||||
|
|||||||
@@ -1,61 +1,91 @@
|
|||||||
package logging
|
package logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Setup sets up/configures a FileLogger and prepares it for use.
|
// Setup sets up/configures a FileLogger and prepares it for use.
|
||||||
func (l *FileLogger) Setup() {
|
func (l *FileLogger) Setup() (err error) {
|
||||||
|
|
||||||
var err error
|
// This uses a shared handle across the import. We don't want that.
|
||||||
var multi io.Writer
|
// l.Logger = log.Default()
|
||||||
|
if l.Prefix != "" {
|
||||||
l.Logger = log.Default()
|
l.Prefix = strings.TrimRight(l.Prefix, " ") + " "
|
||||||
l.Logger.SetPrefix(l.Prefix + " ")
|
// l.Logger.SetPrefix(l.Prefix)
|
||||||
|
}
|
||||||
if l.writer, err = os.OpenFile(l.Path, appendFlags, logPerm); err != nil {
|
if l.writer, err = os.OpenFile(l.Path, appendFlags, logPerm); err != nil {
|
||||||
log.Panicf("could not open log file \"%v\" for writing: %v\n", l.Path, err)
|
return
|
||||||
}
|
}
|
||||||
// https://stackoverflow.com/a/36719588/733214
|
|
||||||
if l.EnableStdOut {
|
l.Logger = log.New(l.writer, l.Prefix, l.LogFlags)
|
||||||
multi = io.MultiWriter(os.Stdout, l.writer)
|
// l.Logger.SetOutput(multi)
|
||||||
} else {
|
|
||||||
multi = l.writer
|
return
|
||||||
}
|
|
||||||
l.Logger.SetOutput(multi)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown cleanly shuts down a FileLogger.
|
// Shutdown cleanly shuts down a FileLogger.
|
||||||
func (l *FileLogger) Shutdown() {
|
func (l *FileLogger) Shutdown() (err error) {
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = l.writer.Close(); err != nil {
|
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
|
||||||
|
|
||||||
// GetPrefix returns the prefix used by this FileLogger.
|
|
||||||
func (l *FileLogger) GetPrefix() string {
|
|
||||||
return l.Prefix
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
DoDebug sets the debug state of this FileLogger.
|
GetPrefix returns the prefix used by 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) {
|
func (l *FileLogger) GetPrefix() (prefix string, err error) {
|
||||||
l.EnableDebug = d
|
|
||||||
|
prefix = l.Prefix
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPrefix sets the prefix for this FileLogger.
|
/*
|
||||||
func (l *FileLogger) SetPrefix(prefix string) {
|
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.Prefix = prefix + " "
|
l.EnableDebug = d
|
||||||
l.Logger.SetPrefix(prefix + " ")
|
|
||||||
|
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.
|
||||||
|
err will always be nil; it's there for interface-compat.
|
||||||
|
*/
|
||||||
|
func (l *FileLogger) SetPrefix(prefix string) (err error) {
|
||||||
|
|
||||||
|
l.Prefix = prefix
|
||||||
|
if prefix != "" {
|
||||||
|
l.Prefix = strings.TrimRight(l.Prefix, " ") + " "
|
||||||
|
}
|
||||||
|
l.Logger.SetPrefix(l.Prefix)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alert writes an ALERT-level message to this FileLogger.
|
// Alert writes an ALERT-level message to this FileLogger.
|
||||||
@@ -190,6 +220,22 @@ 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(l.ToRaw(prio), "", 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToRaw returns a *logWriter.
|
||||||
|
func (l *FileLogger) ToRaw(prio logPrio) (raw *logWriter) {
|
||||||
|
|
||||||
|
raw = &logWriter{backend: l, prio: prio}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
`os`
|
`os`
|
||||||
`path`
|
`path`
|
||||||
|
|
||||||
sysd `github.com/coreos/go-systemd/journal`
|
sysd `github.com/coreos/go-systemd/v22/journal`
|
||||||
`r00t2.io/goutils/bitmask`
|
`r00t2.io/goutils/bitmask`
|
||||||
`r00t2.io/sysutils/paths`
|
`r00t2.io/sysutils/paths`
|
||||||
)
|
)
|
||||||
@@ -24,6 +24,9 @@ var (
|
|||||||
If prefix is "\x00" (a null byte), then the default logging prefix will be used. If anything else, even an empty string,
|
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.
|
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,
|
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.
|
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 want to log to multiple files simultaneously, use a MultiLogger instead.
|
||||||
@@ -32,10 +35,11 @@ var (
|
|||||||
If you want to log to multiple Logger destinations at once (or want to log to an explicit Logger type),
|
If you want to log to multiple Logger destinations at once (or want to log to an explicit Logger type),
|
||||||
use GetMultiLogger.
|
use GetMultiLogger.
|
||||||
*/
|
*/
|
||||||
func GetLogger(enableDebug bool, prefix string, logPaths ...string) (logger Logger, err error) {
|
func GetLogger(enableDebug bool, prefix string, logConfigFlags int, logPaths ...string) (logger Logger, err error) {
|
||||||
|
|
||||||
var logPath string
|
var logPath string
|
||||||
var logFlags bitmask.MaskBit
|
var logFlags bitmask.MaskBit
|
||||||
|
var currentPrefix string
|
||||||
|
|
||||||
// Configure system-supported logger(s).
|
// Configure system-supported logger(s).
|
||||||
if sysd.Enabled() {
|
if sysd.Enabled() {
|
||||||
@@ -109,6 +113,7 @@ func GetLogger(enableDebug bool, prefix string, logPaths ...string) (logger Logg
|
|||||||
StdLogger: StdLogger{
|
StdLogger: StdLogger{
|
||||||
Prefix: logPrefix,
|
Prefix: logPrefix,
|
||||||
EnableDebug: enableDebug,
|
EnableDebug: enableDebug,
|
||||||
|
LogFlags: logConfigFlags,
|
||||||
},
|
},
|
||||||
Path: logPath,
|
Path: logPath,
|
||||||
}
|
}
|
||||||
@@ -116,17 +121,26 @@ func GetLogger(enableDebug bool, prefix string, logPaths ...string) (logger Logg
|
|||||||
logger = &StdLogger{
|
logger = &StdLogger{
|
||||||
Prefix: logPrefix,
|
Prefix: logPrefix,
|
||||||
EnableDebug: enableDebug,
|
EnableDebug: enableDebug,
|
||||||
|
LogFlags: logConfigFlags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Setup()
|
|
||||||
if prefix != "\x00" {
|
if prefix != "\x00" {
|
||||||
logger.SetPrefix(prefix)
|
if err = logger.SetPrefix(prefix); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = logger.Setup(); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("logger initialized of type %T with prefix %v", logger, logger.GetPrefix())
|
if currentPrefix, err = logger.GetPrefix(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug("logger initialized of type %T with prefix %v", logger, currentPrefix)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
270
logging/funcs_linux_test.go
Normal file
270
logging/funcs_linux_test.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
25
logging/funcs_logprio.go
Normal file
25
logging/funcs_logprio.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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))
|
||||||
|
|
||||||
|
// Use IsOneOf instead in case PriorityAll is passed for prio.
|
||||||
|
// hasFlag = m.HasFlag(*p)
|
||||||
|
hasFlag = m.IsOneOf(*p)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
211
logging/funcs_logwriter.go
Normal file
211
logging/funcs_logwriter.go
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"r00t2.io/goutils/multierr"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Close calls Logger.Shutdown() on the underlying Logger.
|
||||||
|
The Logger *must not be used* after this; it will need to be re-initialized with Logger.Setup()
|
||||||
|
or a new Logger (and thuse new logWriter) must be created to replace it.
|
||||||
|
|
||||||
|
It (along with logWriter.Write()) conforms to WriteCloser().
|
||||||
|
*/
|
||||||
|
func (l *logWriter) Close() (err error) {
|
||||||
|
|
||||||
|
if err = l.backend.Shutdown(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Write writes bytes b to the underlying Logger's priority level if the logWriter's priority level(s) match.
|
||||||
|
It conforms to io.Writer. n will *always* == len(b) on success, because otherwise n would technically be >= len(b)
|
||||||
|
(if multiple priorities are enabled), which is undefined behavior per io.Writer.
|
||||||
|
|
||||||
|
b is converted to a string to normalize to the underlying Logger.
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Since this explicitly checks each priority level, there's no need for IsOneOf in case of PriorityAll.
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
n = len(b)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
WriteByte conforms a logWriter to an io.ByteWriter. (It just wraps logWriter.Write().)
|
||||||
|
You should probably never use this; the logging overhead/prefix is going to be more data than the single byte itself.
|
||||||
|
|
||||||
|
c is converted to a string to normalize to the underlying Logger.
|
||||||
|
*/
|
||||||
|
func (l *logWriter) WriteByte(c byte) (err error) {
|
||||||
|
|
||||||
|
if _, err = l.Write([]byte{c}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
WriteRune follows the same signature of (bytes.Buffer).WriteRune() and (bufio.Writer).WriteRune(); thus if `io` ever defines an io.RuneWriter interface, here ya go.
|
||||||
|
|
||||||
|
n will *always* be equal to (unicode/utf8).RuneLen(r), unless r is an "invalid rune" -- in which case n will be 0 and err will be ErrInvalidRune..
|
||||||
|
*/
|
||||||
|
func (l *logWriter) WriteRune(r rune) (n int, err error) {
|
||||||
|
|
||||||
|
var b []byte
|
||||||
|
|
||||||
|
n = utf8.RuneLen(r)
|
||||||
|
if n < 0 {
|
||||||
|
err = ErrInvalidRune
|
||||||
|
n = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b = make([]byte, n)
|
||||||
|
utf8.EncodeRune(b, r)
|
||||||
|
|
||||||
|
if n, err = l.Write(b); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
WriteString writes string s to the underlying Logger's priority level if the logWriter's priority level(s) match.
|
||||||
|
It conforms to io.StringWriter. n will *always* == len(s) on success, because otherwise n would technically be >= len(s)
|
||||||
|
(if multiple priorities are enabled), which is undefined behavior per io.StringWriter.
|
||||||
|
*/
|
||||||
|
func (l *logWriter) WriteString(s string) (n int, err error) {
|
||||||
|
|
||||||
|
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
n = len(s)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,93 +1,161 @@
|
|||||||
package logging
|
package logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`sync`
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"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.
|
||||||
func (m *MultiLogger) Setup() {
|
func (m *MultiLogger) Setup() (err error) {
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
var errs *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
|
||||||
for _, l := range m.Loggers {
|
for logName, l := range m.Loggers {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func(logger Logger, lName string) {
|
||||||
|
var err2 error
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
l.Setup()
|
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()
|
wg.Wait()
|
||||||
|
|
||||||
|
if errs.Count() > 0 {
|
||||||
|
err = errs
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown cleanly shuts down a MultiLogger (and all its MultiLogger.Loggers).
|
// Shutdown cleanly shuts down a MultiLogger (and all its MultiLogger.Loggers).
|
||||||
func (m *MultiLogger) Shutdown() {
|
func (m *MultiLogger) Shutdown() (err error) {
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
var errs *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
|
||||||
for _, l := range m.Loggers {
|
for logName, l := range m.Loggers {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func(logger Logger, lName string) {
|
||||||
|
var err2 error
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
l.Shutdown()
|
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()
|
wg.Wait()
|
||||||
|
|
||||||
|
if errs.Count() > 0 {
|
||||||
|
err = errs
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPrefix returns the prefix used by this MultiLogger (and all its MultiLogger.Loggers).
|
/*
|
||||||
func (m *MultiLogger) GetPrefix() string {
|
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) {
|
||||||
|
|
||||||
return m.Prefix
|
prefix = m.Prefix
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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) {
|
func (m *MultiLogger) DoDebug(d bool) (err error) {
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
var errs *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
|
||||||
m.EnableDebug = d
|
m.EnableDebug = d
|
||||||
|
|
||||||
for _, l := range m.Loggers {
|
for logName, l := range m.Loggers {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func(logger Logger, lName string) {
|
||||||
|
var err2 error
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
l.DoDebug(d)
|
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()
|
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).
|
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) {
|
func (m *MultiLogger) SetPrefix(prefix string) (err error) {
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
var errs *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
|
||||||
m.Prefix = prefix
|
m.Prefix = prefix
|
||||||
|
|
||||||
for _, l := range m.Loggers {
|
for logName, l := range m.Loggers {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func(logger Logger, lName string) {
|
||||||
|
var err2 error
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
l.SetPrefix(prefix)
|
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()
|
wg.Wait()
|
||||||
|
|
||||||
|
if errs.Count() > 0 {
|
||||||
|
err = errs
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alert writes an ALERT-level message to this MultiLogger (and all its MultiLogger.Loggers).
|
// Alert writes an ALERT-level message to this MultiLogger (and all its MultiLogger.Loggers).
|
||||||
@@ -96,20 +164,16 @@ func (m *MultiLogger) Alert(s string, v ...interface{}) (err error) {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
|
||||||
for _, l := range m.Loggers {
|
for logName, l := range m.Loggers {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(logObj Logger, msg string, rplc ...interface{}) {
|
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if rplc != nil {
|
if err = logObj.Alert(msg, rplc...); err != nil {
|
||||||
err = logObj.Alert(msg, rplc...)
|
e.AddError(errors.New(fmt.Sprintf("error on Alert for logger %v; follows (may be out of order):", lName)))
|
||||||
} else {
|
|
||||||
err = logObj.Alert(msg)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
e.AddError(err)
|
e.AddError(err)
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
}(l, s, v)
|
}(l, s, logName, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -127,20 +191,16 @@ func (m *MultiLogger) Crit(s string, v ...interface{}) (err error) {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
|
||||||
for _, l := range m.Loggers {
|
for logName, l := range m.Loggers {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(logObj Logger, msg string, rplc ...interface{}) {
|
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if rplc != nil {
|
if err = logObj.Crit(msg, rplc...); err != nil {
|
||||||
err = logObj.Crit(msg, rplc...)
|
e.AddError(errors.New(fmt.Sprintf("error on Crit for logger %v; follows (may be out of order):", lName)))
|
||||||
} else {
|
|
||||||
err = logObj.Crit(msg)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
e.AddError(err)
|
e.AddError(err)
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
}(l, s, v)
|
}(l, s, logName, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -158,20 +218,16 @@ func (m *MultiLogger) Debug(s string, v ...interface{}) (err error) {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
|
||||||
for _, l := range m.Loggers {
|
for logName, l := range m.Loggers {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(logObj Logger, msg string, rplc ...interface{}) {
|
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if rplc != nil {
|
if err = logObj.Debug(msg, rplc...); err != nil {
|
||||||
err = logObj.Debug(msg, rplc...)
|
e.AddError(errors.New(fmt.Sprintf("error on Debug for logger %v; follows (may be out of order):", lName)))
|
||||||
} else {
|
|
||||||
err = logObj.Debug(msg)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
e.AddError(err)
|
e.AddError(err)
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
}(l, s, v)
|
}(l, s, logName, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -188,20 +244,16 @@ func (m *MultiLogger) Emerg(s string, v ...interface{}) (err error) {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
|
||||||
for _, l := range m.Loggers {
|
for logName, l := range m.Loggers {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(logObj Logger, msg string, rplc ...interface{}) {
|
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if rplc != nil {
|
if err = logObj.Emerg(msg, rplc...); err != nil {
|
||||||
err = logObj.Emerg(msg, rplc...)
|
e.AddError(errors.New(fmt.Sprintf("error on Emerg for logger %v; follows (may be out of order):", lName)))
|
||||||
} else {
|
|
||||||
err = logObj.Emerg(msg)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
e.AddError(err)
|
e.AddError(err)
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
}(l, s, v)
|
}(l, s, logName, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -218,20 +270,16 @@ func (m *MultiLogger) Err(s string, v ...interface{}) (err error) {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
|
||||||
for _, l := range m.Loggers {
|
for logName, l := range m.Loggers {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(logObj Logger, msg string, rplc ...interface{}) {
|
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if rplc != nil {
|
if err = logObj.Err(msg, rplc...); err != nil {
|
||||||
err = logObj.Err(msg, rplc...)
|
e.AddError(errors.New(fmt.Sprintf("error on Err for logger %v; follows (may be out of order):", lName)))
|
||||||
} else {
|
|
||||||
err = logObj.Err(msg)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
e.AddError(err)
|
e.AddError(err)
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
}(l, s, v)
|
}(l, s, logName, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -249,20 +297,16 @@ func (m *MultiLogger) Info(s string, v ...interface{}) (err error) {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
|
||||||
for _, l := range m.Loggers {
|
for logName, l := range m.Loggers {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(logObj Logger, msg string, rplc ...interface{}) {
|
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if rplc != nil {
|
if err = logObj.Info(msg, rplc...); err != nil {
|
||||||
err = logObj.Info(msg, rplc...)
|
e.AddError(errors.New(fmt.Sprintf("error on Info for logger %v; follows (may be out of order):", lName)))
|
||||||
} else {
|
|
||||||
err = logObj.Info(msg)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
e.AddError(err)
|
e.AddError(err)
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
}(l, s, v)
|
}(l, s, logName, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -280,20 +324,16 @@ func (m *MultiLogger) Notice(s string, v ...interface{}) (err error) {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
|
||||||
for _, l := range m.Loggers {
|
for logName, l := range m.Loggers {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(logObj Logger, msg string, rplc ...interface{}) {
|
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if rplc != nil {
|
if err = logObj.Notice(msg, rplc...); err != nil {
|
||||||
err = logObj.Notice(msg, rplc...)
|
e.AddError(errors.New(fmt.Sprintf("error on Notice for logger %v; follows (may be out of order):", lName)))
|
||||||
} else {
|
|
||||||
err = logObj.Notice(msg)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
e.AddError(err)
|
e.AddError(err)
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
}(l, s, v)
|
}(l, s, logName, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -311,20 +351,16 @@ func (m *MultiLogger) Warning(s string, v ...interface{}) (err error) {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
var e *multierr.MultiError = multierr.NewMultiError(nil)
|
||||||
|
|
||||||
for _, l := range m.Loggers {
|
for logName, l := range m.Loggers {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(logObj Logger, msg string, rplc ...interface{}) {
|
go func(logObj Logger, msg, lName string, rplc ...interface{}) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if rplc != nil {
|
if err = logObj.Warning(msg, rplc...); err != nil {
|
||||||
err = logObj.Warning(msg, rplc...)
|
e.AddError(errors.New(fmt.Sprintf("error on Warning for logger %v; follows (may be out of order):", lName)))
|
||||||
} else {
|
|
||||||
err = logObj.Warning(msg)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
e.AddError(err)
|
e.AddError(err)
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
}(l, s, v)
|
}(l, s, logName, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -335,3 +371,19 @@ 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(m.ToRaw(prio), "", 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToRaw returns a *logWriter.
|
||||||
|
func (m *MultiLogger) ToRaw(prio logPrio) (raw *logWriter) {
|
||||||
|
|
||||||
|
raw = &logWriter{backend: m, prio: prio}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -21,7 +21,7 @@ func GetMultiLogger(enableDebug bool, prefix string) (m *MultiLogger) {
|
|||||||
|
|
||||||
m = &MultiLogger{
|
m = &MultiLogger{
|
||||||
EnableDebug: enableDebug,
|
EnableDebug: enableDebug,
|
||||||
Prefix: "",
|
Prefix: logPrefix,
|
||||||
Loggers: make(map[string]Logger),
|
Loggers: make(map[string]Logger),
|
||||||
}
|
}
|
||||||
if prefix != "\x00" {
|
if prefix != "\x00" {
|
||||||
@@ -36,10 +36,19 @@ func GetMultiLogger(enableDebug bool, prefix string) (m *MultiLogger) {
|
|||||||
|
|
||||||
identifier is a string to use to identify the added StdLogger in MultiLogger.Loggers.
|
identifier is a string to use to identify the added StdLogger in MultiLogger.Loggers.
|
||||||
If empty, one will be automatically generated.
|
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) (err error) {
|
func (m *MultiLogger) AddStdLogger(identifier string, enableStdOut, enableStdErr bool, logFlags int) (err error) {
|
||||||
|
|
||||||
var exists bool
|
var exists bool
|
||||||
|
var prefix string
|
||||||
|
|
||||||
if identifier == "" {
|
if identifier == "" {
|
||||||
identifier = uuid.New().String()
|
identifier = uuid.New().String()
|
||||||
@@ -51,13 +60,22 @@ func (m *MultiLogger) AddStdLogger(identifier string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.Loggers[identifier] = &StdLogger{
|
m.Loggers[identifier] = &StdLogger{
|
||||||
Logger: nil,
|
Logger: nil,
|
||||||
EnableDebug: m.EnableDebug,
|
EnableDebug: m.EnableDebug,
|
||||||
Prefix: m.Prefix,
|
Prefix: m.Prefix,
|
||||||
|
LogFlags: logFlags,
|
||||||
|
EnableStdOut: enableStdOut,
|
||||||
|
EnableStdErr: enableStdErr,
|
||||||
|
}
|
||||||
|
if err = m.Loggers[identifier].Setup(); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
m.Loggers[identifier].Setup()
|
|
||||||
|
|
||||||
m.Loggers[identifier].Info("logger initialized of type %T with prefix %v", m.Loggers[identifier], m.Loggers[identifier].GetPrefix())
|
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
|
return
|
||||||
}
|
}
|
||||||
@@ -70,11 +88,12 @@ func (m *MultiLogger) AddStdLogger(identifier string) (err error) {
|
|||||||
|
|
||||||
logfilePath is a string for the path to the desired logfile.
|
logfilePath is a string for the path to the desired logfile.
|
||||||
*/
|
*/
|
||||||
func (m *MultiLogger) AddFileLogger(identifier, logfilePath string) (err error) {
|
func (m *MultiLogger) AddFileLogger(identifier string, logFlags int, logfilePath string) (err error) {
|
||||||
|
|
||||||
var exists bool
|
var exists bool
|
||||||
var success bool
|
var success bool
|
||||||
var dirPath string
|
var dirPath string
|
||||||
|
var prefix string
|
||||||
|
|
||||||
if identifier == "" {
|
if identifier == "" {
|
||||||
identifier = uuid.New().String()
|
identifier = uuid.New().String()
|
||||||
@@ -85,7 +104,9 @@ func (m *MultiLogger) AddFileLogger(identifier, logfilePath string) (err error)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if exists, _ = paths.RealPathExists(&logfilePath); !exists {
|
if exists, err = paths.RealPathExists(&logfilePath); err != nil {
|
||||||
|
return
|
||||||
|
} else if !exists {
|
||||||
if success, err = testOpen(logfilePath); err != nil {
|
if success, err = testOpen(logfilePath); err != nil {
|
||||||
return
|
return
|
||||||
} else if !success {
|
} else if !success {
|
||||||
@@ -107,12 +128,53 @@ func (m *MultiLogger) AddFileLogger(identifier, logfilePath string) (err error)
|
|||||||
Logger: nil,
|
Logger: nil,
|
||||||
EnableDebug: m.EnableDebug,
|
EnableDebug: m.EnableDebug,
|
||||||
Prefix: m.Prefix,
|
Prefix: m.Prefix,
|
||||||
|
LogFlags: logFlags,
|
||||||
},
|
},
|
||||||
Path: logfilePath,
|
Path: logfilePath,
|
||||||
}
|
}
|
||||||
m.Loggers[identifier].Setup()
|
if err = m.Loggers[identifier].Setup(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
m.Loggers[identifier].Info("logger initialized of type %T with prefix %v", m.Loggers[identifier], m.Loggers[identifier].GetPrefix())
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
package logging
|
package logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`os`
|
sysd "github.com/coreos/go-systemd/v22/journal"
|
||||||
|
"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.
|
||||||
*/
|
*/
|
||||||
func (m *MultiLogger) AddDefaultLogger(identifier string, logPaths ...string) (err error) {
|
func (m *MultiLogger) AddDefaultLogger(identifier string, logFlags int, logPaths ...string) (err error) {
|
||||||
|
|
||||||
var l Logger
|
var l Logger
|
||||||
var exists bool
|
var exists bool
|
||||||
@@ -28,7 +27,7 @@ func (m *MultiLogger) AddDefaultLogger(identifier string, logPaths ...string) (e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if l, err = GetLogger(m.EnableDebug, m.Prefix, logPaths...); err != nil {
|
if l, err = GetLogger(m.EnableDebug, m.Prefix, logFlags, logPaths...); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,14 +37,15 @@ func (m *MultiLogger) AddDefaultLogger(identifier string, logPaths ...string) (e
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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) {
|
||||||
|
|
||||||
var exists bool
|
var exists bool
|
||||||
|
var prefix string
|
||||||
|
|
||||||
if identifier == "" {
|
if identifier == "" {
|
||||||
identifier = uuid.New().String()
|
identifier = uuid.New().String()
|
||||||
@@ -65,54 +65,15 @@ func (m *MultiLogger) AddSysdLogger(identifier string) (err error) {
|
|||||||
EnableDebug: m.EnableDebug,
|
EnableDebug: m.EnableDebug,
|
||||||
Prefix: m.Prefix,
|
Prefix: m.Prefix,
|
||||||
}
|
}
|
||||||
m.Loggers[identifier].Setup()
|
if err = m.Loggers[identifier].Setup(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
m.Loggers[identifier].Info("logger initialized of type %T with prefix %v", m.Loggers[identifier], m.Loggers[identifier].GetPrefix())
|
if prefix, err = m.Loggers[identifier].GetPrefix(); err != nil {
|
||||||
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
m.Loggers[identifier].Debug("logger initialized of type %T with prefix %v", m.Loggers[identifier], prefix)
|
||||||
/*
|
|
||||||
AddSyslogLogger adds a SyslogLogger to a MultiLogger.
|
|
||||||
|
|
||||||
identifier is a string to use to identify the added SyslogLogger in MultiLogger.Loggers.
|
|
||||||
If empty, one will be automatically generated.
|
|
||||||
*/
|
|
||||||
func (m *MultiLogger) AddSyslogLogger(identifier string) (err error) {
|
|
||||||
|
|
||||||
var exists bool
|
|
||||||
var hasSyslog bool
|
|
||||||
var stat os.FileInfo
|
|
||||||
var devlogPath string = devlog
|
|
||||||
|
|
||||||
if identifier == "" {
|
|
||||||
identifier = uuid.New().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, exists = m.Loggers[identifier]; exists {
|
|
||||||
err = ErrExistingLogger
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasSyslog, stat, err = paths.RealPathExistsStat(&devlogPath); hasSyslog && err != nil {
|
|
||||||
return
|
|
||||||
} else if !hasSyslog {
|
|
||||||
err = ErrNoSyslog
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if stat.Mode().IsRegular() {
|
|
||||||
err = ErrInvalidDevLog
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m.Loggers[identifier] = &SyslogLogger{
|
|
||||||
EnableDebug: m.EnableDebug,
|
|
||||||
Prefix: m.Prefix,
|
|
||||||
}
|
|
||||||
m.Loggers[identifier].Setup()
|
|
||||||
|
|
||||||
m.Loggers[identifier].Info("logger initialized of type %T with prefix %v", m.Loggers[identifier], m.Loggers[identifier].GetPrefix())
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
63
logging/funcs_multilogger_mgr_nix.go
Normal file
63
logging/funcs_multilogger_mgr_nix.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
//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
|
||||||
|
}
|
||||||
41
logging/funcs_multilogger_mgr_oldnix.go
Normal file
41
logging/funcs_multilogger_mgr_oldnix.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
//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
|
||||||
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
package logging
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
`github.com/google/uuid`
|
||||||
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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.
|
||||||
|
|
||||||
@@ -12,10 +16,12 @@ package logging
|
|||||||
logPaths is an (optional) list of strings to use as paths to test for writing. If the file can be created/written to,
|
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).
|
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.
|
Only the first logPaths entry that "works" will be used, later entries will be ignored.
|
||||||
Currently this will almost always return a WinLogger.
|
Currently this will almost always return a WinLogger.
|
||||||
*/
|
*/
|
||||||
func (m *MultiLogger) AddDefaultLogger(identifier string, eventIDs *WinEventID) (err error) {
|
func (m *MultiLogger) AddDefaultLogger(identifier string, eventIDs *WinEventID, logFlags int, logPaths ...string) (err error) {
|
||||||
|
|
||||||
var l Logger
|
var l Logger
|
||||||
var exists bool
|
var exists bool
|
||||||
@@ -29,7 +35,12 @@ func (m *MultiLogger) AddDefaultLogger(identifier string, eventIDs *WinEventID)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if l, err = GetLogger(m.EnableDebug, m.Prefix, eventIDs, logPaths...); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,9 +65,10 @@ func (m *MultiLogger) AddDefaultLogger(identifier string, eventIDs *WinEventID)
|
|||||||
|
|
||||||
See GetLogger for details.
|
See GetLogger for details.
|
||||||
*/
|
*/
|
||||||
func (m *MultiLogger) AddWinLogger(identifier, source string, eventIDs *WinEventID) (err error) {
|
func (m *MultiLogger) AddWinLogger(identifier string, eventIDs *WinEventID) (err error) {
|
||||||
|
|
||||||
var exists bool
|
var exists bool
|
||||||
|
var prefix string
|
||||||
|
|
||||||
if identifier == "" {
|
if identifier == "" {
|
||||||
identifier = uuid.New().String()
|
identifier = uuid.New().String()
|
||||||
@@ -72,13 +84,19 @@ func (m *MultiLogger) AddWinLogger(identifier, source string, eventIDs *WinEvent
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.Loggers[identifier] = &WinLogger{
|
m.Loggers[identifier] = &WinLogger{
|
||||||
Prefix: source,
|
Prefix: m.Prefix,
|
||||||
EnableDebug: m.EnableDebug,
|
EnableDebug: m.EnableDebug,
|
||||||
eids: eventIDs,
|
EIDs: eventIDs,
|
||||||
|
}
|
||||||
|
if err = m.Loggers[identifier].Setup(); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
m.Loggers[identifier].Setup()
|
|
||||||
|
|
||||||
m.Loggers[identifier].Info("logger initialized of type %T with prefix %v", m.Loggers[identifier], m.Loggers[identifier].GetPrefix())
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
94
logging/funcs_nulllogger.go
Normal file
94
logging/funcs_nulllogger.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToRaw returns a *logWriter. (This is a little less efficient than using ToLogger's log.Logger as an io.Writer if that's all you need.)
|
||||||
|
func (l *NullLogger) ToRaw(prio logPrio) (raw *logWriter) {
|
||||||
|
|
||||||
|
raw = &logWriter{backend: l, prio: prio}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
58
logging/funcs_nullwriter.go
Normal file
58
logging/funcs_nullwriter.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Close conforms a nullWriter to an io.WriteCloser. It obviously does nothing, and will always return with err == nil.
|
||||||
|
func (nw *nullWriter) Close() (err error) {
|
||||||
|
|
||||||
|
// NO-OP
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write conforms a nullWriter to an io.Writer, but it 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteByte conforms to an io.ByteWriter but again... nothing is actually written anywhere.
|
||||||
|
func (nw *nullWriter) WriteByte(c byte) (err error) {
|
||||||
|
|
||||||
|
// NO-OP
|
||||||
|
|
||||||
|
_ = c
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
WriteRune conforms to the other Loggers. It WILL return the proper value for n (matching (bytes.Buffer).WriteRune() and (bufio.Writer).WriteRune() signatures,
|
||||||
|
and it WILL return an ErrInvalidRune if r is not a valid rune, but otherwise it will no-op.
|
||||||
|
*/
|
||||||
|
func (nw *nullWriter) WriteRune(r rune) (n int, err error) {
|
||||||
|
|
||||||
|
n = utf8.RuneLen(r)
|
||||||
|
if n < 0 {
|
||||||
|
err = ErrInvalidRune
|
||||||
|
n = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString conforms to an io.StringWriter but nothing is actually written. (n will *always* == len(s))
|
||||||
|
func (nw *nullWriter) WriteString(s string) (n int, err error) {
|
||||||
|
|
||||||
|
n = len(s)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
138
logging/funcs_oldnix.go
Normal file
138
logging/funcs_oldnix.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
//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
|
||||||
|
}
|
||||||
@@ -2,26 +2,66 @@ package logging
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Setup sets up/configures a StdLogger and prepares it for use.
|
/*
|
||||||
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()
|
var multi io.Writer
|
||||||
l.Logger.SetPrefix(l.Prefix + " ")
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown cleanly shuts down a StdLogger.
|
/*
|
||||||
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
|
// NOOP
|
||||||
_ = ""
|
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPrefix returns the prefix used by this StdLogger.
|
/*
|
||||||
func (l *StdLogger) GetPrefix() (prefix string) {
|
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
|
prefix = l.Prefix
|
||||||
|
|
||||||
@@ -29,17 +69,38 @@ func (l *StdLogger) GetPrefix() (prefix string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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.
|
||||||
*/
|
*/
|
||||||
func (l *StdLogger) DoDebug(d bool) {
|
func (l *StdLogger) DoDebug(d bool) (err error) {
|
||||||
|
|
||||||
l.EnableDebug = d
|
l.EnableDebug = d
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPrefix sets the prefix for this StdLogger.
|
// GetDebug returns the debug status of this StdLogger.
|
||||||
func (l *StdLogger) SetPrefix(prefix string) {
|
func (l *StdLogger) GetDebug() (d bool) {
|
||||||
l.Prefix = prefix + " "
|
|
||||||
l.Logger.SetPrefix(prefix + " ")
|
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
|
||||||
|
if prefix != "" {
|
||||||
|
l.Prefix = strings.TrimRight(l.Prefix, " ") + " "
|
||||||
|
}
|
||||||
|
l.Logger.SetPrefix(l.Prefix)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alert writes an ALERT-level message to this StdLogger.
|
// Alert writes an ALERT-level message to this StdLogger.
|
||||||
@@ -174,6 +235,22 @@ func (l *StdLogger) Warning(s string, v ...interface{}) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToLogger returns a stdlib log.Logger.
|
||||||
|
func (l *StdLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
|
||||||
|
|
||||||
|
stdLibLog = log.New(l.ToRaw(prio), "", 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToRaw returns a *logWriter.
|
||||||
|
func (l *StdLogger) ToRaw(prio logPrio) (raw *logWriter) {
|
||||||
|
|
||||||
|
raw = &logWriter{backend: l, prio: prio}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// renderWrite prepares/formats a log message to be written to this StdLogger.
|
// renderWrite prepares/formats a log message to be written to this StdLogger.
|
||||||
func (l *StdLogger) renderWrite(msg, prio string) {
|
func (l *StdLogger) renderWrite(msg, prio string) {
|
||||||
|
|
||||||
|
|||||||
@@ -4,27 +4,36 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/coreos/go-systemd/journal"
|
"github.com/coreos/go-systemd/v22/journal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Setup sets up/configures a SystemDLogger and prepares it for use.
|
/*
|
||||||
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
|
// NOOP
|
||||||
_ = ""
|
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown cleanly shuts down a SystemDLogger.
|
/*
|
||||||
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
|
// NOOP
|
||||||
_ = ""
|
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPrefix returns the prefix used by this SystemDLogger.
|
/*
|
||||||
func (l *SystemDLogger) GetPrefix() (prefix string) {
|
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
|
prefix = l.Prefix
|
||||||
|
|
||||||
@@ -32,16 +41,34 @@ func (l *SystemDLogger) GetPrefix() (prefix string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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.
|
||||||
*/
|
*/
|
||||||
func (l *SystemDLogger) DoDebug(d bool) {
|
func (l *SystemDLogger) DoDebug(d bool) (err error) {
|
||||||
|
|
||||||
l.EnableDebug = d
|
l.EnableDebug = d
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPrefix sets the prefix for this SystemDLogger.
|
// GetDebug returns the debug status of this SystemDLogger.
|
||||||
func (l *SystemDLogger) SetPrefix(prefix string) {
|
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
|
l.Prefix = prefix
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alert writes an ALERT-level message to this SystemDLogger.
|
// Alert writes an ALERT-level message to this SystemDLogger.
|
||||||
@@ -196,3 +223,19 @@ 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(l.ToRaw(prio), "", 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToRaw returns a *logWriter.
|
||||||
|
func (l *SystemDLogger) ToRaw(prio logPrio) (raw *logWriter) {
|
||||||
|
|
||||||
|
raw = &logWriter{backend: l, prio: prio}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,58 +1,85 @@
|
|||||||
|
//go:build !(windows || plan9 || wasip1 || js || ios)
|
||||||
|
// +build !windows,!plan9,!wasip1,!js,!ios
|
||||||
|
|
||||||
package logging
|
package logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
|
|
||||||
|
"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.
|
||||||
func (l *SyslogLogger) Setup() {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown cleanly shuts down a SyslogLogger.
|
// Shutdown cleanly shuts down a SyslogLogger.
|
||||||
func (l *SyslogLogger) Shutdown() {
|
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} {
|
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 {
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPrefix returns the prefix used by this SyslogLogger.
|
/*
|
||||||
func (l *SyslogLogger) GetPrefix() (prefix string) {
|
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
|
prefix = l.Prefix
|
||||||
|
|
||||||
@@ -60,17 +87,40 @@ func (l *SyslogLogger) GetPrefix() (prefix string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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.
|
||||||
*/
|
*/
|
||||||
func (l *SyslogLogger) DoDebug(d bool) {
|
func (l *SyslogLogger) DoDebug(d bool) (err error) {
|
||||||
|
|
||||||
l.EnableDebug = d
|
l.EnableDebug = d
|
||||||
|
|
||||||
|
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) {
|
func (l *SyslogLogger) SetPrefix(prefix string) (err error) {
|
||||||
|
|
||||||
l.Prefix = prefix
|
l.Prefix = prefix
|
||||||
l.Setup()
|
|
||||||
|
// 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.
|
// Alert writes an ALERT-level message to this SyslogLogger.
|
||||||
@@ -219,3 +269,19 @@ 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(l.ToRaw(prio), "", 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToRaw returns a *logWriter.
|
||||||
|
func (l *SyslogLogger) ToRaw(prio logPrio) (raw *logWriter) {
|
||||||
|
|
||||||
|
raw = &logWriter{backend: l, prio: prio}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
196
logging/funcs_test.go
Normal file
196
logging/funcs_test.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
@@ -2,7 +2,11 @@ package logging
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
`errors`
|
`errors`
|
||||||
|
`path`
|
||||||
`strings`
|
`strings`
|
||||||
|
|
||||||
|
`r00t2.io/goutils/bitmask`
|
||||||
|
`r00t2.io/sysutils/paths`
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -18,6 +22,9 @@ import (
|
|||||||
A pointer to a WinEventID struct may be specified for eventIDs to map extended logging levels (as Windows only supports three levels natively).
|
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.
|
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,
|
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).
|
it will be used (assuming you have no higher-level loggers available).
|
||||||
|
|
||||||
@@ -28,13 +35,14 @@ import (
|
|||||||
If you want to log to multiple Logger destinations at once (or want to log to an explicit Logger type),
|
If you want to log to multiple Logger destinations at once (or want to log to an explicit Logger type),
|
||||||
use GetMultiLogger.
|
use GetMultiLogger.
|
||||||
*/
|
*/
|
||||||
func GetLogger(enableDebug bool, source string, eventIDs *WinEventID, logPaths ...string) (logger Logger, err error) {
|
func GetLogger(enableDebug bool, source string, eventIDs *WinEventID, logConfigFlags int, logPaths ...string) (logger Logger, err error) {
|
||||||
|
|
||||||
var logPath string
|
var logPath string
|
||||||
var logFlags bitmask.MaskBit
|
var logFlags bitmask.MaskBit
|
||||||
var exists bool
|
var exists bool
|
||||||
var success bool
|
var success bool
|
||||||
var ckLogPaths []string
|
var ckLogPaths []string
|
||||||
|
var prefix string
|
||||||
|
|
||||||
if strings.TrimSpace(source) == "" {
|
if strings.TrimSpace(source) == "" {
|
||||||
err = errors.New("invalid source for Windows logging")
|
err = errors.New("invalid source for Windows logging")
|
||||||
@@ -82,7 +90,7 @@ func GetLogger(enableDebug bool, source string, eventIDs *WinEventID, logPaths .
|
|||||||
logger = &WinLogger{
|
logger = &WinLogger{
|
||||||
Prefix: source,
|
Prefix: source,
|
||||||
EnableDebug: enableDebug,
|
EnableDebug: enableDebug,
|
||||||
eids: eventIDs,
|
EIDs: eventIDs,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if logFlags.HasFlag(LogFile) {
|
if logFlags.HasFlag(LogFile) {
|
||||||
@@ -90,6 +98,7 @@ func GetLogger(enableDebug bool, source string, eventIDs *WinEventID, logPaths .
|
|||||||
StdLogger: StdLogger{
|
StdLogger: StdLogger{
|
||||||
Prefix: source,
|
Prefix: source,
|
||||||
EnableDebug: enableDebug,
|
EnableDebug: enableDebug,
|
||||||
|
LogFlags: logConfigFlags,
|
||||||
},
|
},
|
||||||
Path: logPath,
|
Path: logPath,
|
||||||
}
|
}
|
||||||
@@ -97,13 +106,25 @@ func GetLogger(enableDebug bool, source string, eventIDs *WinEventID, logPaths .
|
|||||||
logger = &StdLogger{
|
logger = &StdLogger{
|
||||||
Prefix: source,
|
Prefix: source,
|
||||||
EnableDebug: enableDebug,
|
EnableDebug: enableDebug,
|
||||||
|
LogFlags: logConfigFlags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Setup()
|
if err = logger.Setup(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if source != "\x00" {
|
||||||
|
if err = logger.SetPrefix(source); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.Info("logger initialized of type %T with source %v", logger, logger.GetPrefix())
|
if prefix, err = logger.GetPrefix(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug("logger initialized of type %T with source %v", logger, prefix)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
205
logging/funcs_windows_test.go
Normal file
205
logging/funcs_windows_test.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
@@ -1,69 +1,138 @@
|
|||||||
package logging
|
package logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`errors`
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
`golang.org/x/sys/windows/svc/eventlog`
|
"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.
|
/*
|
||||||
func (l *WinLogger) Setup() {
|
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.
|
||||||
|
|
||||||
var err error
|
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) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
First a sanity check on the EventIDs.
|
A sanity check on the EventIDs.
|
||||||
Since we use eventcreate, all Event IDs must be 1 <= eid <= 1000.
|
Since we use eventcreate, all Event IDs must be 1 <= eid <= 1000.
|
||||||
*/
|
*/
|
||||||
for _, eid := range []uint32{
|
for _, eid := range []uint32{
|
||||||
l.eids.Alert,
|
l.EIDs.Alert,
|
||||||
l.eids.Crit,
|
l.EIDs.Crit,
|
||||||
l.eids.Debug,
|
l.EIDs.Debug,
|
||||||
l.eids.Emerg,
|
l.EIDs.Emerg,
|
||||||
l.eids.Err,
|
l.EIDs.Err,
|
||||||
l.eids.Info,
|
l.EIDs.Info,
|
||||||
l.eids.Notice,
|
l.EIDs.Notice,
|
||||||
l.eids.Warning,
|
l.EIDs.Warning,
|
||||||
} {
|
} {
|
||||||
if !(1 <= eid <= 1000) {
|
if !((eid <= EIDMax) && (EIDMin <= eid)) {
|
||||||
err = errors.New("event IDs must be between 1 and 1000 inclusive")
|
err = ErrBadEid
|
||||||
panic(err)
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = eventlog.InstallAsEventCreate(l.Prefix, eventlog.Error|eventlog.Warning|eventlog.Info); err != nil {
|
if err = l.Install(); err != nil {
|
||||||
if idx := ptrnSourceExists.FindStringIndex(err.Error()); idx == nil {
|
return
|
||||||
// It's an error we want to panic on.
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
// It already exists, so ignore the error.
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.elog, err = eventlog.Open(l.Prefix); err != nil {
|
if l.elog, err = eventlog.Open(l.Prefix); err != nil {
|
||||||
panic(err)
|
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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown cleanly shuts down a WinLogger.
|
// Remove uninstalls a registered WinLogger source.
|
||||||
func (l *WinLogger) Shutdown() {
|
func (l *WinLogger) Remove() (err error) {
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = l.elog.Close(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = eventlog.Remove(l.Prefix); err != nil {
|
if err = eventlog.Remove(l.Prefix); err != nil {
|
||||||
panic(err)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPrefix returns the prefix used by this WinLogger.
|
/*
|
||||||
func (l *WinLogger) GetPrefix() (prefix string) {
|
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
|
prefix = l.Prefix
|
||||||
|
|
||||||
@@ -71,45 +140,65 @@ func (l *WinLogger) GetPrefix() (prefix string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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.
|
||||||
*/
|
*/
|
||||||
func (l *WinLogger) DoDebug(d bool) {
|
func (l *WinLogger) DoDebug(d bool) (err error) {
|
||||||
|
|
||||||
l.EnableDebug = d
|
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.
|
// SetPrefix sets the prefix for this WinLogger.
|
||||||
func (l *WinLogger) SetPrefix(prefix string) {
|
func (l *WinLogger) SetPrefix(prefix string) (err error) {
|
||||||
|
|
||||||
var 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
|
l.Prefix = prefix
|
||||||
|
|
||||||
// To properly change the prefix, we need to tear down the old event log and create a new one.
|
if err = l.Setup(); err != nil {
|
||||||
if err = l.elog.Close(); err != nil {
|
return
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = eventlog.Remove(l.Prefix); err != nil {
|
return
|
||||||
panic(err)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if err = eventlog.InstallAsEventCreate(l.Prefix, eventlog.Error|eventlog.Warning|eventlog.Info); err != nil {
|
// Exists indicates if the WinLogger.Prefix is a registered source or not.
|
||||||
if idx := ptrnSourceExists.FindStringIndex(err.Error()); idx == nil {
|
func (l *WinLogger) Exists() (e bool, err error) {
|
||||||
// It's an error we want to panic on.
|
|
||||||
panic(err)
|
var regKey registry.Key
|
||||||
} else {
|
var subKey registry.Key
|
||||||
// It already exists, so ignore the error.
|
|
||||||
|
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
|
err = nil
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
defer subKey.Close()
|
||||||
|
|
||||||
if l.elog, err = eventlog.Open(l.Prefix); err != nil {
|
e = true
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alert writes an ALERT-level message to this WinLogger.
|
// Alert writes an ALERT-level message to this WinLogger.
|
||||||
@@ -124,7 +213,7 @@ func (l *WinLogger) Alert(s string, v ...interface{}) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Treat ALERT as Warning
|
// Treat ALERT as Warning
|
||||||
err = l.elog.Warning(l.eids.Alert, msg)
|
err = l.elog.Warning(l.EIDs.Alert, msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -141,7 +230,7 @@ func (l *WinLogger) Crit(s string, v ...interface{}) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Treat CRIT as Error
|
// Treat CRIT as Error
|
||||||
err = l.elog.Error(l.eids.Crit, msg)
|
err = l.elog.Error(l.EIDs.Crit, msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -162,7 +251,7 @@ func (l *WinLogger) Debug(s string, v ...interface{}) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Treat DEBUG as Info
|
// Treat DEBUG as Info
|
||||||
err = l.elog.Info(l.eids.Debug, msg)
|
err = l.elog.Info(l.EIDs.Debug, msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -180,7 +269,7 @@ func (l *WinLogger) Emerg(s string, v ...interface{}) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Treat EMERG as Error
|
// Treat EMERG as Error
|
||||||
err = l.elog.Error(l.eids.Emerg, msg)
|
err = l.elog.Error(l.EIDs.Emerg, msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -197,7 +286,7 @@ func (l *WinLogger) Err(s string, v ...interface{}) (err error) {
|
|||||||
msg = s
|
msg = s
|
||||||
}
|
}
|
||||||
|
|
||||||
err = l.elog.Error(l.eids.Error, msg)
|
err = l.elog.Error(l.EIDs.Err, msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -214,7 +303,7 @@ func (l *WinLogger) Info(s string, v ...interface{}) (err error) {
|
|||||||
msg = s
|
msg = s
|
||||||
}
|
}
|
||||||
|
|
||||||
err = l.elog.Info(l.eids.Info, msg)
|
err = l.elog.Info(l.EIDs.Info, msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -232,7 +321,7 @@ func (l *WinLogger) Notice(s string, v ...interface{}) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Treat NOTICE as Info
|
// Treat NOTICE as Info
|
||||||
err = l.elog.Info(l.eids.Notice, msg)
|
err = l.elog.Info(l.EIDs.Notice, msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -249,8 +338,24 @@ func (l *WinLogger) Warning(s string, v ...interface{}) (err error) {
|
|||||||
msg = s
|
msg = s
|
||||||
}
|
}
|
||||||
|
|
||||||
err = l.elog.Warning(l.eids.Warning, msg)
|
err = l.elog.Warning(l.EIDs.Warning, msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToLogger returns a stdlib log.Logger.
|
||||||
|
func (l *WinLogger) ToLogger(prio logPrio) (stdLibLog *log.Logger) {
|
||||||
|
|
||||||
|
stdLibLog = log.New(l.ToRaw(prio), "", 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToRaw returns a *logWriter.
|
||||||
|
func (l *WinLogger) ToRaw(prio logPrio) (raw *logWriter) {
|
||||||
|
|
||||||
|
raw = &logWriter{backend: l, prio: prio}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
121
logging/muiltilogger_linux_test.go
Normal file
121
logging/muiltilogger_linux_test.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
118
logging/multilogger_windows_test.go
Normal file
118
logging/multilogger_windows_test.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
@@ -2,11 +2,15 @@ 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)
|
||||||
@@ -17,16 +21,19 @@ type Logger interface {
|
|||||||
Info(s string, v ...interface{}) (err error)
|
Info(s string, v ...interface{}) (err error)
|
||||||
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)
|
DoDebug(d bool) (err error)
|
||||||
SetPrefix(p string)
|
GetDebug() (d bool)
|
||||||
GetPrefix() (p string)
|
SetPrefix(p string) (err error)
|
||||||
Setup()
|
GetPrefix() (p string, err error)
|
||||||
Shutdown()
|
Setup() (err error)
|
||||||
|
Shutdown() (err error)
|
||||||
|
ToLogger(prio logPrio) (stdLibLog *log.Logger)
|
||||||
|
ToRaw(prio logPrio) (raw *logWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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.
|
||||||
@@ -38,24 +45,57 @@ type StdLogger struct {
|
|||||||
EnableDebug bool
|
EnableDebug bool
|
||||||
// Prefix indicates the prefix for log entries; in shared logs, this helps differentiate the source.
|
// Prefix indicates the prefix for log entries; in shared logs, this helps differentiate the source.
|
||||||
Prefix string
|
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.
|
/*
|
||||||
|
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 {
|
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.
|
||||||
StdLogger
|
StdLogger
|
||||||
// Path is the path to the logfile.
|
// Path is the path to the logfile.
|
||||||
Path string
|
Path string
|
||||||
/*
|
|
||||||
EnableStdOut is true if the log will send to STDOUT as well as the file.
|
|
||||||
If false (default), it will only (silently) write to the log file.
|
|
||||||
You will need to run *FileLogger.Shutdown and then *FileLogger.Setup again if you wish to change this.
|
|
||||||
*/
|
|
||||||
EnableStdOut bool
|
|
||||||
// writer is used for the writing out of the log file.
|
// writer is used for the writing out of the log file.
|
||||||
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 {
|
||||||
/*
|
/*
|
||||||
@@ -71,3 +111,12 @@ 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{}
|
||||||
|
|||||||
@@ -1,23 +1,9 @@
|
|||||||
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.
|
||||||
)
|
*/
|
||||||
|
|
||||||
type SystemDLogger struct {
|
type SystemDLogger struct {
|
||||||
EnableDebug bool
|
EnableDebug bool
|
||||||
Prefix string
|
Prefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
type SyslogLogger struct {
|
|
||||||
EnableDebug bool
|
|
||||||
Prefix string
|
|
||||||
alert,
|
|
||||||
crit,
|
|
||||||
debug,
|
|
||||||
emerg,
|
|
||||||
err,
|
|
||||||
info,
|
|
||||||
notice,
|
|
||||||
warning *syslog.Writer
|
|
||||||
}
|
|
||||||
|
|||||||
22
logging/types_nix.go
Normal file
22
logging/types_nix.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//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
|
||||||
|
}
|
||||||
@@ -4,13 +4,60 @@ import (
|
|||||||
`golang.org/x/sys/windows/svc/eventlog`
|
`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 {
|
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
|
EnableDebug bool
|
||||||
Prefix string
|
/*
|
||||||
elog *eventlog.Log
|
Prefix is used as the Event Log "Source". It's named as Prefix to retain compatability with methods in the Logger interface.
|
||||||
eids *WinEventID
|
*/
|
||||||
|
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 {
|
type WinEventID struct {
|
||||||
Alert,
|
Alert,
|
||||||
Crit,
|
Crit,
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ 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)
|
||||||
@@ -87,6 +90,9 @@ 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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
4
netx/docs.go
Normal file
4
netx/docs.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
Package netx includes extensions to the stdlib `net` module.
|
||||||
|
*/
|
||||||
|
package netx
|
||||||
24
netx/inetcksum/consts.go
Normal file
24
netx/inetcksum/consts.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package inetcksum
|
||||||
|
|
||||||
|
import (
|
||||||
|
`encoding/binary`
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// EmptyCksum is returned for checksums of 0-length byte slices/buffers.
|
||||||
|
EmptyCksum uint16 = 0xffff
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// cksumMask is AND'd with a checksum to get the "carried ones".
|
||||||
|
cksumMask uint32 = 0x0000ffff
|
||||||
|
// cksumShift is used in the "carried-ones folding".
|
||||||
|
cksumShift uint32 = 0x00000010
|
||||||
|
// padShift is used to "pad out" a checksum for odd-length buffers by left-shifting.
|
||||||
|
padShift uint32 = 0x00000008
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ord is the byte order used by the Internet Checksum.
|
||||||
|
ord binary.ByteOrder = binary.BigEndian
|
||||||
|
)
|
||||||
32
netx/inetcksum/docs.go
Normal file
32
netx/inetcksum/docs.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
Package inetcksum applies the "Internet Checksum" algorithm as specified/described in:
|
||||||
|
|
||||||
|
* [RFC 1071]
|
||||||
|
* [RFC 1141]
|
||||||
|
* [RFC 1624]
|
||||||
|
|
||||||
|
It provides [InetChecksum], which can be used as a:
|
||||||
|
|
||||||
|
* [hash.Hash]
|
||||||
|
* [io.ByteWriter]
|
||||||
|
* [io.StringWriter]
|
||||||
|
* [io.Writer]
|
||||||
|
* [io.WriterTo]
|
||||||
|
|
||||||
|
and allows one to retrieve the actual bytes that were checksummed.
|
||||||
|
It is also fully concurrency-safe.
|
||||||
|
|
||||||
|
There is also an [InetChecksumSimple] provided, which is more
|
||||||
|
tailored for performance/resource usage at the cost of no concurrency
|
||||||
|
safety and no data retention, which can be used as a:
|
||||||
|
|
||||||
|
* [hash.Hash]
|
||||||
|
* [io.ByteWriter]
|
||||||
|
* [io.StringWriter]
|
||||||
|
* [io.Writer]
|
||||||
|
|
||||||
|
[RFC 1071]: https://datatracker.ietf.org/doc/html/rfc1071
|
||||||
|
[RFC 1141]: https://datatracker.ietf.org/doc/html/rfc1141
|
||||||
|
[RFC 1624]: https://datatracker.ietf.org/doc/html/rfc1624
|
||||||
|
*/
|
||||||
|
package inetcksum
|
||||||
62
netx/inetcksum/funcs.go
Normal file
62
netx/inetcksum/funcs.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package inetcksum
|
||||||
|
|
||||||
|
import (
|
||||||
|
`io`
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns a new initialized [InetChecksum]. It will never panic.
|
||||||
|
func New() (i *InetChecksum) {
|
||||||
|
|
||||||
|
i = &InetChecksum{}
|
||||||
|
_ = i.Aligned()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFromBytes returns a new [InetChecksum] initialized with explicit bytes.
|
||||||
|
|
||||||
|
b may be nil or 0-length; this will not cause an error.
|
||||||
|
*/
|
||||||
|
func NewFromBytes(b []byte) (i *InetChecksum, copied int, err error) {
|
||||||
|
|
||||||
|
var cksum InetChecksum
|
||||||
|
|
||||||
|
if b != nil && len(b) > 0 {
|
||||||
|
if copied, err = cksum.Write(b); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = i.Aligned()
|
||||||
|
} else {
|
||||||
|
i = New()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i = &cksum
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFromBuf returns an [InetChecksum] from a specified [io.Reader].
|
||||||
|
|
||||||
|
buf may be nil. If it isn't, NewFromBuf will call [io.Copy] on buf.
|
||||||
|
Note that this may exhaust your passed buf or advance its current seek position/offset,
|
||||||
|
depending on its type.
|
||||||
|
*/
|
||||||
|
func NewFromBuf(buf io.Reader) (i *InetChecksum, copied int64, err error) {
|
||||||
|
|
||||||
|
var cksum InetChecksum
|
||||||
|
|
||||||
|
_ = i.Aligned()
|
||||||
|
|
||||||
|
if buf != nil {
|
||||||
|
if copied, err = io.Copy(&cksum, buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i = &cksum
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
351
netx/inetcksum/funcs_inetchecksum.go
Normal file
351
netx/inetcksum/funcs_inetchecksum.go
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
package inetcksum
|
||||||
|
|
||||||
|
import (
|
||||||
|
`io`
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Aligned returns true if the current underlying buffer in an InetChecksum is
|
||||||
|
aligned to the algorithm's requirement for an even number of bytes.
|
||||||
|
|
||||||
|
Note that if Aligned returns false, a single null pad byte will be applied
|
||||||
|
to the underlying data buffer at time of a Sum* call, but will not be written
|
||||||
|
to the persistent underlying storage.
|
||||||
|
|
||||||
|
If aligned's underlying buffer/storage is empty or nil, aligned will be true.
|
||||||
|
|
||||||
|
Aligned will also force-set the internal state's aligned status.
|
||||||
|
*/
|
||||||
|
func (i *InetChecksum) Aligned() (aligned bool) {
|
||||||
|
|
||||||
|
i.alignLock.Lock()
|
||||||
|
defer i.alignLock.Unlock()
|
||||||
|
|
||||||
|
i.bufLock.RLock()
|
||||||
|
aligned = i.buf.Len()&2 == 0
|
||||||
|
i.bufLock.RUnlock()
|
||||||
|
|
||||||
|
i.aligned = aligned
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockSize returns the number of bytes at a time that InetChecksum operates on. (It will always return 1.)
|
||||||
|
func (i *InetChecksum) BlockSize() (blockSize int) {
|
||||||
|
|
||||||
|
blockSize = 1
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Bytes returns teh bytes currently in the internal storage.
|
||||||
|
|
||||||
|
curBuf will be nil if the internal storage has not yet been initialized.
|
||||||
|
*/
|
||||||
|
func (i *InetChecksum) Bytes() (curBuf []byte) {
|
||||||
|
|
||||||
|
i.bufLock.RLock()
|
||||||
|
defer i.bufLock.RUnlock()
|
||||||
|
|
||||||
|
if i.buf.Len() != 0 {
|
||||||
|
curBuf = i.buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear empties the internal buffer (but does not affect the checksum state).
|
||||||
|
func (i *InetChecksum) Clear() {
|
||||||
|
|
||||||
|
i.bufLock.Lock()
|
||||||
|
defer i.bufLock.Unlock()
|
||||||
|
|
||||||
|
i.buf.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
DisablePersist disables the internal persistence of an InetChecksum.
|
||||||
|
|
||||||
|
This is recommended for integrations that desire the concurrency safety
|
||||||
|
of an InetChecksum but want a smaller memory footprint and do not need a copy
|
||||||
|
of data that was hashed.
|
||||||
|
|
||||||
|
Any data existing in the buffer will NOT be cleared out if DisablePersist is called.
|
||||||
|
You must call [InetChecksum.Clear] to do that.
|
||||||
|
|
||||||
|
Persistence CANNOT be reenabled once disabled. [InetChecksum.Reset]
|
||||||
|
must be called to re-enable persistence.
|
||||||
|
*/
|
||||||
|
func (i *InetChecksum) DisablePersist() {
|
||||||
|
|
||||||
|
i.bufLock.Lock()
|
||||||
|
defer i.bufLock.Unlock()
|
||||||
|
|
||||||
|
i.disabledBuf = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the current amount of bytes stored in this InetChecksum's internal buffer.
|
||||||
|
func (i *InetChecksum) Len() (l int) {
|
||||||
|
|
||||||
|
i.bufLock.RLock()
|
||||||
|
defer i.bufLock.RUnlock()
|
||||||
|
l = i.buf.Len()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset resets the internal buffer/storage to an empty state.
|
||||||
|
|
||||||
|
If persistence was disabled ([InetChecksum.DisablePersist]),
|
||||||
|
this method will re-enable it with an empty buffer.
|
||||||
|
If you wish the buffer to be disabled, you must invoke [InetChecksum.DisablePersist]
|
||||||
|
again.
|
||||||
|
|
||||||
|
If you only wish to clear the buffer without losing the checksum state,
|
||||||
|
use [InetChecksum.Clear].
|
||||||
|
*/
|
||||||
|
func (i *InetChecksum) Reset() {
|
||||||
|
|
||||||
|
i.alignLock.Lock()
|
||||||
|
i.bufLock.Lock()
|
||||||
|
i.sumLock.Lock()
|
||||||
|
i.lastLock.Lock()
|
||||||
|
|
||||||
|
i.aligned = false
|
||||||
|
i.alignLock.Unlock()
|
||||||
|
|
||||||
|
i.buf.Reset()
|
||||||
|
i.disabledBuf = false
|
||||||
|
i.bufLock.Unlock()
|
||||||
|
|
||||||
|
i.last = 0x00
|
||||||
|
i.lastLock.Unlock()
|
||||||
|
|
||||||
|
i.sum = 0
|
||||||
|
i.sumLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns how many bytes a checksum is. (It will always return 2.)
|
||||||
|
func (i *InetChecksum) Size() (bufSize int) {
|
||||||
|
|
||||||
|
bufSize = 2
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum computes the checksum cksum of the current buffer and appends it as big-endian bytes to b.
|
||||||
|
func (i *InetChecksum) Sum(b []byte) (cksumAppended []byte) {
|
||||||
|
|
||||||
|
var sum16 []byte = i.Sum16Bytes()
|
||||||
|
|
||||||
|
cksumAppended = append(b, sum16...)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sum16 computes the checksum of the current buffer and returns it as a uint16.
|
||||||
|
|
||||||
|
This is the native number used in the IPv4 header.
|
||||||
|
All other Sum* methods wrap this method.
|
||||||
|
|
||||||
|
If the underlying buffer is empty or nil, cksum will be 0xffff (65535)
|
||||||
|
in line with common implementations.
|
||||||
|
*/
|
||||||
|
func (i *InetChecksum) Sum16() (cksum uint16) {
|
||||||
|
|
||||||
|
var thisSum uint32
|
||||||
|
|
||||||
|
i.alignLock.RLock()
|
||||||
|
i.lastLock.RLock()
|
||||||
|
i.sumLock.RLock()
|
||||||
|
|
||||||
|
thisSum = i.sum
|
||||||
|
i.sumLock.RUnlock()
|
||||||
|
|
||||||
|
if !i.aligned {
|
||||||
|
/*
|
||||||
|
"Pad" at the end of the additive ops - a bitshift is used on the sum integer itself
|
||||||
|
instead of a binary.Append() or append() or such to avoid additional memory allocation.
|
||||||
|
*/
|
||||||
|
thisSum += uint32(i.last) << padShift
|
||||||
|
}
|
||||||
|
i.lastLock.RUnlock()
|
||||||
|
i.alignLock.RUnlock()
|
||||||
|
|
||||||
|
// Fold the "carried ones".
|
||||||
|
for thisSum > cksumMask {
|
||||||
|
thisSum = (thisSum & cksumMask) + (thisSum >> cksumShift)
|
||||||
|
}
|
||||||
|
cksum = ^uint16(thisSum)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sum16Bytes is a convenience wrapper around [InetChecksum.Sum16]
|
||||||
|
which returns a slice of the uint16 as a 2-byte-long slice instead.
|
||||||
|
*/
|
||||||
|
func (i *InetChecksum) Sum16Bytes() (cksum []byte) {
|
||||||
|
|
||||||
|
var sum16 uint16 = i.Sum16()
|
||||||
|
|
||||||
|
cksum = make([]byte, 2)
|
||||||
|
|
||||||
|
ord.PutUint16(cksum, sum16)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Write writes data to the underlying InetChecksum buffer. It conforms to [io.Writer].
|
||||||
|
|
||||||
|
If this operation returns an error, you MUST call [InetChecksum.Reset] as the instance
|
||||||
|
being used can no longer be considered to be in a consistent state.
|
||||||
|
|
||||||
|
p may be nil or empty; no error will be returned and n will be 0 if so.
|
||||||
|
|
||||||
|
Write is concurrency safe; a copy of p is made first and all hashing/internal
|
||||||
|
storage writing is performed on/which that copy.
|
||||||
|
*/
|
||||||
|
func (i *InetChecksum) Write(p []byte) (n int, err error) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var bufLen int
|
||||||
|
var buf []byte
|
||||||
|
var iter int
|
||||||
|
var origLast byte
|
||||||
|
var origAligned bool
|
||||||
|
var origSum uint32
|
||||||
|
|
||||||
|
if p == nil || len(p) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The TL;DR here is the checksum boils down to:
|
||||||
|
// cksum = cksum + ((high << 8) | low)
|
||||||
|
|
||||||
|
bufLen = len(p)
|
||||||
|
buf = make([]byte, bufLen)
|
||||||
|
copy(buf, p)
|
||||||
|
|
||||||
|
i.alignLock.Lock()
|
||||||
|
defer i.alignLock.Unlock()
|
||||||
|
i.bufLock.Lock()
|
||||||
|
defer i.bufLock.Unlock()
|
||||||
|
i.sumLock.Lock()
|
||||||
|
defer i.sumLock.Unlock()
|
||||||
|
i.lastLock.Lock()
|
||||||
|
defer i.lastLock.Unlock()
|
||||||
|
|
||||||
|
origLast = i.last
|
||||||
|
origAligned = i.aligned
|
||||||
|
origSum = i.sum
|
||||||
|
|
||||||
|
if !i.aligned {
|
||||||
|
// Last write was unaligned, so pair i.last in.
|
||||||
|
i.sum += (uint32(i.last) << padShift) | uint32(buf[0])
|
||||||
|
i.aligned = true
|
||||||
|
idx = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operate on bytepairs.
|
||||||
|
// Note that idx is set to either 0 or 1 depending on if
|
||||||
|
// buf[0] has already been summed in.
|
||||||
|
for iter = idx; iter < bufLen; iter += 2 {
|
||||||
|
if iter+1 < bufLen {
|
||||||
|
// Technically could use "i.sum += uint32(ord.Uint16(buf[iter:iter+2))" here instead.
|
||||||
|
i.sum += (uint32(buf[iter]) << padShift) | uint32(buf[iter+1])
|
||||||
|
} else {
|
||||||
|
i.last = buf[iter]
|
||||||
|
i.aligned = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !i.disabledBuf {
|
||||||
|
if n, err = i.buf.Write(buf); err != nil {
|
||||||
|
i.sum = origSum
|
||||||
|
i.aligned = origAligned
|
||||||
|
i.last = origLast
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteByte writes a single byte to the underlying storage. It conforms to [io.ByteWriter].
|
||||||
|
func (i *InetChecksum) WriteByte(c byte) (err error) {
|
||||||
|
|
||||||
|
var origLast byte
|
||||||
|
var origAligned bool
|
||||||
|
var origSum uint32
|
||||||
|
|
||||||
|
i.alignLock.Lock()
|
||||||
|
defer i.alignLock.Unlock()
|
||||||
|
i.bufLock.Lock()
|
||||||
|
defer i.bufLock.Unlock()
|
||||||
|
i.sumLock.Lock()
|
||||||
|
defer i.sumLock.Unlock()
|
||||||
|
i.lastLock.Lock()
|
||||||
|
defer i.lastLock.Unlock()
|
||||||
|
|
||||||
|
origLast = i.last
|
||||||
|
origAligned = i.aligned
|
||||||
|
origSum = i.sum
|
||||||
|
|
||||||
|
if i.aligned {
|
||||||
|
// Since it's a single byte, we just set i.last and unalign.
|
||||||
|
i.last = c
|
||||||
|
i.aligned = false
|
||||||
|
} else {
|
||||||
|
// It's unaligned, so join with i.last and align.
|
||||||
|
i.sum += (uint32(i.last) << padShift) | uint32(c)
|
||||||
|
i.aligned = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !i.disabledBuf {
|
||||||
|
if err = i.WriteByte(c); err != nil {
|
||||||
|
i.sum = origSum
|
||||||
|
i.aligned = origAligned
|
||||||
|
i.last = origLast
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString writes a string to the underlying storage. It conforms to [io.StringWriter].
|
||||||
|
func (i *InetChecksum) WriteString(s string) (n int, err error) {
|
||||||
|
|
||||||
|
if n, err = i.Write([]byte(s)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo writes the current contents of the underlying buffer to w. The contents are not drained. Noop if persistence is disabled.
|
||||||
|
func (i *InetChecksum) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
|
||||||
|
var wrtn int
|
||||||
|
|
||||||
|
if i.disabledBuf {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i.bufLock.RLock()
|
||||||
|
defer i.bufLock.RUnlock()
|
||||||
|
|
||||||
|
if wrtn, err = w.Write(i.buf.Bytes()); err != nil {
|
||||||
|
n = int64(wrtn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n = int64(wrtn)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
172
netx/inetcksum/funcs_inetchecksumsimple.go
Normal file
172
netx/inetcksum/funcs_inetchecksumsimple.go
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
package inetcksum
|
||||||
|
|
||||||
|
/*
|
||||||
|
Aligned returns true if the current checksum for an InetChecksumSimple is
|
||||||
|
aligned to the algorithm's requirement for an even number of bytes.
|
||||||
|
|
||||||
|
Note that if Aligned returns false, a single null pad byte will be applied
|
||||||
|
to the underlying data buffer at time of a Sum* call.
|
||||||
|
*/
|
||||||
|
func (i *InetChecksumSimple) Aligned() (aligned bool) {
|
||||||
|
|
||||||
|
aligned = i.aligned
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockSize returns the number of bytes at a time that InetChecksumSimple operates on. (It will always return 1.)
|
||||||
|
func (i *InetChecksumSimple) BlockSize() (blockSize int) {
|
||||||
|
|
||||||
|
blockSize = 1
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the state of an InetChecksumSimple.
|
||||||
|
func (i *InetChecksumSimple) Reset() {
|
||||||
|
|
||||||
|
i.last = 0x00
|
||||||
|
i.sum = 0
|
||||||
|
i.last = 0x00
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns how many bytes a checksum is. (It will always return 2.)
|
||||||
|
func (i *InetChecksumSimple) Size() (bufSize int) {
|
||||||
|
|
||||||
|
bufSize = 2
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum computes the checksum cksum of the current buffer and appends it as big-endian bytes to b.
|
||||||
|
func (i *InetChecksumSimple) Sum(b []byte) (cksumAppended []byte) {
|
||||||
|
|
||||||
|
var sum16 []byte = i.Sum16Bytes()
|
||||||
|
|
||||||
|
cksumAppended = append(b, sum16...)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sum16 computes the checksum of the current buffer and returns it as a uint16.
|
||||||
|
|
||||||
|
This is the native number used in the IPv4 header.
|
||||||
|
All other Sum* methods wrap this method.
|
||||||
|
|
||||||
|
If the underlying buffer is empty or nil, cksum will be 0xffff (65535)
|
||||||
|
in line with common implementations.
|
||||||
|
*/
|
||||||
|
func (i *InetChecksumSimple) Sum16() (cksum uint16) {
|
||||||
|
|
||||||
|
var thisSum uint32
|
||||||
|
|
||||||
|
thisSum = i.sum
|
||||||
|
|
||||||
|
if !i.aligned {
|
||||||
|
/*
|
||||||
|
"Pad" at the end of the additive ops - a bitshift is used on the sum integer itself
|
||||||
|
instead of a binary.Append() or append() or such to avoid additional memory allocation.
|
||||||
|
*/
|
||||||
|
thisSum += uint32(i.last) << padShift
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fold the "carried ones".
|
||||||
|
for thisSum > cksumMask {
|
||||||
|
thisSum = (thisSum & cksumMask) + (thisSum >> cksumShift)
|
||||||
|
}
|
||||||
|
cksum = ^uint16(thisSum)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sum16Bytes is a convenience wrapper around [InetChecksumSimple.Sum16]
|
||||||
|
which returns a slice of the uint16 as a 2-byte-long slice instead.
|
||||||
|
*/
|
||||||
|
func (i *InetChecksumSimple) Sum16Bytes() (cksum []byte) {
|
||||||
|
|
||||||
|
var sum16 uint16 = i.Sum16()
|
||||||
|
|
||||||
|
cksum = make([]byte, 2)
|
||||||
|
|
||||||
|
ord.PutUint16(cksum, sum16)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Write writes data to the underlying InetChecksumSimple buffer. It conforms to [io.Writer].
|
||||||
|
|
||||||
|
p may be nil or empty; no error will be returned and n will be 0 if so.
|
||||||
|
|
||||||
|
A copy of p is made first and all hashing operations are performed on that copy.
|
||||||
|
*/
|
||||||
|
func (i *InetChecksumSimple) Write(p []byte) (n int, err error) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var bufLen int
|
||||||
|
var buf []byte
|
||||||
|
var iter int
|
||||||
|
|
||||||
|
if p == nil || len(p) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The TL;DR here is the checksum boils down to:
|
||||||
|
// cksum = cksum + ((high << 8) | low)
|
||||||
|
|
||||||
|
bufLen = len(p)
|
||||||
|
buf = make([]byte, bufLen)
|
||||||
|
copy(buf, p)
|
||||||
|
|
||||||
|
if !i.aligned {
|
||||||
|
// Last write was unaligned, so pair i.last in.
|
||||||
|
i.sum += (uint32(i.last) << padShift) | uint32(buf[0])
|
||||||
|
i.aligned = true
|
||||||
|
idx = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operate on bytepairs.
|
||||||
|
// Note that idx is set to either 0 or 1 depending on if
|
||||||
|
// buf[0] has already been summed in.
|
||||||
|
for iter = idx; iter < bufLen; iter += 2 {
|
||||||
|
if iter+1 < bufLen {
|
||||||
|
// Technically could use "i.sum += uint32(ord.Uint16(buf[iter:iter+2))" here instead.
|
||||||
|
i.sum += (uint32(buf[iter]) << padShift) | uint32(buf[iter+1])
|
||||||
|
} else {
|
||||||
|
i.last = buf[iter]
|
||||||
|
i.aligned = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteByte checksums a single byte. It conforms to [io.ByteWriter].
|
||||||
|
func (i *InetChecksumSimple) WriteByte(c byte) (err error) {
|
||||||
|
|
||||||
|
if i.aligned {
|
||||||
|
// Since it's a single byte, we just set i.last and unalign.
|
||||||
|
i.last = c
|
||||||
|
i.aligned = false
|
||||||
|
} else {
|
||||||
|
// It's unaligned, so join with i.last and align.
|
||||||
|
i.sum += (uint32(i.last) << padShift) | uint32(c)
|
||||||
|
i.aligned = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString checksums a string. It conforms to [io.StringWriter].
|
||||||
|
func (i *InetChecksumSimple) WriteString(s string) (n int, err error) {
|
||||||
|
|
||||||
|
if n, err = i.Write([]byte(s)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
68
netx/inetcksum/types.go
Normal file
68
netx/inetcksum/types.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package inetcksum
|
||||||
|
|
||||||
|
import (
|
||||||
|
`bytes`
|
||||||
|
`sync`
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
/*
|
||||||
|
InetChecksum implements [hash.Hash] and various other stdlib interfaces.
|
||||||
|
|
||||||
|
If the current data in an InetChecksum's buffer is not aligned
|
||||||
|
to an even number of bytes -- e.g. InetChecksum.buf.Len() % 2 != 0,
|
||||||
|
[InetChecksum.Aligned] will return false (otherwise it will return
|
||||||
|
true).
|
||||||
|
|
||||||
|
If [InetChecksum.Aligned] returns false, the checksum result of an
|
||||||
|
[InetChecksum.Sum] or [InetChecksum.Sum16] (or any other operation
|
||||||
|
returning a sum) will INCLUDE THE PAD NULL BYTE (which is only
|
||||||
|
applied *at the time of the Sum/Sum32 call) and is NOT applied to
|
||||||
|
the persistent underlying storage.
|
||||||
|
|
||||||
|
InetChecksum differs from [InetChecksumSimple] in that it:
|
||||||
|
|
||||||
|
* Is MUCH better-suited/safer for concurrent operations - ALL
|
||||||
|
methods are concurrency-safe.
|
||||||
|
* Allows the data that is hashed to be recovered from a
|
||||||
|
sequential internal buffer. (See [InetChecksum.DisablePersist]
|
||||||
|
to disable the persistent internal buffer.)
|
||||||
|
|
||||||
|
At the cost of increased memory usage and additional cycles for mutexing.
|
||||||
|
|
||||||
|
Note that once persistence is disabled for an InetChecksum, it cannot be
|
||||||
|
re-enabled until/unless [InetChecksum.Reset] is called (which will reset
|
||||||
|
the persistence to enabled with a fresh buffer). Any data within the
|
||||||
|
persistent buffer will be removed if [InetChecksum.DisablePersist] is called.
|
||||||
|
*/
|
||||||
|
InetChecksum struct {
|
||||||
|
buf bytes.Buffer
|
||||||
|
disabledBuf bool
|
||||||
|
aligned bool
|
||||||
|
last byte
|
||||||
|
sum uint32
|
||||||
|
bufLock sync.RWMutex
|
||||||
|
alignLock sync.RWMutex
|
||||||
|
lastLock sync.RWMutex
|
||||||
|
sumLock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
InetChecksumSimple is like [InetChecksum], but with a few key differences.
|
||||||
|
|
||||||
|
It is MUCH much more performant/optimized for *single throughput* operations.
|
||||||
|
Because it also does not retain a buffer of what was hashed, it uses *far* less
|
||||||
|
memory over time.
|
||||||
|
|
||||||
|
However, the downside is it is NOT concurrency safe. There are no promises made
|
||||||
|
about safety or proper checksum ordering with concurrency for this type, but it
|
||||||
|
should have much better performance for non-concurrent use.
|
||||||
|
|
||||||
|
It behaves much more like a traditional [hash.Hash].
|
||||||
|
*/
|
||||||
|
InetChecksumSimple struct {
|
||||||
|
aligned bool
|
||||||
|
last byte
|
||||||
|
sum uint32
|
||||||
|
}
|
||||||
|
)
|
||||||
4
remap/doc.go
Normal file
4
remap/doc.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
Package remap provides convenience functions around regular expressions, primarily offering maps for named capture groups.
|
||||||
|
*/
|
||||||
|
package remap
|
||||||
488
remap/funcs_remap.go
Normal file
488
remap/funcs_remap.go
Normal file
@@ -0,0 +1,488 @@
|
|||||||
|
package remap
|
||||||
|
|
||||||
|
/*
|
||||||
|
Map returns a map[string][]<match bytes> for regexes with named capture groups matched in bytes b.
|
||||||
|
Note that this supports non-unique group names; [regexp.Regexp] allows for patterns with multiple groups
|
||||||
|
using the same group name (though your IDE might complain; I know GoLand does).
|
||||||
|
|
||||||
|
Each match for each group is in a slice keyed under that group name, with that slice
|
||||||
|
ordered by the indexing done by the regex match itself.
|
||||||
|
|
||||||
|
In summary, the parameters are as follows:
|
||||||
|
|
||||||
|
# inclNoMatch
|
||||||
|
|
||||||
|
If true, then attempt to return a non-nil matches (as long as b isn't nil).
|
||||||
|
Group keys will be populated and explicitly defined as nil.
|
||||||
|
|
||||||
|
For example, if a pattern
|
||||||
|
|
||||||
|
^(?P<g1>foo)(?P<g1>bar)(?P<g2>baz)$
|
||||||
|
|
||||||
|
is provided but b does not match then matches will be:
|
||||||
|
|
||||||
|
map[string][][]byte{
|
||||||
|
"g1": nil,
|
||||||
|
"g2": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
# inclNoMatchStrict
|
||||||
|
|
||||||
|
If true (and inclNoMatch is true), instead of a single nil the group's values will be
|
||||||
|
a slice of nil values explicitly matching the number of times the group name is specified
|
||||||
|
in the pattern.
|
||||||
|
|
||||||
|
For example, if a pattern:
|
||||||
|
|
||||||
|
^(?P<g1>foo)(?P<g1>bar)(?P<g2>baz)$
|
||||||
|
|
||||||
|
is provided but b does not match then matches will be:
|
||||||
|
|
||||||
|
map[string][][]byte{
|
||||||
|
"g1": [][]byte{
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"g2": [][]byte{
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# mustMatch
|
||||||
|
|
||||||
|
If true, matches will be nil if the entirety of b does not match the pattern (and thus
|
||||||
|
no capture groups matched) (overrides inclNoMatch) -- explicitly:
|
||||||
|
|
||||||
|
matches == nil
|
||||||
|
|
||||||
|
Otherwise if false (and assuming inclNoMatch is false), matches will be:
|
||||||
|
|
||||||
|
map[string][][]byte{}{}
|
||||||
|
|
||||||
|
# Condition Tree
|
||||||
|
|
||||||
|
In detail, matches and/or its values may be nil or empty under the following condition tree:
|
||||||
|
|
||||||
|
IF b is nil:
|
||||||
|
THEN matches will always be nil
|
||||||
|
ELSE:
|
||||||
|
IF all of b does not match pattern
|
||||||
|
IF mustMuch is true
|
||||||
|
THEN matches == nil
|
||||||
|
ELSE
|
||||||
|
THEN matches == map[string][][]byte{} (non-nil but empty)
|
||||||
|
ELSE IF pattern has no named capture groups
|
||||||
|
IF inclNoMatch is true
|
||||||
|
THEN matches == map[string][][]byte{} (non-nil but empty)
|
||||||
|
ELSE
|
||||||
|
THEN matches == nil
|
||||||
|
ELSE
|
||||||
|
IF there are no named group matches
|
||||||
|
IF inclNoMatch is true
|
||||||
|
THEN matches is non-nil; matches[<group name>, ...] is/are defined but nil (_, ok = matches[<group name>]; ok == true)
|
||||||
|
ELSE
|
||||||
|
THEN matches == nil
|
||||||
|
ELSE
|
||||||
|
IF <group name> does not have a match
|
||||||
|
IF inclNoMatch is true
|
||||||
|
IF inclNoMatchStrict is true
|
||||||
|
THEN matches[<group name>] is defined and non-nil, but populated with placeholder nils
|
||||||
|
(matches[<group name>] == [][]byte{nil[, nil...]})
|
||||||
|
ELSE
|
||||||
|
THEN matches[<group name>] is guaranteed defined but may be nil (_, ok = matches[<group name>]; ok == true)
|
||||||
|
ELSE
|
||||||
|
THEN matches[<group name>] is not defined (_, ok = matches[<group name>]; ok == false)
|
||||||
|
ELSE
|
||||||
|
matches[<group name>] == []{<match>[, <match>...]}
|
||||||
|
*/
|
||||||
|
func (r *ReMap) Map(b []byte, inclNoMatch, inclNoMatchStrict, mustMatch bool) (matches map[string][][]byte) {
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
var mIdx int
|
||||||
|
var match []byte
|
||||||
|
var grpNm string
|
||||||
|
var names []string
|
||||||
|
var matchBytes [][]byte
|
||||||
|
var tmpMap map[string][][]byte = make(map[string][][]byte)
|
||||||
|
|
||||||
|
if b == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
names = r.Regexp.SubexpNames()
|
||||||
|
matchBytes = r.Regexp.FindSubmatch(b)
|
||||||
|
|
||||||
|
if matchBytes == nil {
|
||||||
|
// b does not match pattern
|
||||||
|
if !mustMatch {
|
||||||
|
matches = make(map[string][][]byte)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if names == nil || len(names) == 0 || len(names) == 1 {
|
||||||
|
/*
|
||||||
|
no named capture groups;
|
||||||
|
technically only the last condition would be the case.
|
||||||
|
*/
|
||||||
|
if inclNoMatch {
|
||||||
|
matches = make(map[string][][]byte)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
names = names[1:]
|
||||||
|
|
||||||
|
if len(matchBytes) == 0 || len(matchBytes) == 1 {
|
||||||
|
/*
|
||||||
|
no submatches whatsoever.
|
||||||
|
*Technically* I don't think this condition can actually be reached.
|
||||||
|
This is more of a safe-return before we re-slice.
|
||||||
|
*/
|
||||||
|
matches = make(map[string][][]byte)
|
||||||
|
if inclNoMatch {
|
||||||
|
if len(names) >= 1 {
|
||||||
|
for _, grpNm = range names {
|
||||||
|
matches[grpNm] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
matchBytes = matchBytes[1:]
|
||||||
|
|
||||||
|
for mIdx, match = range matchBytes {
|
||||||
|
grpNm = names[mIdx]
|
||||||
|
/*
|
||||||
|
Thankfully, it's actually a build error if a pattern specifies a named
|
||||||
|
capture group with an empty name.
|
||||||
|
So we don't need to worry about accounting for that,
|
||||||
|
and can just skip over grpNm == "" (which is an *unnamed* capture group).
|
||||||
|
*/
|
||||||
|
if grpNm == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if match == nil {
|
||||||
|
// group did not match
|
||||||
|
if !inclNoMatch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok = tmpMap[grpNm]; !ok {
|
||||||
|
if !inclNoMatchStrict {
|
||||||
|
tmpMap[grpNm] = nil
|
||||||
|
} else {
|
||||||
|
tmpMap[grpNm] = [][]byte{nil}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if inclNoMatchStrict {
|
||||||
|
tmpMap[grpNm] = append(tmpMap[grpNm], nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok = tmpMap[grpNm]; !ok {
|
||||||
|
tmpMap[grpNm] = make([][]byte, 0)
|
||||||
|
}
|
||||||
|
tmpMap[grpNm] = append(tmpMap[grpNm], match)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This *technically* should be completely handled above.
|
||||||
|
if inclNoMatch {
|
||||||
|
for _, grpNm = range names {
|
||||||
|
if _, ok = tmpMap[grpNm]; !ok {
|
||||||
|
tmpMap[grpNm] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tmpMap) > 0 {
|
||||||
|
matches = tmpMap
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
MapString is exactly like ReMap.Map(), but operates on (and returns) strings instead.
|
||||||
|
(matches will always be nil if s == “.)
|
||||||
|
|
||||||
|
A small deviation, though; empty strings instead of nils (because duh) will occupy slice placeholders (if `inclNoMatchStrict` is specified).
|
||||||
|
This unfortunately *does not provide any indication* if an empty string positively matched the pattern (a "hit") or if it was simply
|
||||||
|
not matched at all (a "miss"). If you need definitive determination between the two conditions, it is instead recommended to either
|
||||||
|
*not* use inclNoMatchStrict or to use ReMap.Map() instead and convert any non-nil values to strings after.
|
||||||
|
|
||||||
|
Particularly:
|
||||||
|
|
||||||
|
# inclNoMatch
|
||||||
|
|
||||||
|
If true, then attempt to return a non-nil matches (as long as s isn't empty).
|
||||||
|
Group keys will be populated and explicitly defined as nil.
|
||||||
|
|
||||||
|
For example, if a pattern
|
||||||
|
|
||||||
|
^(?P<g1>foo)(?P<g1>bar)(?P<g2>baz)$
|
||||||
|
|
||||||
|
is provided but s does not match then matches will be:
|
||||||
|
|
||||||
|
map[string][]string{
|
||||||
|
"g1": nil,
|
||||||
|
"g2": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
# inclNoMatchStrict
|
||||||
|
|
||||||
|
If true (and inclNoMatch is true), instead of a single nil the group's values will be
|
||||||
|
a slice of eempty string values explicitly matching the number of times the group name is specified
|
||||||
|
in the pattern.
|
||||||
|
|
||||||
|
For example, if a pattern:
|
||||||
|
|
||||||
|
^(?P<g1>foo)(?P<g1>bar)(?P<g2>baz)$
|
||||||
|
|
||||||
|
is provided but s does not match then matches will be:
|
||||||
|
|
||||||
|
map[string][]string{
|
||||||
|
"g1": []string{
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
"g2": []string{
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# mustMatch
|
||||||
|
|
||||||
|
If true, matches will be nil if the entirety of s does not match the pattern (and thus
|
||||||
|
no capture groups matched) (overrides inclNoMatch) -- explicitly:
|
||||||
|
|
||||||
|
matches == nil
|
||||||
|
|
||||||
|
Otherwise if false (and assuming inclNoMatch is false), matches will be:
|
||||||
|
|
||||||
|
map[string][]string{}{}
|
||||||
|
|
||||||
|
# Condition Tree
|
||||||
|
|
||||||
|
In detail, matches and/or its values may be nil or empty under the following condition tree:
|
||||||
|
|
||||||
|
IF s is empty:
|
||||||
|
THEN matches will always be nil
|
||||||
|
ELSE:
|
||||||
|
IF all of s does not match pattern
|
||||||
|
IF mustMuch is true
|
||||||
|
THEN matches == nil
|
||||||
|
ELSE
|
||||||
|
THEN matches == map[string][]string{} (non-nil but empty)
|
||||||
|
ELSE IF pattern has no named capture groups
|
||||||
|
IF inclNoMatch is true
|
||||||
|
THEN matches == map[string][]string{} (non-nil but empty)
|
||||||
|
ELSE
|
||||||
|
THEN matches == nil
|
||||||
|
ELSE
|
||||||
|
IF there are no named group matches
|
||||||
|
IF inclNoMatch is true
|
||||||
|
THEN matches is non-nil; matches[<group name>, ...] is/are defined but nil (_, ok = matches[<group name>]; ok == true)
|
||||||
|
ELSE
|
||||||
|
THEN matches == nil
|
||||||
|
ELSE
|
||||||
|
IF <group name> does not have a match
|
||||||
|
IF inclNoMatch is true
|
||||||
|
IF inclNoMatchStrict is true
|
||||||
|
THEN matches[<group name>] is defined and non-nil, but populated with placeholder nils
|
||||||
|
(matches[<group name>] == []string{""[, ""...]})
|
||||||
|
ELSE
|
||||||
|
THEN matches[<group name>] is guaranteed defined but may be nil (_, ok = matches[<group name>]; ok == true)
|
||||||
|
ELSE
|
||||||
|
THEN matches[<group name>] is not defined (_, ok = matches[<group name>]; ok == false)
|
||||||
|
ELSE
|
||||||
|
matches[<group name>] == []{<match>[, <match>...]}
|
||||||
|
*/
|
||||||
|
func (r *ReMap) MapString(s string, inclNoMatch, inclNoMatchStrict, mustMatch bool) (matches map[string][]string) {
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
var endIdx int
|
||||||
|
var startIdx int
|
||||||
|
var chunkIdx int
|
||||||
|
var grpNm string
|
||||||
|
var names []string
|
||||||
|
var matchStr string
|
||||||
|
/*
|
||||||
|
A slice of indices or index pairs.
|
||||||
|
For each element `e` in idxChunks,
|
||||||
|
* if `e` is nil, no group match.
|
||||||
|
* if len(e) == 1, only a single character was matched.
|
||||||
|
* otherwise len(e) == 2, the start and end of the match.
|
||||||
|
*/
|
||||||
|
var idxChunks [][]int
|
||||||
|
var matchIndices []int
|
||||||
|
var chunkIndices []int // always 2 elements; start pos and end pos
|
||||||
|
var tmpMap map[string][]string = make(map[string][]string)
|
||||||
|
|
||||||
|
/*
|
||||||
|
OK so this is a bit of a deviation.
|
||||||
|
|
||||||
|
It's not as straightforward as above, because there isn't an explicit way
|
||||||
|
like above to determine if a pattern was *matched as an empty string* vs.
|
||||||
|
*not matched*.
|
||||||
|
|
||||||
|
So instead do roundabout index-y things.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if s == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
I'm not entirely sure how serious they are about "the slice should not be modified"...
|
||||||
|
|
||||||
|
DO NOT sort or dedupe `names`! If the same name for groups is duplicated,
|
||||||
|
it will be duplicated here in proper order and the ordering is tied to
|
||||||
|
the ordering of matchIndices.
|
||||||
|
*/
|
||||||
|
names = r.Regexp.SubexpNames()[:]
|
||||||
|
matchIndices = r.Regexp.FindStringSubmatchIndex(s)
|
||||||
|
|
||||||
|
if matchIndices == nil {
|
||||||
|
// s does not match pattern at all.
|
||||||
|
if !mustMatch {
|
||||||
|
matches = make(map[string][]string)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if names == nil || len(names) <= 1 {
|
||||||
|
/*
|
||||||
|
No named capture groups;
|
||||||
|
technically only the last condition would be the case,
|
||||||
|
as (regexp.Regexp).SubexpNames() will ALWAYS at the LEAST
|
||||||
|
return a `[]string{""}`.
|
||||||
|
*/
|
||||||
|
if inclNoMatch {
|
||||||
|
matches = make(map[string][]string)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(matchIndices) == 0 || len(matchIndices) == 1 {
|
||||||
|
/*
|
||||||
|
No (sub)matches whatsoever.
|
||||||
|
*technically* I don't think this condition can actually be reached;
|
||||||
|
matchIndices should ALWAYS either be `nil` or len will be at LEAST 2,
|
||||||
|
and modulo 2 thereafter since they're PAIRS of indices...
|
||||||
|
Why they didn't just return a [][]int or [][2]int or something
|
||||||
|
instead of an []int, who knows.
|
||||||
|
But we're correcting that poor design.
|
||||||
|
This is more of a safe-return before we chunk the indices.
|
||||||
|
*/
|
||||||
|
matches = make(map[string][]string)
|
||||||
|
if inclNoMatch {
|
||||||
|
for _, grpNm = range names {
|
||||||
|
if grpNm != "" {
|
||||||
|
matches[grpNm] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
A reslice of `matchIndices` could technically start at 2 (as long as `names` is sliced [1:])
|
||||||
|
because they're in pairs: []int{<start>, <end>, <start>, <end>, ...}
|
||||||
|
and the first pair is the entire pattern match (un-resliced names[0]).
|
||||||
|
Thus the len(matchIndices) == 2*len(names), *even* if you
|
||||||
|
Keep in mind that since the first element of names is removed,
|
||||||
|
the first pair here is skipped.
|
||||||
|
This provides a bit more consistent readability, though.
|
||||||
|
*/
|
||||||
|
idxChunks = make([][]int, len(names))
|
||||||
|
chunkIdx = 0
|
||||||
|
endIdx = 0
|
||||||
|
for startIdx = 0; endIdx < len(matchIndices); startIdx += 2 {
|
||||||
|
endIdx = startIdx + 2
|
||||||
|
// This technically should never happen.
|
||||||
|
if endIdx > len(matchIndices) {
|
||||||
|
endIdx = len(matchIndices)
|
||||||
|
}
|
||||||
|
|
||||||
|
chunkIndices = matchIndices[startIdx:endIdx]
|
||||||
|
|
||||||
|
if chunkIndices[0] == -1 || chunkIndices[1] == -1 {
|
||||||
|
// group did not match
|
||||||
|
chunkIndices = nil
|
||||||
|
} else {
|
||||||
|
if chunkIndices[0] == chunkIndices[1] {
|
||||||
|
chunkIndices = []int{chunkIndices[0]}
|
||||||
|
} else {
|
||||||
|
chunkIndices = matchIndices[startIdx:endIdx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
idxChunks[chunkIdx] = chunkIndices
|
||||||
|
chunkIdx++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now associate with names and pull the string sequence.
|
||||||
|
for chunkIdx, chunkIndices = range idxChunks {
|
||||||
|
grpNm = names[chunkIdx]
|
||||||
|
/*
|
||||||
|
Thankfully, it's actually a build error if a pattern specifies a named
|
||||||
|
capture group with an empty name.
|
||||||
|
So we don't need to worry about accounting for that,
|
||||||
|
and can just skip over grpNm == ""
|
||||||
|
(which is either an *unnamed* capture group
|
||||||
|
OR the first element in `names`, which is always
|
||||||
|
the entire match).
|
||||||
|
*/
|
||||||
|
if grpNm == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if chunkIndices == nil || len(chunkIndices) == 0 {
|
||||||
|
// group did not match
|
||||||
|
if !inclNoMatch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok = tmpMap[grpNm]; !ok {
|
||||||
|
if !inclNoMatchStrict {
|
||||||
|
tmpMap[grpNm] = nil
|
||||||
|
} else {
|
||||||
|
tmpMap[grpNm] = []string{""}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if inclNoMatchStrict {
|
||||||
|
tmpMap[grpNm] = append(tmpMap[grpNm], "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(chunkIndices) {
|
||||||
|
case 1:
|
||||||
|
// Single character
|
||||||
|
matchStr = string(s[chunkIndices[0]])
|
||||||
|
case 2:
|
||||||
|
// Multiple characters
|
||||||
|
matchStr = s[chunkIndices[0]:chunkIndices[1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok = tmpMap[grpNm]; !ok {
|
||||||
|
tmpMap[grpNm] = make([]string, 0)
|
||||||
|
}
|
||||||
|
tmpMap[grpNm] = append(tmpMap[grpNm], matchStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This *technically* should be completely handled above.
|
||||||
|
if inclNoMatch {
|
||||||
|
for _, grpNm = range names {
|
||||||
|
if _, ok = tmpMap[grpNm]; !ok {
|
||||||
|
tmpMap[grpNm] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tmpMap) > 0 {
|
||||||
|
matches = tmpMap
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
27
remap/types.go
Normal file
27
remap/types.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package remap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// ReMap provides some map-related functions around a regexp.Regexp.
|
||||||
|
ReMap struct {
|
||||||
|
*regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO?
|
||||||
|
/*
|
||||||
|
ExplicitStringMatch is used with ReMap.MapStringExplicit to indicate if a
|
||||||
|
capture group result is a hit (a group matched, but e.g. the match value is empty string)
|
||||||
|
or not (a group did not match).
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
ExplicitStringMatch struct {
|
||||||
|
Group string
|
||||||
|
IsMatch bool
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
)
|
||||||
5
structutils/consts.go
Normal file
5
structutils/consts.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package structutils
|
||||||
|
|
||||||
|
const (
|
||||||
|
TagMapTrim tagMapOpt = iota
|
||||||
|
)
|
||||||
362
structutils/funcs.go
Normal file
362
structutils/funcs.go
Normal 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
5
structutils/types.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package structutils
|
||||||
|
|
||||||
|
type (
|
||||||
|
tagMapOpt uint8
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user