journal/journslog/key_map.go

98 lines
2.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 az chars into uppercase AZ (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
}