diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/journal/browse.html | 77 | ||||
-rw-r--r-- | src/journal/journal-gatewayd.c | 38 | ||||
-rw-r--r-- | src/journal/libsystemd-journal.sym | 5 | ||||
-rw-r--r-- | src/journal/sd-journal.c | 88 | ||||
-rw-r--r-- | src/journal/test-journal-stream.c | 16 | ||||
-rw-r--r-- | src/systemd/sd-journal.h | 1 |
6 files changed, 213 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="|<" 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); diff --git a/src/systemd/sd-journal.h b/src/systemd/sd-journal.h index 6c18c89425..9a2aab923a 100644 --- a/src/systemd/sd-journal.h +++ b/src/systemd/sd-journal.h @@ -104,6 +104,7 @@ int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec); int sd_journal_seek_cursor(sd_journal *j, const char *cursor); int sd_journal_get_cursor(sd_journal *j, char **cursor); +int sd_journal_test_cursor(sd_journal *j, const char *cursor); int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to); int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, const sd_id128_t boot_id, uint64_t *from, uint64_t *to); |