summaryrefslogtreecommitdiff
path: root/src/journal
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2012-10-10 22:39:45 +0200
committerLennart Poettering <lennart@poettering.net>2012-10-10 22:41:03 +0200
commitc6511e859c35b12de4e6fb5f58d7258d9de3b8f2 (patch)
treeedec1b14c70ba7bec4869b4ef6f9be8bf16f6a75 /src/journal
parent934a316cbf68c46f05b7ce0b35fc312eca786747 (diff)
journal: when browsing the journal via browse.html allow clicking on entries to show their details
Diffstat (limited to 'src/journal')
-rw-r--r--src/journal/browse.html77
-rw-r--r--src/journal/journal-gatewayd.c38
-rw-r--r--src/journal/libsystemd-journal.sym5
-rw-r--r--src/journal/sd-journal.c88
-rw-r--r--src/journal/test-journal-stream.c16
5 files changed, 212 insertions, 12 deletions
diff --git a/src/journal/browse.html b/src/journal/browse.html
index 5ceca26a5d..068b296da1 100644
--- a/src/journal/browse.html
+++ b/src/journal/browse.html
@@ -4,7 +4,7 @@
<title>Journal</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css">
- div#divlogs {
+ div#divlogs, div#diventry {
font-family: monospace;
font-size: 8pt;
background-color: #ffffff;
@@ -15,6 +15,12 @@
white-space: nowrap;
overflow-x: scroll;
}
+ div#diventry {
+ display: none;
+ }
+ div#divlogs {
+ display: block;
+ }
body {
background-color: #ededed;
color: #313739;
@@ -34,17 +40,41 @@
td.message {
padding-left: 5px;
}
+ td.message > a:link, td.message > a:visited {
+ text-decoration: none;
+ color: #313739;
+ }
td.message-error {
padding-left: 5px;
color: red;
font-weight: bold;
}
+ td.message-error > a:link, td.message-error > a:visited {
+ text-decoration: none;
+ color: red;
+ }
td.message-highlight {
padding-left: 5px;
font-weight: bold;
}
- table#tablelogs {
- border-collapse:collapse;
+ td.message-highlight > a:link, td.message-highlight > a:visited {
+ text-decoration: none;
+ color: #313739;
+ }
+ td > a:hover, td > a:active {
+ text-decoration: underline;
+ color: #c13739;
+ }
+ table#tablelogs, table#tableentry {
+ border-collapse: collapse;
+ }
+ td.field {
+ text-align: right;
+ border-right: 1px dotted lightgrey;
+ padding-right: 5px;
+ }
+ td.data {
+ padding-left: 5px;
}
</style>
</head>
@@ -65,6 +95,8 @@
<div id="showing"></div>
<div id="divlogs"><table id="tablelogs"></table></div>
+ <a name="entry"></a>
+ <div id="diventry"><table id="tableentry"></table></div>
<form>
<input id="head" type="button" value="|&lt;" onclick="entriesLoadHead();"/>
@@ -271,7 +303,7 @@
else if (d.SYSLOG_PID != undefined)
buf += "[" + d.SYSLOG_PID + "]";
- buf += '</td><td class="' + clazz + '">';
+ buf += '</td><td class="' + clazz + '"><a href="#entry" onclick="onMessageClick(\'' + lc + '\');">';
if (d.MESSAGE == null)
buf += "[blob data]";
@@ -280,10 +312,10 @@
else
buf += d.MESSAGE;
- buf += '</td></tr>';
+ buf += '</a></td></tr>';
}
- logs.innerHTML = buf + '</tbody>';
+ logs.innerHTML = '<tbody>' + buf + '</tbody>';
if (fc != null)
first_cursor = fc;
@@ -293,12 +325,41 @@
function entriesMore() {
setNEntries(getNEntries() + 10);
- entriesLoad("");
+ entriesLoad(first_cursor);
}
function entriesLess() {
setNEntries(getNEntries() - 10);
- entriesLoad("");
+ entriesLoad(first_cursor);
+ }
+
+ function onResultMessageClick(event) {
+ if ((event.currentTarget.readyState != 4) ||
+ (event.currentTarget.status != 200 && event.currentTarget.status != 0))
+ return;
+
+ var d = JSON.parse(event.currentTarget.responseText);
+
+ document.getElementById("diventry").style.display = "block";
+
+ entry = document.getElementById("tableentry");
+
+ var buf = "";
+
+ for (var key in d){
+ buf += '<tr><td class="field">' + key + '</td><td class="data">' + d[key] + '</td></tr>';
+ }
+
+ entry.innerHTML = '<tbody>' + buf + '</tbody>';
+ }
+
+ function onMessageClick(t) {
+ var request = new XMLHttpRequest();
+ request.open("GET", "/entries?discrete");
+ request.onreadystatechange = onResultMessageClick;
+ request.setRequestHeader("Accept", "application/json");
+ request.setRequestHeader("Range", "entries=" + t + ":0:1");
+ request.send(null);
}
machineLoad();
diff --git a/src/journal/journal-gatewayd.c b/src/journal/journal-gatewayd.c
index 274ef5ffeb..33dda266b6 100644
--- a/src/journal/journal-gatewayd.c
+++ b/src/journal/journal-gatewayd.c
@@ -49,6 +49,7 @@ typedef struct RequestMeta {
int argument_parse_error;
bool follow;
+ bool discrete;
} RequestMeta;
static const char* const mime_types[_OUTPUT_MODE_MAX] = {
@@ -205,6 +206,19 @@ static ssize_t request_reader_entries(
return MHD_CONTENT_READER_END_OF_STREAM;
}
+ if (m->discrete) {
+ assert(m->cursor);
+
+ r = sd_journal_test_cursor(m->journal, m->cursor);
+ if (r < 0) {
+ log_error("Failed to test cursor: %s", strerror(-r));
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ if (r == 0)
+ return MHD_CONTENT_READER_END_OF_STREAM;
+ }
+
pos -= m->size;
m->delta += m->size;
@@ -380,6 +394,22 @@ static int request_parse_arguments_iterator(
return MHD_YES;
}
+ if (streq(key, "discrete")) {
+ if (isempty(value)) {
+ m->discrete = true;
+ return MHD_YES;
+ }
+
+ r = parse_boolean(value);
+ if (r < 0) {
+ m->argument_parse_error = r;
+ return MHD_NO;
+ }
+
+ m->discrete = r;
+ return MHD_YES;
+ }
+
p = strjoin(key, "=", strempty(value), NULL);
if (!p) {
m->argument_parse_error = log_oom();
@@ -436,6 +466,14 @@ static int request_handler_entries(
if (request_parse_arguments(m, connection) < 0)
return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
+ if (m->discrete) {
+ if (!m->cursor)
+ return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
+
+ m->n_entries = 1;
+ m->n_entries_set = true;
+ }
+
if (m->cursor)
r = sd_journal_seek_cursor(m->journal, m->cursor);
else if (m->n_skip >= 0)
diff --git a/src/journal/libsystemd-journal.sym b/src/journal/libsystemd-journal.sym
index 7dfae2625f..77de862dcb 100644
--- a/src/journal/libsystemd-journal.sym
+++ b/src/journal/libsystemd-journal.sym
@@ -75,3 +75,8 @@ LIBSYSTEMD_JOURNAL_190 {
global:
sd_journal_get_usage;
} LIBSYSTEMD_JOURNAL_188;
+
+LIBSYSTEMD_JOURNAL_195 {
+global:
+ sd_journal_test_cursor;
+} LIBSYSTEMD_JOURNAL_190;
diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c
index 9e594a2cff..88b382f4cc 100644
--- a/src/journal/sd-journal.c
+++ b/src/journal/sd-journal.c
@@ -951,9 +951,8 @@ _public_ int sd_journal_get_cursor(sd_journal *j, char **cursor) {
}
_public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
- char *w;
+ char *w, *state;
size_t l;
- char *state;
unsigned long long seqnum, monotonic, realtime, xor_hash;
bool
seqnum_id_set = false,
@@ -966,7 +965,7 @@ _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
if (!j)
return -EINVAL;
- if (!cursor)
+ if (isempty(cursor))
return -EINVAL;
FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
@@ -1057,6 +1056,89 @@ _public_ int sd_journal_seek_cursor(sd_journal *j, const char *cursor) {
return 0;
}
+_public_ int sd_journal_test_cursor(sd_journal *j, const char *cursor) {
+ int r;
+ char *w, *state;
+ size_t l;
+ Object *o;
+
+ if (!j)
+ return -EINVAL;
+ if (isempty(cursor))
+ return -EINVAL;
+
+ if (!j->current_file || j->current_file->current_offset <= 0)
+ return -EADDRNOTAVAIL;
+
+ r = journal_file_move_to_object(j->current_file, OBJECT_ENTRY, j->current_file->current_offset, &o);
+ if (r < 0)
+ return r;
+
+ FOREACH_WORD_SEPARATOR(w, l, cursor, ";", state) {
+ _cleanup_free_ char *item = NULL;
+ sd_id128_t id;
+ unsigned long long ll;
+ int k = 0;
+
+ if (l < 2 || w[1] != '=')
+ return -EINVAL;
+
+ item = strndup(w, l);
+ if (!item)
+ return -ENOMEM;
+
+ switch (w[0]) {
+
+ case 's':
+ k = sd_id128_from_string(item+2, &id);
+ if (k < 0)
+ return k;
+ if (!sd_id128_equal(id, j->current_file->header->seqnum_id))
+ return 0;
+ break;
+
+ case 'i':
+ if (sscanf(item+2, "%llx", &ll) != 1)
+ return -EINVAL;
+ if (ll != le64toh(o->entry.seqnum))
+ return 0;
+ break;
+
+ case 'b':
+ k = sd_id128_from_string(item+2, &id);
+ if (k < 0)
+ return k;
+ if (!sd_id128_equal(id, o->entry.boot_id))
+ return 0;
+ break;
+
+ case 'm':
+ if (sscanf(item+2, "%llx", &ll) != 1)
+ return -EINVAL;
+ if (ll != le64toh(o->entry.monotonic))
+ return 0;
+ break;
+
+ case 't':
+ if (sscanf(item+2, "%llx", &ll) != 1)
+ return -EINVAL;
+ if (ll != le64toh(o->entry.realtime))
+ return 0;
+ break;
+
+ case 'x':
+ if (sscanf(item+2, "%llx", &ll) != 1)
+ return -EINVAL;
+ if (ll != le64toh(o->entry.xor_hash))
+ return 0;
+ break;
+ }
+ }
+
+ return 1;
+}
+
+
_public_ int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec) {
if (!j)
return -EINVAL;
diff --git a/src/journal/test-journal-stream.c b/src/journal/test-journal-stream.c
index 707dcc178b..caea2b2c63 100644
--- a/src/journal/test-journal-stream.c
+++ b/src/journal/test-journal-stream.c
@@ -39,7 +39,7 @@ static void verify_contents(sd_journal *j, unsigned skip) {
i = 0;
SD_JOURNAL_FOREACH(j) {
const void *d;
- char *k;
+ char *k, *c;
size_t l;
unsigned u;
@@ -61,6 +61,10 @@ static void verify_contents(sd_journal *j, unsigned skip) {
}
free(k);
+
+ assert_se(sd_journal_get_cursor(j, &c) >= 0);
+ assert_se(sd_journal_test_cursor(j, c) > 0);
+ free(c);
}
if (skip > 0)
@@ -122,17 +126,27 @@ int main(int argc, char *argv[]) {
SD_JOURNAL_FOREACH_BACKWARDS(j) {
const void *d;
size_t l;
+ char *c;
assert_se(sd_journal_get_data(j, "NUMBER", &d, &l) >= 0);
printf("\t%.*s\n", (int) l, (const char*) d);
+
+ assert_se(sd_journal_get_cursor(j, &c) >= 0);
+ assert_se(sd_journal_test_cursor(j, c) > 0);
+ free(c);
}
SD_JOURNAL_FOREACH(j) {
const void *d;
size_t l;
+ char *c;
assert_se(sd_journal_get_data(j, "NUMBER", &d, &l) >= 0);
printf("\t%.*s\n", (int) l, (const char*) d);
+
+ assert_se(sd_journal_get_cursor(j, &c) >= 0);
+ assert_se(sd_journal_test_cursor(j, c) > 0);
+ free(c);
}
sd_journal_flush_matches(j);