mirror of
https://github.com/systemd/systemd.git
synced 2024-11-24 02:33:36 +08:00
journal: when browsing the journal via browse.html allow clicking on entries to show their details
This commit is contained in:
parent
934a316cbf
commit
c6511e859c
@ -580,7 +580,8 @@ MANPAGES_ALIAS = \
|
||||
man/sd_journal_seek_tail.3 \
|
||||
man/sd_journal_seek_monotonic_usec.3 \
|
||||
man/sd_journal_seek_realtime_usec.3 \
|
||||
man/sd_journal_seek_cursor.3
|
||||
man/sd_journal_seek_cursor.3 \
|
||||
man/sd_journal_test_cursor.3
|
||||
|
||||
man/reboot.8: man/halt.8
|
||||
man/poweroff.8: man/halt.8
|
||||
@ -649,6 +650,7 @@ man/sd_journal_seek_tail.3: man/sd_journal_seek_head.3
|
||||
man/sd_journal_seek_monotonic_usec.3: man/sd_journal_seek_head.3
|
||||
man/sd_journal_seek_realtime_usec.3: man/sd_journal_seek_head.3
|
||||
man/sd_journal_seek_cursor.3: man/sd_journal_seek_head.3
|
||||
man/sd_journal_test_cursor.3: man/sd_journal_get_cursor.3
|
||||
|
||||
XML_FILES = \
|
||||
${patsubst %.1,%.xml,${patsubst %.3,%.xml,${patsubst %.5,%.xml,${patsubst %.7,%.xml,${patsubst %.8,%.xml,$(MANPAGES)}}}}}
|
||||
|
@ -44,7 +44,8 @@
|
||||
|
||||
<refnamediv>
|
||||
<refname>sd_journal_get_cursor</refname>
|
||||
<refpurpose>Get cursor string for the current journal entry</refpurpose>
|
||||
<refname>sd_journal_test_cursor</refname>
|
||||
<refpurpose>Get cursor string for or test cursor string against the current journal entry</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
@ -57,6 +58,12 @@
|
||||
<paramdef>char ** <parameter>cursor</parameter></paramdef>
|
||||
</funcprototype>
|
||||
|
||||
<funcprototype>
|
||||
<funcdef>int <function>sd_journal_test_cursor</function></funcdef>
|
||||
<paramdef>sd_journal* <parameter>j</parameter></paramdef>
|
||||
<paramdef>const char * <parameter>cursor</parameter></paramdef>
|
||||
</funcprototype>
|
||||
|
||||
</funcsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
@ -66,7 +73,7 @@
|
||||
<para><function>sd_journal_get_cursor()</function>
|
||||
returns a cursor string for the current journal
|
||||
entry. A cursor is a serialization of the current
|
||||
journal position in text form. The string only
|
||||
journal position formatted as text. The string only
|
||||
contains printable characters and can be passed around
|
||||
in text form. The cursor identifies a journal entry
|
||||
globally and in a stable way and may be used to later
|
||||
@ -77,16 +84,33 @@
|
||||
without the specific entry being available locally
|
||||
will seek to the next closest (in terms of time)
|
||||
available entry. The call takes two arguments: a
|
||||
journal context object and a pointer to a
|
||||
string pointer where the cursor string will be
|
||||
placed. The string is allocated via libc <citerefentry><refentrytitle>malloc</refentrytitle><manvolnum>3</manvolnum></citerefentry> and should
|
||||
be freed after use with
|
||||
journal context object and a pointer to a string
|
||||
pointer where the cursor string will be placed. The
|
||||
string is allocated via libc
|
||||
<citerefentry><refentrytitle>malloc</refentrytitle><manvolnum>3</manvolnum></citerefentry>
|
||||
and should be freed after use with
|
||||
<citerefentry><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para>Note that this function will not work before
|
||||
<para>Note that
|
||||
<function>sd_journal_get_cursor()</function> will not
|
||||
work before
|
||||
<citerefentry><refentrytitle>sd_journal_next</refentrytitle><manvolnum>3</manvolnum></citerefentry>
|
||||
(or related call) has been called at least
|
||||
once, in order to position the read pointer at a valid entry.</para>
|
||||
(or related call) has been called at least once, in
|
||||
order to position the read pointer at a valid
|
||||
entry.</para>
|
||||
|
||||
<para><function>sd_journal_test_cursor()</function>
|
||||
may be used to check whether the current position in
|
||||
the journal matches the specified cursor. This is
|
||||
useful since cursor strings do not uniquely identify
|
||||
an entry: the same entry might be referred to by
|
||||
multiple different cursor strings, and hence string
|
||||
comparing cursors is not possible. Use this call to
|
||||
verify after an invocation of
|
||||
<citerefentry><refentrytitle>sd_journal_seek_cursor</refentrytitle><manvolnum>3</manvolnum></citerefentry>
|
||||
whether the entry being seeked to was actually found
|
||||
in the journal or the next closest entry was used
|
||||
instead.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
@ -94,15 +118,20 @@
|
||||
|
||||
<para><function>sd_journal_get_cursor()</function>
|
||||
returns 0 on success or a negative errno-style error
|
||||
code.</para>
|
||||
code. <function>sd_journal_test_cursor()</function>
|
||||
returns positive if the current entry matches the
|
||||
specified cursor, 0 if it doesn't match the specified
|
||||
cursor or a negative errno-style error code on
|
||||
failure.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Notes</title>
|
||||
|
||||
<para>The <function>sd_journal_get_cursor()</function>
|
||||
interface is available as shared library, which can be
|
||||
compiled and linked to with the
|
||||
and <function>sd_journal_test_cursor()</function>
|
||||
interfaces are available as shared library, which can
|
||||
be compiled and linked to with the
|
||||
<literal>libsystemd-journal</literal>
|
||||
<citerefentry><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
file.</para>
|
||||
|
@ -115,7 +115,12 @@
|
||||
<para><function>sd_journal_seek_cursor()</function>
|
||||
seeks to the entry located at the specified cursor
|
||||
string. For details on cursors see
|
||||
<citerefentry><refentrytitle>sd_journal_get_cursor</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para>
|
||||
<citerefentry><refentrytitle>sd_journal_get_cursor</refentrytitle><manvolnum>3</manvolnum></citerefentry>. If
|
||||
no entry matching the specified cursor is found the
|
||||
call will seek to the next closest entry (in terms of
|
||||
time) instead. To verify whether the newly selected
|
||||
entry actually matches the cursor use
|
||||
<citerefentry><refentrytitle>sd_journal_test_cursor</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para>Note that these calls do not actually make any
|
||||
entry the new current entry, this needs to be done in
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user