Skip to content

Commit ff577b2

Browse files
committed
Merge pull request #164 from glerchundi/add-getjournalentry-function
sdjournal: add GetEntry method to retrieve all fields
2 parents e8cb11c + 7fd54aa commit ff577b2

File tree

2 files changed

+236
-9
lines changed

2 files changed

+236
-9
lines changed

sdjournal/journal.go

Lines changed: 183 additions & 9 deletions
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"
@@ -245,15 +273,45 @@ var libsystemdFunctions = map[string]unsafe.Pointer{}
245273
// Journal entry field strings which correspond to:
246274
// http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
247275
const (
248-
SD_JOURNAL_FIELD_SYSTEMD_UNIT = "_SYSTEMD_UNIT"
249-
SD_JOURNAL_FIELD_SYSLOG_IDENTIFIER = "SYSLOG_IDENTIFIER"
276+
// User Journal Fields
250277
SD_JOURNAL_FIELD_MESSAGE = "MESSAGE"
251-
SD_JOURNAL_FIELD_PID = "_PID"
252-
SD_JOURNAL_FIELD_UID = "_UID"
253-
SD_JOURNAL_FIELD_GID = "_GID"
254-
SD_JOURNAL_FIELD_HOSTNAME = "_HOSTNAME"
255-
SD_JOURNAL_FIELD_MACHINE_ID = "_MACHINE_ID"
256-
SD_JOURNAL_FIELD_TRANSPORT = "_TRANSPORT"
278+
SD_JOURNAL_FIELD_MESSAGE_ID = "MESSAGE_ID"
279+
SD_JOURNAL_FIELD_PRIORITY = "PRIORITY"
280+
SD_JOURNAL_FIELD_CODE_FILE = "CODE_FILE"
281+
SD_JOURNAL_FIELD_CODE_LINE = "CODE_LINE"
282+
SD_JOURNAL_FIELD_CODE_FUNC = "CODE_FUNC"
283+
SD_JOURNAL_FIELD_ERRNO = "ERRNO"
284+
SD_JOURNAL_FIELD_SYSLOG_FACILITY = "SYSLOG_FACILITY"
285+
SD_JOURNAL_FIELD_SYSLOG_IDENTIFIER = "SYSLOG_IDENTIFIER"
286+
SD_JOURNAL_FIELD_SYSLOG_PID = "SYSLOG_PID"
287+
288+
// Trusted Journal Fields
289+
SD_JOURNAL_FIELD_PID = "_PID"
290+
SD_JOURNAL_FIELD_UID = "_UID"
291+
SD_JOURNAL_FIELD_GID = "_GID"
292+
SD_JOURNAL_FIELD_COMM = "_COMM"
293+
SD_JOURNAL_FIELD_EXE = "_EXE"
294+
SD_JOURNAL_FIELD_CMDLINE = "_CMDLINE"
295+
SD_JOURNAL_FIELD_CAP_EFFECTIVE = "_CAP_EFFECTIVE"
296+
SD_JOURNAL_FIELD_AUDIT_SESSION = "_AUDIT_SESSION"
297+
SD_JOURNAL_FIELD_AUDIT_LOGINUID = "_AUDIT_LOGINUID"
298+
SD_JOURNAL_FIELD_SYSTEMD_CGROUP = "_SYSTEMD_CGROUP"
299+
SD_JOURNAL_FIELD_SYSTEMD_SESSION = "_SYSTEMD_SESSION"
300+
SD_JOURNAL_FIELD_SYSTEMD_UNIT = "_SYSTEMD_UNIT"
301+
SD_JOURNAL_FIELD_SYSTEMD_USER_UNIT = "_SYSTEMD_USER_UNIT"
302+
SD_JOURNAL_FIELD_SYSTEMD_OWNER_UID = "_SYSTEMD_OWNER_UID"
303+
SD_JOURNAL_FIELD_SYSTEMD_SLICE = "_SYSTEMD_SLICE"
304+
SD_JOURNAL_FIELD_SELINUX_CONTEXT = "_SELINUX_CONTEXT"
305+
SD_JOURNAL_FIELD_SOURCE_REALTIME_TIMESTAMP = "_SOURCE_REALTIME_TIMESTAMP"
306+
SD_JOURNAL_FIELD_BOOT_ID = "_BOOT_ID"
307+
SD_JOURNAL_FIELD_MACHINE_ID = "_MACHINE_ID"
308+
SD_JOURNAL_FIELD_HOSTNAME = "_HOSTNAME"
309+
SD_JOURNAL_FIELD_TRANSPORT = "_TRANSPORT"
310+
311+
// Address Fields
312+
SD_JOURNAL_FIELD_CURSOR = "__CURSOR"
313+
SD_JOURNAL_FIELD_REALTIME_TIMESTAMP = "__REALTIME_TIMESTAMP"
314+
SD_JOURNAL_FIELD_MONOTONIC_TIMESTAMP = "__MONOTONIC_TIMESTAMP"
257315
)
258316

259317
// Journal event constants
@@ -288,6 +346,14 @@ type Journal struct {
288346
lib *dlopen.LibHandle
289347
}
290348

349+
// JournalEntry represents all fields of a journal entry plus address fields.
350+
type JournalEntry struct {
351+
Fields map[string]string
352+
Cursor string
353+
RealtimeTimestamp uint64
354+
MonotonicTimestamp uint64
355+
}
356+
291357
// Match is a convenience wrapper to describe filters supplied to AddMatch.
292358
type Match struct {
293359
Field string
@@ -583,6 +649,93 @@ func (j *Journal) GetDataValue(field string) (string, error) {
583649
return strings.SplitN(val, "=", 2)[1], nil
584650
}
585651

652+
// GetEntry returns a full representation of a journal entry with
653+
// all key-value pairs of data as well as address fields (cursor, realtime
654+
// timestamp and monotonic timestamp)
655+
func (j *Journal) GetEntry() (*JournalEntry, error) {
656+
sd_journal_get_realtime_usec, err := j.getFunction("sd_journal_get_realtime_usec")
657+
if err != nil {
658+
return nil, err
659+
}
660+
661+
sd_journal_get_monotonic_usec, err := j.getFunction("sd_journal_get_monotonic_usec")
662+
if err != nil {
663+
return nil, err
664+
}
665+
666+
sd_journal_get_cursor, err := j.getFunction("sd_journal_get_cursor")
667+
if err != nil {
668+
return nil, err
669+
}
670+
671+
sd_journal_restart_data, err := j.getFunction("sd_journal_restart_data")
672+
if err != nil {
673+
return nil, err
674+
}
675+
676+
sd_journal_enumerate_data, err := j.getFunction("sd_journal_enumerate_data")
677+
if err != nil {
678+
return nil, err
679+
}
680+
681+
j.mu.Lock()
682+
defer j.mu.Unlock()
683+
684+
var r C.int
685+
entry := &JournalEntry{Fields: make(map[string]string)}
686+
687+
var realtimeUsec C.uint64_t
688+
r = C.my_sd_journal_get_realtime_usec(sd_journal_get_realtime_usec, j.cjournal, &realtimeUsec)
689+
if r < 0 {
690+
return nil, fmt.Errorf("failed to get realtime timestamp: %d", syscall.Errno(-r))
691+
}
692+
693+
entry.RealtimeTimestamp = uint64(realtimeUsec)
694+
695+
var monotonicUsec C.uint64_t
696+
var boot_id C.sd_id128_t
697+
698+
r = C.my_sd_journal_get_monotonic_usec(sd_journal_get_monotonic_usec, j.cjournal, &monotonicUsec, &boot_id)
699+
if r < 0 {
700+
return nil, fmt.Errorf("failed to get monotonic timestamp: %d", syscall.Errno(-r))
701+
}
702+
703+
entry.MonotonicTimestamp = uint64(monotonicUsec)
704+
705+
var c *C.char
706+
r = C.my_sd_journal_get_cursor(sd_journal_get_cursor, j.cjournal, &c)
707+
if r < 0 {
708+
return nil, fmt.Errorf("failed to get cursor: %d", syscall.Errno(-r))
709+
}
710+
711+
entry.Cursor = C.GoString(c)
712+
713+
// Implements the JOURNAL_FOREACH_DATA_RETVAL macro from journal-internal.h
714+
var d unsafe.Pointer
715+
var l C.size_t
716+
C.my_sd_journal_restart_data(sd_journal_restart_data, j.cjournal)
717+
for {
718+
r = C.my_sd_journal_enumerate_data(sd_journal_enumerate_data, j.cjournal, &d, &l)
719+
if r == 0 {
720+
break
721+
}
722+
723+
if r < 0 {
724+
return nil, fmt.Errorf("failed to read message field: %d", syscall.Errno(-r))
725+
}
726+
727+
msg := C.GoStringN((*C.char)(d), C.int(l))
728+
kv := strings.SplitN(msg, "=", 2)
729+
if len(kv) < 2 {
730+
return nil, fmt.Errorf("failed to parse field")
731+
}
732+
733+
entry.Fields[kv[0]] = kv[1]
734+
}
735+
736+
return entry, nil
737+
}
738+
586739
// SetDataThresold sets the data field size threshold for data returned by
587740
// GetData. To retrieve the complete data fields this threshold should be
588741
// turned off by setting it to 0, so that the library always returns the
@@ -619,7 +772,28 @@ func (j *Journal) GetRealtimeUsec() (uint64, error) {
619772
j.mu.Unlock()
620773

621774
if r < 0 {
622-
return 0, fmt.Errorf("error getting timestamp for entry: %d", syscall.Errno(-r))
775+
return 0, fmt.Errorf("failed to get realtime timestamp: %d", syscall.Errno(-r))
776+
}
777+
778+
return uint64(usec), nil
779+
}
780+
781+
// GetMonotonicUsec gets the monotonic timestamp of the current journal entry.
782+
func (j *Journal) GetMonotonicUsec() (uint64, error) {
783+
var usec C.uint64_t
784+
var boot_id C.sd_id128_t
785+
786+
sd_journal_get_monotonic_usec, err := j.getFunction("sd_journal_get_monotonic_usec")
787+
if err != nil {
788+
return 0, err
789+
}
790+
791+
j.mu.Lock()
792+
r := C.my_sd_journal_get_monotonic_usec(sd_journal_get_monotonic_usec, j.cjournal, &usec, &boot_id)
793+
j.mu.Unlock()
794+
795+
if r < 0 {
796+
return 0, fmt.Errorf("failed to get monotonic timestamp: %d", syscall.Errno(-r))
623797
}
624798

625799
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)