98 lines
2.4 KiB
Go
98 lines
2.4 KiB
Go
|
|
package journslog
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
_ "log/slog"
|
|||
|
|
"strings"
|
|||
|
|
"sync"
|
|||
|
|
|
|||
|
|
"src.lwithers.me.uk/go/journal"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// KeyMapper maps [slog.Attr] keys onto [journal.AttrKey] objects. Such a mapper
|
|||
|
|
// is necessary because systemd-journald is very restrictive about suitable
|
|||
|
|
// attribute keys.
|
|||
|
|
type KeyMapper interface {
|
|||
|
|
KeyMap(slogAttrKey string) journal.AttrKey
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DefaultKeyMapper returns a [KeyMapper] with the package's default behaviour.
|
|||
|
|
// The returned mapper contains an internal, persistent map and a [sync.RWMutex]
|
|||
|
|
// to cache map results.
|
|||
|
|
//
|
|||
|
|
// The default behaviour is to strip leading underscores; to turn lowercase
|
|||
|
|
// ASCII ‘a’–‘z’ chars into uppercase ‘A’–‘Z’ (uppercase is passed through
|
|||
|
|
// verbatim), to accept underscores, and to map all other chars to underscores.
|
|||
|
|
// If the result is an empty string, it instead returns the key "BAD_ATTR_KEY".
|
|||
|
|
func DefaultKeyMapper() KeyMapper {
|
|||
|
|
return &defaultKeyMapper{
|
|||
|
|
badKey: journal.MustAttrKey("BAD_ATTR_KEY"),
|
|||
|
|
keys: make(map[string]journal.AttrKey),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type defaultKeyMapper struct {
|
|||
|
|
badKey journal.AttrKey
|
|||
|
|
keys map[string]journal.AttrKey
|
|||
|
|
lock sync.RWMutex
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (dkm *defaultKeyMapper) KeyMap(slogAttrKey string) journal.AttrKey {
|
|||
|
|
if key, ok := dkm.mapFast(slogAttrKey); ok {
|
|||
|
|
return key
|
|||
|
|
}
|
|||
|
|
return dkm.mapSlow(slogAttrKey)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (dkm *defaultKeyMapper) mapFast(slogAttrKey string) (journal.AttrKey, bool) {
|
|||
|
|
dkm.lock.RLock()
|
|||
|
|
defer dkm.lock.RUnlock()
|
|||
|
|
key, ok := dkm.keys[slogAttrKey]
|
|||
|
|
return key, ok
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (dkm *defaultKeyMapper) mapSlow(slogAttrKey string) journal.AttrKey {
|
|||
|
|
key := dkm.mapFunc(slogAttrKey)
|
|||
|
|
|
|||
|
|
// it's OK if another goroutine in the slow path just wrote the key, we
|
|||
|
|
// will overwrite its result but the result will still be correct
|
|||
|
|
dkm.lock.Lock()
|
|||
|
|
defer dkm.lock.Unlock()
|
|||
|
|
dkm.keys[slogAttrKey] = key
|
|||
|
|
return key
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (dkm *defaultKeyMapper) mapFunc(slogAttrKey string) journal.AttrKey {
|
|||
|
|
key, err := journal.NewAttrKey(slogAttrKey)
|
|||
|
|
if err == nil {
|
|||
|
|
return key
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var s strings.Builder
|
|||
|
|
for _, r := range slogAttrKey {
|
|||
|
|
switch {
|
|||
|
|
case r >= 'a' && r <= 'z':
|
|||
|
|
s.WriteRune(r - 'a' + 'A')
|
|||
|
|
case r >= '0' && r <= '9',
|
|||
|
|
r >= 'A' && r <= 'Z':
|
|||
|
|
s.WriteRune(r)
|
|||
|
|
default:
|
|||
|
|
if s.Len() > 0 {
|
|||
|
|
s.WriteRune('_')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if s.Len() == 0 {
|
|||
|
|
return dkm.badKey
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
skey := s.String()
|
|||
|
|
if len(skey) > 255 {
|
|||
|
|
skey = skey[:255] // known to only contain ASCII chars
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if key, err = journal.NewAttrKey(skey); err != nil {
|
|||
|
|
return dkm.badKey
|
|||
|
|
}
|
|||
|
|
return key
|
|||
|
|
}
|