From 1357d57530c7b261eabc8f413cee7f24c12891e1 Mon Sep 17 00:00:00 2001 From: Gorka Lerchundi Osa Date: Thu, 26 May 2016 23:30:17 +0200 Subject: [PATCH 1/2] sdjournal: add GetEntry method to retrieve all fields plus address fields Implements the same semantics as the JOURNAL_FOREACH_DATA_RETVAL macro in journal-internal.h and produces a *JournalEntry with every field with its corresponding data in a map[string]string. Also it includes address fields like realtime (__REALTIME_TIMESTAMP), monotonic (__MONOTONIC_TIMESTAMP and cursor (__CURSOR). It mimics more or less `journalctl -o json`. Fixes #142 Supersedes #136 --- sdjournal/journal.go | 146 +++++++++++++++++++++++++++++++++++++- sdjournal/journal_test.go | 53 ++++++++++++++ 2 files changed, 198 insertions(+), 1 deletion(-) diff --git a/sdjournal/journal.go b/sdjournal/journal.go index d3c5675b..07b4f630 100644 --- a/sdjournal/journal.go +++ b/sdjournal/journal.go @@ -25,6 +25,7 @@ package sdjournal // #include +// #include // #include // #include // @@ -182,6 +183,15 @@ package sdjournal // } // // int +// my_sd_journal_get_monotonic_usec(void *f, sd_journal *j, uint64_t *usec, sd_id128_t *boot_id) +// { +// int (*sd_journal_get_monotonic_usec)(sd_journal *, uint64_t *, sd_id128_t *); +// +// sd_journal_get_monotonic_usec = f; +// return sd_journal_get_monotonic_usec(j, usec, boot_id); +// } +// +// int // my_sd_journal_seek_head(void *f, sd_journal *j) // { // int (*sd_journal_seek_head)(sd_journal *); @@ -227,6 +237,24 @@ package sdjournal // return sd_journal_wait(j, timeout_usec); // } // +// void +// my_sd_journal_restart_data(void *f, sd_journal *j) +// { +// void (*sd_journal_restart_data)(sd_journal *); +// +// sd_journal_restart_data = f; +// sd_journal_restart_data(j); +// } +// +// int +// my_sd_journal_enumerate_data(void *f, sd_journal *j, const void **data, size_t *length) +// { +// int (*sd_journal_enumerate_data)(sd_journal *, const void **, size_t *); +// +// sd_journal_enumerate_data = f; +// return sd_journal_enumerate_data(j, data, length); +// } +// import "C" import ( "fmt" @@ -288,6 +316,14 @@ type Journal struct { lib *dlopen.LibHandle } +// JournalEntry represents all fields of a journal entry plus address fields. +type JournalEntry struct { + Fields map[string]string + Cursor string + RealtimeTimestamp uint64 + MonotonicTimestamp uint64 +} + // Match is a convenience wrapper to describe filters supplied to AddMatch. type Match struct { Field string @@ -583,6 +619,93 @@ func (j *Journal) GetDataValue(field string) (string, error) { return strings.SplitN(val, "=", 2)[1], nil } +// GetEntry returns a full representation of a journal entry with +// all key-value pairs of data as well as address fields (cursor, realtime +// timestamp and monotonic timestamp) +func (j *Journal) GetEntry() (*JournalEntry, error) { + sd_journal_get_realtime_usec, err := j.getFunction("sd_journal_get_realtime_usec") + if err != nil { + return nil, err + } + + sd_journal_get_monotonic_usec, err := j.getFunction("sd_journal_get_monotonic_usec") + if err != nil { + return nil, err + } + + sd_journal_get_cursor, err := j.getFunction("sd_journal_get_cursor") + if err != nil { + return nil, err + } + + sd_journal_restart_data, err := j.getFunction("sd_journal_restart_data") + if err != nil { + return nil, err + } + + sd_journal_enumerate_data, err := j.getFunction("sd_journal_enumerate_data") + if err != nil { + return nil, err + } + + j.mu.Lock() + defer j.mu.Unlock() + + var r C.int + entry := &JournalEntry{Fields: make(map[string]string)} + + var realtimeUsec C.uint64_t + r = C.my_sd_journal_get_realtime_usec(sd_journal_get_realtime_usec, j.cjournal, &realtimeUsec) + if r < 0 { + return nil, fmt.Errorf("failed to get realtime timestamp: %d", syscall.Errno(-r)) + } + + entry.RealtimeTimestamp = uint64(realtimeUsec) + + var monotonicUsec C.uint64_t + var boot_id C.sd_id128_t + + r = C.my_sd_journal_get_monotonic_usec(sd_journal_get_monotonic_usec, j.cjournal, &monotonicUsec, &boot_id) + if r < 0 { + return nil, fmt.Errorf("failed to get monotonic timestamp: %d", syscall.Errno(-r)) + } + + entry.MonotonicTimestamp = uint64(monotonicUsec) + + var c *C.char + r = C.my_sd_journal_get_cursor(sd_journal_get_cursor, j.cjournal, &c) + if r < 0 { + return nil, fmt.Errorf("failed to get cursor: %d", syscall.Errno(-r)) + } + + entry.Cursor = C.GoString(c) + + // Implements the JOURNAL_FOREACH_DATA_RETVAL macro from journal-internal.h + var d unsafe.Pointer + var l C.size_t + C.my_sd_journal_restart_data(sd_journal_restart_data, j.cjournal) + for { + r = C.my_sd_journal_enumerate_data(sd_journal_enumerate_data, j.cjournal, &d, &l) + if r == 0 { + break + } + + if r < 0 { + return nil, fmt.Errorf("failed to read message field: %d", syscall.Errno(-r)) + } + + msg := C.GoStringN((*C.char)(d), C.int(l)) + kv := strings.SplitN(msg, "=", 2) + if len(kv) < 2 { + return nil, fmt.Errorf("failed to parse field") + } + + entry.Fields[kv[0]] = kv[1] + } + + return entry, nil +} + // SetDataThresold sets the data field size threshold for data returned by // GetData. To retrieve the complete data fields this threshold should be // turned off by setting it to 0, so that the library always returns the @@ -619,7 +742,28 @@ func (j *Journal) GetRealtimeUsec() (uint64, error) { j.mu.Unlock() if r < 0 { - return 0, fmt.Errorf("error getting timestamp for entry: %d", syscall.Errno(-r)) + return 0, fmt.Errorf("failed to get realtime timestamp: %d", syscall.Errno(-r)) + } + + return uint64(usec), nil +} + +// GetMonotonicUsec gets the monotonic timestamp of the current journal entry. +func (j *Journal) GetMonotonicUsec() (uint64, error) { + var usec C.uint64_t + var boot_id C.sd_id128_t + + sd_journal_get_monotonic_usec, err := j.getFunction("sd_journal_get_monotonic_usec") + if err != nil { + return 0, err + } + + j.mu.Lock() + r := C.my_sd_journal_get_monotonic_usec(sd_journal_get_monotonic_usec, j.cjournal, &usec, &boot_id) + j.mu.Unlock() + + if r < 0 { + return 0, fmt.Errorf("failed to get monotonic timestamp: %d", syscall.Errno(-r)) } return uint64(usec), nil diff --git a/sdjournal/journal_test.go b/sdjournal/journal_test.go index 0e82b243..74284517 100755 --- a/sdjournal/journal_test.go +++ b/sdjournal/journal_test.go @@ -175,3 +175,56 @@ func TestNewJournalFromDir(t *testing.T) { } j.Close() } + +func TestJournalGetEntry(t *testing.T) { + j, err := NewJournal() + if err != nil { + t.Fatalf("Error opening journal: %s", err) + } + + if j == nil { + t.Fatal("Got a nil journal") + } + + defer j.Close() + + j.FlushMatches() + + matchField := "TESTJOURNALGETENTRY" + matchValue := fmt.Sprintf("%d", time.Now().UnixNano()) + m := Match{Field: matchField, Value: matchValue} + err = j.AddMatch(m.String()) + if err != nil { + t.Fatalf("Error adding matches to journal: %s", err) + } + + want := fmt.Sprintf("test journal get entry message %s", time.Now()) + err = journal.Send(want, journal.PriInfo, map[string]string{matchField: matchValue}) + if err != nil { + t.Fatalf("Error writing to journal: %s", err) + } + + r := j.Wait(time.Duration(1) * time.Second) + if r < 0 { + t.Fatalf("Error waiting to journal") + } + + n, err := j.Next() + if err != nil { + t.Fatalf("Error reading to journal: %s", err) + } + + if n == 0 { + t.Fatalf("Error reading to journal: %s", io.EOF) + } + + entry, err := j.GetEntry() + if err != nil { + t.Fatalf("Error getting the entry to journal: %s", err) + } + + got := entry.Fields["MESSAGE"] + if got != want { + t.Fatalf("Bad result for entry.Fields[\"MESSAGE\"]: got %s, want %s", got, want) + } +} From 7fd54aa9100ce4c4c88b2483bf264fcd9d5b0468 Mon Sep 17 00:00:00 2001 From: Gorka Lerchundi Osa Date: Tue, 7 Jun 2016 17:45:24 +0200 Subject: [PATCH 2/2] sdjournal: export all user, trusted and address journal fields As GetEntry outputs every field in a string keyed map, it eases access to a concrete field without hardcoding it. Ref: https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html --- sdjournal/journal.go | 46 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/sdjournal/journal.go b/sdjournal/journal.go index 07b4f630..e8cc17c4 100644 --- a/sdjournal/journal.go +++ b/sdjournal/journal.go @@ -273,15 +273,45 @@ var libsystemdFunctions = map[string]unsafe.Pointer{} // Journal entry field strings which correspond to: // http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html const ( - SD_JOURNAL_FIELD_SYSTEMD_UNIT = "_SYSTEMD_UNIT" - SD_JOURNAL_FIELD_SYSLOG_IDENTIFIER = "SYSLOG_IDENTIFIER" + // User Journal Fields SD_JOURNAL_FIELD_MESSAGE = "MESSAGE" - SD_JOURNAL_FIELD_PID = "_PID" - SD_JOURNAL_FIELD_UID = "_UID" - SD_JOURNAL_FIELD_GID = "_GID" - SD_JOURNAL_FIELD_HOSTNAME = "_HOSTNAME" - SD_JOURNAL_FIELD_MACHINE_ID = "_MACHINE_ID" - SD_JOURNAL_FIELD_TRANSPORT = "_TRANSPORT" + SD_JOURNAL_FIELD_MESSAGE_ID = "MESSAGE_ID" + SD_JOURNAL_FIELD_PRIORITY = "PRIORITY" + SD_JOURNAL_FIELD_CODE_FILE = "CODE_FILE" + SD_JOURNAL_FIELD_CODE_LINE = "CODE_LINE" + SD_JOURNAL_FIELD_CODE_FUNC = "CODE_FUNC" + SD_JOURNAL_FIELD_ERRNO = "ERRNO" + SD_JOURNAL_FIELD_SYSLOG_FACILITY = "SYSLOG_FACILITY" + SD_JOURNAL_FIELD_SYSLOG_IDENTIFIER = "SYSLOG_IDENTIFIER" + SD_JOURNAL_FIELD_SYSLOG_PID = "SYSLOG_PID" + + // Trusted Journal Fields + SD_JOURNAL_FIELD_PID = "_PID" + SD_JOURNAL_FIELD_UID = "_UID" + SD_JOURNAL_FIELD_GID = "_GID" + SD_JOURNAL_FIELD_COMM = "_COMM" + SD_JOURNAL_FIELD_EXE = "_EXE" + SD_JOURNAL_FIELD_CMDLINE = "_CMDLINE" + SD_JOURNAL_FIELD_CAP_EFFECTIVE = "_CAP_EFFECTIVE" + SD_JOURNAL_FIELD_AUDIT_SESSION = "_AUDIT_SESSION" + SD_JOURNAL_FIELD_AUDIT_LOGINUID = "_AUDIT_LOGINUID" + SD_JOURNAL_FIELD_SYSTEMD_CGROUP = "_SYSTEMD_CGROUP" + SD_JOURNAL_FIELD_SYSTEMD_SESSION = "_SYSTEMD_SESSION" + SD_JOURNAL_FIELD_SYSTEMD_UNIT = "_SYSTEMD_UNIT" + SD_JOURNAL_FIELD_SYSTEMD_USER_UNIT = "_SYSTEMD_USER_UNIT" + SD_JOURNAL_FIELD_SYSTEMD_OWNER_UID = "_SYSTEMD_OWNER_UID" + SD_JOURNAL_FIELD_SYSTEMD_SLICE = "_SYSTEMD_SLICE" + SD_JOURNAL_FIELD_SELINUX_CONTEXT = "_SELINUX_CONTEXT" + SD_JOURNAL_FIELD_SOURCE_REALTIME_TIMESTAMP = "_SOURCE_REALTIME_TIMESTAMP" + SD_JOURNAL_FIELD_BOOT_ID = "_BOOT_ID" + SD_JOURNAL_FIELD_MACHINE_ID = "_MACHINE_ID" + SD_JOURNAL_FIELD_HOSTNAME = "_HOSTNAME" + SD_JOURNAL_FIELD_TRANSPORT = "_TRANSPORT" + + // Address Fields + SD_JOURNAL_FIELD_CURSOR = "__CURSOR" + SD_JOURNAL_FIELD_REALTIME_TIMESTAMP = "__REALTIME_TIMESTAMP" + SD_JOURNAL_FIELD_MONOTONIC_TIMESTAMP = "__MONOTONIC_TIMESTAMP" ) // Journal event constants