package journal import ( "fmt" ) // AttrKey holds the key part of a key/value (attribute) pair. Passing an // uninitialised (or invalid) AttrKey to other journal methods will cause the // attribute to be ignored; check for errors from NewAttrKey to ensure that // your keys are valid ahead of time. type AttrKey struct { // key is written directly to the journal socket. If it is empty, then // the attribute should be silently discarded. By having it unexported, // we can ensure that library users can only ever have valid string // values here. key string } // Key returns the string value of the key. If the attribute is invalid, it // returns an empty string. func (ak AttrKey) Key() string { return ak.key } // Valid returns true if ak is valid. func (ak AttrKey) Valid() bool { return ak.key != "" } // NewAttrKey initialises and returns an AttrKey that may be used when // constructing key/value (attribute) pairs for logging. If the key is invalid, // then an error will be returned (and the returned key value will be invalid, // and any attribute using that key will silently be omitted from the logs). // Rules for valid attribute keys: // - must not be empty; // - must not begin with an underscore '_'; // - must only contain uppercase ASCII 'A'–'Z', digits '0'–'9', or // underscores; // - must be < 256 characters. func NewAttrKey(key string) (AttrKey, error) { if err := AttrKeyValid(key); err != nil { return AttrKey{}, err } return AttrKey{ key: key, }, nil } // MustAttrKey returns an AttrKey for the given string. If the string is not a // valid systemd attribute key, it returns an invalid AttrKey that will cause // the attribute not to be emitted to the journal. func MustAttrKey(key string) AttrKey { if err := AttrKeyValid(key); err != nil { return AttrKey{} } return AttrKey{ key: key, } } // AttrKeyValid checks the given attribute key and returns an error if there is // a fault with it. func AttrKeyValid(key string) error { switch { case key == "": return &InvalidAttrKey{ Key: key, Reason: AttrKeyEmpty, } case key[0] == '_': return &InvalidAttrKey{ Key: key, Reason: AttrKeyTrusted, } case len(key) >= 256: return &InvalidAttrKey{ Key: key, Reason: AttrKeyLength, } case !attrKeyValidChars(key): return &InvalidAttrKey{ Key: key, Reason: AttrKeyInvalidChars, } default: return nil } } // attrKeyValidChars returns true if the given key comprises only ASCII 'A'–'Z', // '0'–'9' and underscores. func attrKeyValidChars(key string) bool { for _, b := range key { switch { case b >= 'A' && b <= 'Z': case b >= '0' && b <= '9': case b == '_': default: return false } } return true } // InvalidAttrKey details why a given attribute key is invalid. type InvalidAttrKey struct { Key string Reason InvalidAttrKeyReason } func (iak *InvalidAttrKey) Error() string { return fmt.Sprintf("invalid systemd journal attribute key %q: %s", iak.Key, iak.Reason) } type InvalidAttrKeyReason int const ( AttrKeyEmpty InvalidAttrKeyReason = iota AttrKeyTrusted AttrKeyLength AttrKeyInvalidChars ) func (iar InvalidAttrKeyReason) String() string { switch iar { case AttrKeyEmpty: return "key is empty" case AttrKeyTrusted: return "key is trusted (begins with '_' character)" case AttrKeyLength: return "key is too long (max 256 chars)" case AttrKeyInvalidChars: return "key contains invalid character (only 'A'–'Z', '0'–'9', or '_' allowed)" default: return "???" } }