Skip to content

Commit 1357d57

Browse files
committed
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
1 parent e8cb11c commit 1357d57

File tree

2 files changed

+198
-1
lines changed

2 files changed

+198
-1
lines changed

sdjournal/journal.go

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
package sdjournal
2626

2727
// #include <systemd/sd-journal.h>
28+
// #include <systemd/sd-id128.h>
2829
// #include <stdlib.h>
2930
// #include <syslog.h>
3031
//
@@ -182,6 +183,15 @@ package sdjournal
182183
// }
183184
//
184185
// int
186+
// my_sd_journal_get_monotonic_usec(void *f, sd_journal *j, uint64_t *usec, sd_id128_t *boot_id)
187+
// {
188+
// int (*sd_journal_get_monotonic_usec)(sd_journal *, uint64_t *, sd_id128_t *);
189+
//
190+
// sd_journal_get_monotonic_usec = f;
191+
// return sd_journal_get_monotonic_usec(j, usec, boot_id);
192+
// }
193+
//
194+
// int
185195
// my_sd_journal_seek_head(void *f, sd_journal *j)
186196
// {
187197
// int (*sd_journal_seek_head)(sd_journal *);
@@ -227,6 +237,24 @@ package sdjournal
227237
// return sd_journal_wait(j, timeout_usec);
228238
// }
229239
//
240+
// void
241+
// my_sd_journal_restart_data(void *f, sd_journal *j)
242+
// {
243+
// void (*sd_journal_restart_data)(sd_journal *);
244+
//
245+
// sd_journal_restart_data = f;
246+
// sd_journal_restart_data(j);
247+
// }
248+
//
249+
// int
250+
// my_sd_journal_enumerate_data(void *f, sd_journal *j, const void **data, size_t *length)
251+
// {
252+
// int (*sd_journal_enumerate_data)(sd_journal *, const void **, size_t *);
253+
//
254+
// sd_journal_enumerate_data = f;
255+
// return sd_journal_enumerate_data(j, data, length);
256+
// }
257+
//
230258
import "C"
231259
import (
232260
"fmt"
@@ -288,6 +316,14 @@ type Journal struct {
288316
lib *dlopen.LibHandle
289317
}
290318

319+
// JournalEntry represents all fields of a journal entry plus address fields.
320+
type JournalEntry struct {
321+
Fields map[string]string
322+
Cursor string
323+
RealtimeTimestamp uint64
324+
MonotonicTimestamp uint64
325+
}
326+
291327
// Match is a convenience wrapper to describe filters supplied to AddMatch.
292328
type Match struct {
293329
Field string
@@ -583,6 +619,93 @@ func (j *Journal) GetDataValue(field string) (string, error) {
583619
return strings.SplitN(val, "=", 2)[1], nil
584620
}
585621

622+
// GetEntry returns a full representation of a journal entry with
623+
// all key-value pairs of data as well as address fields (cursor, realtime
624+
// timestamp and monotonic timestamp)
625+
func (j *Journal) GetEntry() (*JournalEntry, error) {
626+
sd_journal_get_realtime_usec, err := j.getFunction("sd_journal_get_realtime_usec")
627+
if err != nil {
628+
return nil, err
629+
}
630+
631+
sd_journal_get_monotonic_usec, err := j.getFunction("sd_journal_get_monotonic_usec")
632+
if err != nil {
633+
return nil, err
634+
}
635+
636+
sd_journal_get_cursor, err := j.getFunction("sd_journal_get_cursor")
637+
if err != nil {
638+
return nil, err
639+
}
640+
641+
sd_journal_restart_data, err := j.getFunction("sd_journal_restart_data")
642+
if err != nil {
643+
return nil, err
644+
}
645+
646+
sd_journal_enumerate_data, err := j.getFunction("sd_journal_enumerate_data")
647+
if err != nil {
648+
return nil, err
649+
}
650+
651+
j.mu.Lock()
652+
defer j.mu.Unlock()
653+
654+
var r C.int
655+
entry := &JournalEntry{Fields: make(map[string]string)}
656+
657+
var realtimeUsec C.uint64_t
658+
r = C.my_sd_journal_get_realtime_usec(sd_journal_get_realtime_usec, j.cjournal, &realtimeUsec)
659+
if r < 0 {
660+
return nil, fmt.Errorf("failed to get realtime timestamp: %d", syscall.Errno(-r))
661+
}
662+
663+
entry.RealtimeTimestamp = uint64(realtimeUsec)
664+
665+
var monotonicUsec C.uint64_t
666+
var boot_id C.sd_id128_t
667+
668+
r = C.my_sd_journal_get_monotonic_usec(sd_journal_get_monotonic_usec, j.cjournal, &monotonicUsec, &boot_id)
669+
if r < 0 {
670+
return nil, fmt.Errorf("failed to get monotonic timestamp: %d", syscall.Errno(-r))
671+
}
672+
673+
entry.MonotonicTimestamp = uint64(monotonicUsec)
674+
675+
var c *C.char
676+
r = C.my_sd_journal_get_cursor(sd_journal_get_cursor, j.cjournal, &c)
677+
if r < 0 {
678+
return nil, fmt.Errorf("failed to get cursor: %d", syscall.Errno(-r))
679+
}
680+
681+
entry.Cursor = C.GoString(c)
682+
683+
// Implements the JOURNAL_FOREACH_DATA_RETVAL macro from journal-internal.h
684+
var d unsafe.Pointer
685+
var l C.size_t
686+
C.my_sd_journal_restart_data(sd_journal_restart_data, j.cjournal)
687+
for {
688+
r = C.my_sd_journal_enumerate_data(sd_journal_enumerate_data, j.cjournal, &d, &l)
689+
if r == 0 {
690+
break
691+
}
692+
693+
if r < 0 {
694+
return nil, fmt.Errorf("failed to read message field: %d", syscall.Errno(-r))
695+
}
696+
697+
msg := C.GoStringN((*C.char)(d), C.int(l))
698+
kv := strings.SplitN(msg, "=", 2)
699+
if len(kv) < 2 {
700+
return nil, fmt.Errorf("failed to parse field")
701+
}
702+
703+
entry.Fields[kv[0]] = kv[1]
704+
}
705+
706+
return entry, nil
707+
}
708+
586709
// SetDataThresold sets the data field size threshold for data returned by
587710
// GetData. To retrieve the complete data fields this threshold should be
588711
// turned off by setting it to 0, so that the library always returns the
@@ -619,7 +742,28 @@ func (j *Journal) GetRealtimeUsec() (uint64, error) {
619742
j.mu.Unlock()
620743

621744
if r < 0 {
622-
return 0, fmt.Errorf("error getting timestamp for entry: %d", syscall.Errno(-r))
745+
return 0, fmt.Errorf("failed to get realtime timestamp: %d", syscall.Errno(-r))
746+
}
747+
748+
return uint64(usec), nil
749+
}
750+
751+
// GetMonotonicUsec gets the monotonic timestamp of the current journal entry.
752+
func (j *Journal) GetMonotonicUsec() (uint64, error) {
753+
var usec C.uint64_t
754+
var boot_id C.sd_id128_t
755+
756+
sd_journal_get_monotonic_usec, err := j.getFunction("sd_journal_get_monotonic_usec")
757+
if err != nil {
758+
return 0, err
759+
}
760+
761+
j.mu.Lock()
762+
r := C.my_sd_journal_get_monotonic_usec(sd_journal_get_monotonic_usec, j.cjournal, &usec, &boot_id)
763+
j.mu.Unlock()
764+
765+
if r < 0 {
766+
return 0, fmt.Errorf("failed to get monotonic timestamp: %d", syscall.Errno(-r))
623767
}
624768

625769
return uint64(usec), nil

sdjournal/journal_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,56 @@ func TestNewJournalFromDir(t *testing.T) {
175175
}
176176
j.Close()
177177
}
178+
179+
func TestJournalGetEntry(t *testing.T) {
180+
j, err := NewJournal()
181+
if err != nil {
182+
t.Fatalf("Error opening journal: %s", err)
183+
}
184+
185+
if j == nil {
186+
t.Fatal("Got a nil journal")
187+
}
188+
189+
defer j.Close()
190+
191+
j.FlushMatches()
192+
193+
matchField := "TESTJOURNALGETENTRY"
194+
matchValue := fmt.Sprintf("%d", time.Now().UnixNano())
195+
m := Match{Field: matchField, Value: matchValue}
196+
err = j.AddMatch(m.String())
197+
if err != nil {
198+
t.Fatalf("Error adding matches to journal: %s", err)
199+
}
200+
201+
want := fmt.Sprintf("test journal get entry message %s", time.Now())
202+
err = journal.Send(want, journal.PriInfo, map[string]string{matchField: matchValue})
203+
if err != nil {
204+
t.Fatalf("Error writing to journal: %s", err)
205+
}
206+
207+
r := j.Wait(time.Duration(1) * time.Second)
208+
if r < 0 {
209+
t.Fatalf("Error waiting to journal")
210+
}
211+
212+
n, err := j.Next()
213+
if err != nil {
214+
t.Fatalf("Error reading to journal: %s", err)
215+
}
216+
217+
if n == 0 {
218+
t.Fatalf("Error reading to journal: %s", io.EOF)
219+
}
220+
221+
entry, err := j.GetEntry()
222+
if err != nil {
223+
t.Fatalf("Error getting the entry to journal: %s", err)
224+
}
225+
226+
got := entry.Fields["MESSAGE"]
227+
if got != want {
228+
t.Fatalf("Bad result for entry.Fields[\"MESSAGE\"]: got %s, want %s", got, want)
229+
}
230+
}

0 commit comments

Comments
 (0)