From 06e64e119b32194288f729efe3ea823eda945e6a Mon Sep 17 00:00:00 2001 From: Lukas Zapletal Date: Mon, 6 Jan 2025 18:11:36 +0100 Subject: [PATCH] journal: SendVals with arbitrary auto-converting functions This patch adds new function SendVals and Value interface which allows the API users to use alternative syntax to provide journald values: journal.SendVals(message, journal.PriInfo, journal.String("KEY", "VALUE"), journal.Int("INT_KEY", 1), ) The whole suite of helper fucntions is provided, but they immediately convert the value to string since carrying the type does not make much sense in this case as the value is immediately written to byte buffer and sent to journald. It is just a syntactic sugar I thought might be useful, for my usecase only the journal.String is enough. --- journal/journal_unix.go | 22 ++++++++-- journal/journal_unix_test.go | 18 ++++++++ journal/values.go | 80 ++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 journal/values.go diff --git a/journal/journal_unix.go b/journal/journal_unix.go index c5b23a81..f54ab9c8 100644 --- a/journal/journal_unix.go +++ b/journal/journal_unix.go @@ -129,6 +129,24 @@ func fdIsJournalStream(fd int) (bool, error) { // (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html) // for more details. vars may be nil. func Send(message string, priority Priority, vars map[string]string) error { + return send(message, priority, func(b *bytes.Buffer) { + for k, v := range vars { + appendVariable(b, k, v) + } + }) +} + +// Send a message to the local systemd journal. Optional values may be +// provided as a list of Value variablese. +func SendVals(message string, priority Priority, values ...Value) error { + return send(message, priority, func(b *bytes.Buffer) { + for _, v := range values { + appendVariable(b, v.Name(), v.Value()) + } + }) +} + +func send(message string, priority Priority, varFunc func(data *bytes.Buffer)) error { conn := getOrInitConn() if conn == nil { return errors.New("could not initialize socket to journald") @@ -142,9 +160,7 @@ func Send(message string, priority Priority, vars map[string]string) error { data := new(bytes.Buffer) appendVariable(data, "PRIORITY", strconv.Itoa(int(priority))) appendVariable(data, "MESSAGE", message) - for k, v := range vars { - appendVariable(data, k, v) - } + varFunc(data) _, _, err := conn.WriteMsgUnix(data.Bytes(), nil, socketAddr) if err == nil { diff --git a/journal/journal_unix_test.go b/journal/journal_unix_test.go index 3483d337..77e41f5f 100644 --- a/journal/journal_unix_test.go +++ b/journal/journal_unix_test.go @@ -127,6 +127,24 @@ func TestStderrIsJournalStream(t *testing.T) { if err != nil { t.Fatal(err) } + + err = journal.Send(message, journal.PriInfo, map[string]string{"KEY": "VALUE"}) + if err != nil { + t.Fatal(err) + } + + err = journal.SendVals(message, journal.PriInfo) + if err != nil { + t.Fatal(err) + } + + err = journal.SendVals(message, journal.PriInfo, + journal.String("KEY", "VALUE"), + journal.Int("INT_KEY", 1), + ) + if err != nil { + t.Fatal(err) + } } } diff --git a/journal/values.go b/journal/values.go new file mode 100644 index 00000000..86ede099 --- /dev/null +++ b/journal/values.go @@ -0,0 +1,80 @@ +package journal + +import ( + "strconv" + "time" +) + +// Value represents a key-value pair that can be logged to the journal. +type Value interface { + // Name must be composed of uppercase letters, numbers, + // and underscores, but must not start with an underscore. Within these + // restrictions, any arbitrary field name may be used. Some names have special + // significance: see the journalctl documentation + // (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html) + // for more details. + Name() string + + // Value must be a string representation of the value. + Value() string +} + +type value struct { + name string + value string +} + +var _ = Value(&value{}) + +func (s value) Name() string { + return s.name +} + +func (s value) Value() string { + return s.value +} + +// String returns a Value for a string value. +func String(name, Value string) Value { + return &value{name, Value} +} + +// Int returns a Value for an int. +func Int(name string, Value int) Value { + return &value{name, strconv.Itoa(Value)} +} + +// Int64 returns a Value for an int64. +func Int64(name string, Value int64) Value { + return &value{name, strconv.FormatInt(Value, 10)} +} + +// Uint64 returns a Value for a uint64. +func Uint64(name string, Value uint64) Value { + return &value{name, strconv.FormatUint(Value, 10)} +} + +// Float64 returns a Value for a floating-point number. +func Float64(name string, Value float64) Value { + return &value{name, strconv.FormatFloat(Value, 'g', -1, 64)} +} + +// Bool returns a Value for a bool. +func Bool(name string, Value bool) Value { + return &value{name, strconv.FormatBool(Value)} +} + +// Time returns a Value for a time.Time. +func Time(name string, Value time.Time) Value { + return &value{name, Value.Format(time.RFC3339Nano)} +} + +// Duration returns a Value for a time.Duration. +func Duration(name string, Value time.Duration) Value { + return &value{name, Value.String()} +} + +// Error returns a Value for an error. +func Error(name string, Value error) Value { + return &value{name, Value.Error()} +}