Skip to content

Commit

Permalink
sdjournal: add GetEntry method to retrieve all fields plus address fi…
Browse files Browse the repository at this point in the history
…elds

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`.

Supersedes #136.
  • Loading branch information
glerchundi committed May 26, 2016
1 parent b9b5f59 commit eacd850
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 10 deletions.
167 changes: 157 additions & 10 deletions sdjournal/journal.go
Expand Up @@ -25,6 +25,7 @@
package sdjournal

// #include <systemd/sd-journal.h>
// #include <systemd/sd-id128.h>
// #include <stdlib.h>
// #include <syslog.h>
//
Expand Down Expand Up @@ -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 *);
Expand Down Expand Up @@ -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"
Expand All @@ -245,15 +273,18 @@ 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"
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_SYSTEMD_UNIT = "_SYSTEMD_UNIT"
SD_JOURNAL_FIELD_SYSLOG_IDENTIFIER = "SYSLOG_IDENTIFIER"
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_REALTIME_TIMESTAMP = "__REALTIME_TIMESTAMP"
SD_JOURNAL_FIELD_MONOTONIC_TIMESTAMP = "__MONOTONIC_TIMESTAMP"
SD_JOURNAL_FIELD_CURSOR = "__CURSOR"
)

// Journal event constants
Expand Down Expand Up @@ -288,6 +319,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
Expand Down Expand Up @@ -583,6 +622,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", 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
Expand Down Expand Up @@ -619,7 +745,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
Expand Down
45 changes: 45 additions & 0 deletions sdjournal/journal_test.go
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"io"
"os"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -150,3 +151,47 @@ func TestJournalCursorGetSeekAndTest(t *testing.T) {
t.Fatalf("Error testing cursor to journal: %s", err)
}
}

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")
}

m := Match{Field: "_CMDLINE", Value: strings.Join(os.Args, " ")}
j.AddMatch(m.String())

want := fmt.Sprintf("test message %s", time.Now())
err = journal.Print(journal.PriInfo, want)
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)
}
}

0 comments on commit eacd850

Please sign in to comment.