Commit cbdca852 authored by Lennart Poettering's avatar Lennart Poettering
Browse files

journal: beef up journal matches considerably

we now can take multiple matches, and they will apply as AND if they
apply to different fields and OR if they apply to the same fields. Also,
terms of this kind can be combined with an overreaching OR.
parent 362a3f81
/test-journal-match
/test-journal-stream
/test-unit-name
/systemd-system-update-generator
/systemd-fstab-generator
......
......@@ -2232,29 +2232,13 @@ journalctl_LDADD = \
libsystemd-logs.la
test_journal_SOURCES = \
src/journal/test-journal.c \
src/journal/sd-journal.c \
src/journal/journal-file.c \
src/journal/lookup3.c \
src/journal/journal-send.c
src/journal/test-journal.c
test_journal_LDADD = \
libsystemd-label.la \
libsystemd-shared.la \
libsystemd-journal-internal.la \
libsystemd-id128-internal.la
if HAVE_XZ
test_journal_SOURCES += \
src/journal/compress.c
test_journal_CFLAGS = \
$(AM_CFLAGS) \
$(XZ_CFLAGS)
test_journal_LDADD += \
$(XZ_LIBS)
endif
test_journal_send_SOURCES = \
src/journal/test-journal-send.c
......@@ -2263,6 +2247,22 @@ test_journal_send_LDADD = \
libsystemd-journal-internal.la \
libsystemd-id128-internal.la
test_journal_match_SOURCES = \
src/journal/test-journal-match.c
test_journal_match_LDADD = \
libsystemd-shared.la \
libsystemd-journal-internal.la \
libsystemd-id128-internal.la
test_journal_stream_SOURCES = \
src/journal/test-journal-stream.c
test_journal_stream_LDADD = \
libsystemd-shared.la \
libsystemd-journal-internal.la \
libsystemd-id128-internal.la
libsystemd_journal_la_SOURCES = \
src/journal/sd-journal.c \
src/journal/journal-file.c \
......@@ -2327,7 +2327,9 @@ UNINSTALL_EXEC_HOOKS += \
noinst_PROGRAMS += \
test-journal \
test-journal-send
test-journal-send \
test-journal-match \
test-journal-stream
pkginclude_HEADERS += \
src/systemd/sd-journal.h \
......
......@@ -49,7 +49,7 @@
<refsynopsisdiv>
<cmdsynopsis>
<command>journalctl <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt">MATCH</arg></command>
<command>journalctl <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt" rep="repeat">MATCHES</arg></command>
</cmdsynopsis>
</refsynopsisdiv>
......@@ -66,12 +66,25 @@
contents of the journal, starting with the oldest
entry collected.</para>
<para>If a match argument is passed the output is
filtered accordingly. A match is in the format
<literal>FIELD=VALUE</literal>,
e.g. <literal>_SYSTEMD_UNIT=httpd.service</literal>. See
<para>If one or more match arguments are passed the
output is filtered accordingly. A match is in the
format <literal>FIELD=VALUE</literal>,
e.g. <literal>_SYSTEMD_UNIT=httpd.service</literal>,
referring to the components of a structured journal
entry. See
<citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>
for a list of well-known fields.</para>
for a list of well-known fields. If multiple matches
are specified matching different fields the log
entries are filtered by both, i.e. the resulting output
will show only entries matching all the specified
matches of this kind. If two matches apply to the same
field, then they are automatically matched as
alternatives, i.e. the resulting output will show
entries matching any of the specified matches for the
same field. Finally, if the character
"<literal>+</literal>" appears as separate word on the
command line all matches before and after are combined
in a disjunction (i.e. logical OR).</para>
<para>Output is interleaved from all accessible
journal files, whether they are rotated or currently
......@@ -271,6 +284,37 @@
</variablelist>
</refsect1>
<refsect1>
<title>Examples</title>
<para>Without arguments all collected logs are shown
unfiltered:</para>
<programlisting>journalctl</programlisting>
<para>With one match specified all entries with a field matching the expression are shown:</para>
<programlisting>journalctl _SYSTEMD_UNIT=avahi-daemon.service</programlisting>
<para>If two different fields are matched only entries matching both expressions at the same time are shown:</para>
<programlisting>journalctl _SYSTEMD_UNIT=avahi-daemon.service _PID=28097</programlisting>
<para>If two matches refer to the same field all entries matching either expression are shown:</para>
<programlisting>journalctl _SYSTEMD_UNIT=avahi-daemon.service _SYSTEMD_UNIT=dbus.service</programlisting>
<para>If the separator "<literal>+</literal>" is used
two expression may be combined in a logical OR. The
following will show all messages from the Avahi
service process with the PID 28097 plus all messages
from the D-Bus service (from any of its
processes):</para>
<programlisting>journalctl _SYSTEMD_UNIT=avahi-daemon.service _PID=28097 + _SYSTEMD_UNIT=dbus.service</programlisting>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
......
......@@ -299,7 +299,12 @@
addresses of journal entries are serialized into
fields prefixed with double underscores. Note that
these aren't proper fields when stored in the journal,
but addressing meta data of entries.</para>
but addressing meta data of entries. They cannot be
written as part of structured log entries via calls
such as
<citerefentry><refentrytitle>sd_journal_send</refentrytitle><manvolnum>3</manvolnum></citerefentry>. They
may also not be used as matches for
<citerefentry><refentrytitle>sd_journal_add_match</refentrytitle><manvolnum>3</manvolnum></citerefentry></para>
<variablelist>
<varlistentry>
......
......@@ -1212,8 +1212,15 @@ static int generic_array_bisect(JournalFile *f,
}
}
if (k > n)
if (k > n) {
if (direction == DIRECTION_UP) {
i = n;
subtract_one = true;
goto found;
}
return 0;
}
last_p = lp;
......@@ -1246,7 +1253,7 @@ found:
*offset = p;
if (idx)
*idx = t + i - (subtract_one ? 1 : 0);
*idx = t + i + (subtract_one ? -1 : 0);
return 1;
}
......@@ -1263,6 +1270,8 @@ static int generic_array_bisect_plus_one(JournalFile *f,
uint64_t *idx) {
int r;
bool step_back = false;
Object *o;
assert(f);
assert(test_object);
......@@ -1279,33 +1288,77 @@ static int generic_array_bisect_plus_one(JournalFile *f,
if (r == TEST_FOUND)
r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
if (r == TEST_RIGHT) {
Object *o;
r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
if (r < 0)
return r;
/* if we are looking with DIRECTION_UP then we need to first
see if in the actual array there is a matching entry, and
return the last one of that. But if there isn't any we need
to return this one. Hence remember this, and return it
below. */
if (r == TEST_LEFT)
step_back = direction == DIRECTION_UP;
if (ret)
*ret = o;
if (offset)
*offset = extra;
if (idx)
*idx = 0;
return 1;
if (r == TEST_RIGHT) {
if (direction == DIRECTION_DOWN)
goto found;
else
return 0;
}
r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, offset, idx);
if (r == 0 && step_back)
goto found;
if (r > 0 && idx)
(*idx) ++;
return r;
found:
r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
if (r < 0)
return r;
if (ret)
*ret = o;
if (offset)
*offset = extra;
if (idx)
*idx = 0;
return 1;
}
static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) {
assert(f);
assert(p > 0);
if (p == needle)
return TEST_FOUND;
else if (p < needle)
return TEST_LEFT;
else
return TEST_RIGHT;
}
int journal_file_move_to_entry_by_offset(
JournalFile *f,
uint64_t p,
direction_t direction,
Object **ret,
uint64_t *offset) {
return generic_array_bisect(f,
le64toh(f->header->entry_array_offset),
le64toh(f->header->n_entries),
p,
test_object_offset,
direction,
ret, offset, NULL);
}
static int test_object_seqnum(JournalFile *f, uint64_t p, uint64_t needle) {
Object *o;
int r;
......@@ -1407,12 +1460,13 @@ int journal_file_move_to_entry_by_monotonic(
Object *o;
int r;
sd_id128_to_string(boot_id, t + 9);
assert(f);
sd_id128_to_string(boot_id, t + 9);
r = journal_file_find_data_object(f, t, strlen(t), &o, NULL);
if (r < 0)
return r;
else if (r == 0)
if (r == 0)
return -ENOENT;
return generic_array_bisect_plus_one(f,
......@@ -1425,18 +1479,6 @@ int journal_file_move_to_entry_by_monotonic(
ret, offset, NULL);
}
static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) {
assert(f);
assert(p > 0);
if (p == needle)
return TEST_FOUND;
else if (p < needle)
return TEST_LEFT;
else
return TEST_RIGHT;
}
int journal_file_next_entry(
JournalFile *f,
Object *o, uint64_t p,
......@@ -1601,6 +1643,119 @@ int journal_file_next_entry_for_data(
ret, offset);
}
int journal_file_move_to_entry_by_offset_for_data(
JournalFile *f,
uint64_t data_offset,
uint64_t p,
direction_t direction,
Object **ret, uint64_t *offset) {
int r;
Object *d;
assert(f);
r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
if (r < 0)
return r;
return generic_array_bisect_plus_one(f,
le64toh(d->data.entry_offset),
le64toh(d->data.entry_array_offset),
le64toh(d->data.n_entries),
p,
test_object_offset,
direction,
ret, offset, NULL);
}
int journal_file_move_to_entry_by_monotonic_for_data(
JournalFile *f,
uint64_t data_offset,
sd_id128_t boot_id,
uint64_t monotonic,
direction_t direction,
Object **ret, uint64_t *offset) {
char t[9+32+1] = "_BOOT_ID=";
Object *o, *d;
int r;
uint64_t b, z;
assert(f);
/* First, seek by time */
sd_id128_to_string(boot_id, t + 9);
r = journal_file_find_data_object(f, t, strlen(t), &o, &b);
if (r < 0)
return r;
if (r == 0)
return -ENOENT;
r = generic_array_bisect_plus_one(f,
le64toh(o->data.entry_offset),
le64toh(o->data.entry_array_offset),
le64toh(o->data.n_entries),
monotonic,
test_object_monotonic,
direction,
NULL, &z, NULL);
if (r <= 0)
return r;
/* And now, continue seeking until we find an entry that
* exists in both bisection arrays */
for (;;) {
Object *qo;
uint64_t p, q;
r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
if (r < 0)
return r;
r = generic_array_bisect_plus_one(f,
le64toh(d->data.entry_offset),
le64toh(d->data.entry_array_offset),
le64toh(d->data.n_entries),
z,
test_object_offset,
direction,
NULL, &p, NULL);
if (r <= 0)
return r;
r = journal_file_move_to_object(f, OBJECT_DATA, b, &o);
if (r < 0)
return r;
r = generic_array_bisect_plus_one(f,
le64toh(o->data.entry_offset),
le64toh(o->data.entry_array_offset),
le64toh(o->data.n_entries),
p,
test_object_offset,
direction,
&qo, &q, NULL);
if (r <= 0)
return r;
if (p == q) {
if (ret)
*ret = qo;
if (offset)
*offset = q;
return 1;
}
z = q;
}
return 0;
}
int journal_file_move_to_entry_by_seqnum_for_data(
JournalFile *f,
uint64_t data_offset,
......
......@@ -43,6 +43,7 @@ enum {
WINDOW_DATA_HASH_TABLE = OBJECT_DATA_HASH_TABLE,
WINDOW_FIELD_HASH_TABLE = OBJECT_FIELD_HASH_TABLE,
WINDOW_ENTRY_ARRAY = OBJECT_ENTRY_ARRAY,
WINDOW_SIGNATURE = OBJECT_SIGNATURE,
WINDOW_HEADER,
_WINDOW_MAX
};
......@@ -106,12 +107,15 @@ int journal_file_skip_entry(JournalFile *f, Object *o, uint64_t p, int64_t skip,
int journal_file_next_entry_for_data(JournalFile *f, Object *o, uint64_t p, uint64_t data_offset, direction_t direction, Object **ret, uint64_t *offset);
int journal_file_move_to_entry_by_offset(JournalFile *f, uint64_t seqnum, direction_t direction, Object **ret, uint64_t *offset);
int journal_file_move_to_entry_by_seqnum(JournalFile *f, uint64_t seqnum, direction_t direction, Object **ret, uint64_t *offset);
int journal_file_move_to_entry_by_realtime(JournalFile *f, uint64_t realtime, direction_t direction, Object **ret, uint64_t *offset);
int journal_file_move_to_entry_by_monotonic(JournalFile *f, sd_id128_t boot_id, uint64_t monotonic, direction_t direction, Object **ret, uint64_t *offset);
int journal_file_move_to_entry_by_offset_for_data(JournalFile *f, uint64_t data_offset, uint64_t p, direction_t direction, Object **ret, uint64_t *offset);
int journal_file_move_to_entry_by_seqnum_for_data(JournalFile *f, uint64_t data_offset, uint64_t seqnum, direction_t direction, Object **ret, uint64_t *offset);
int journal_file_move_to_entry_by_realtime_for_data(JournalFile *f, uint64_t data_offset, uint64_t realtime, direction_t direction, Object **ret, uint64_t *offset);
int journal_file_move_to_entry_by_monotonic_for_data(JournalFile *f, uint64_t data_offset, sd_id128_t boot_id, uint64_t monotonic, direction_t direction, Object **ret, uint64_t *offset);
int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset);
......
......@@ -28,28 +28,46 @@
#include <systemd/sd-id128.h>
#include "journal-def.h"
#include "list.h"
#include "hashmap.h"
#include "journal-file.h"
typedef enum MatchType MatchType;
typedef enum LocationType LocationType;
typedef struct Match Match;
typedef struct Location Location;
typedef struct Directory Directory;
typedef enum location_type {
LOCATION_HEAD,
LOCATION_TAIL,
LOCATION_DISCRETE
} location_type_t;
typedef enum MatchType {
MATCH_DISCRETE,
MATCH_OR_TERM,
MATCH_AND_TERM
} MatchType;
struct Match {
MatchType type;
Match *parent;
LIST_FIELDS(Match, matches);
/* For concrete matches */
char *data;
size_t size;
le64_t le_hash;
LIST_FIELDS(Match, matches);
/* For terms */
LIST_HEAD(Match, matches);
};
typedef enum LocationType {
LOCATION_HEAD,
LOCATION_TAIL,
LOCATION_DISCRETE
} LocationType;
struct Location {
location_type_t type;
LocationType type;
uint64_t seqnum;
sd_id128_t seqnum_id;
......@@ -78,6 +96,7 @@ struct sd_journal {
Hashmap *files;
Location current_location;
JournalFile *current_file;
uint64_t current_field;
......@@ -86,10 +105,11 @@ struct sd_journal {
int inotify_fd;
LIST_HEAD(Match, matches);
unsigned n_matches;
Match *level0, *level1;
unsigned current_invalidate_counter, last_invalidate_counter;
};
char *journal_make_match_string(sd_journal *j);
#endif
......@@ -225,7 +225,9 @@ static int add_matches(sd_journal *j, char **args) {
STRV_FOREACH(i, args) {
if (path_is_absolute(*i)) {
if (streq(*i, "+"))
r = sd_journal_add_disjunction(j);
else if (path_is_absolute(*i)) {
char *p;
const char *path;
struct stat st;
......@@ -249,7 +251,7 @@ static int add_matches(sd_journal *j, char **args) {
return -ENOMEM;
}
r = sd_journal_add_match(j, t, strlen(t));
r = sd_journal_add_match(j, t, 0);
free(t);
} else {
free(p);
......@@ -259,10 +261,10 @@ static int add_matches(sd_journal *j, char **args) {
free(p);
} else
r = sd_journal_add_match(j, *i, strlen(*i));
r = sd_journal_add_match(j, *i, 0);
if (r < 0) {
log_error("Failed to add match: %s", strerror(-r));
log_error("Failed to add match '%s': %s", *i, strerror(-r));
return r;
}
}
......
This diff is collapsed.
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2012 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <stdio.h>
#include <systemd/sd-journal.h>
#include "journal-internal.h"
#include "util.h"
#include "log.h"
int main(int argc, char *argv[]) {
sd_journal *j;
char *t;
log_set_max_level(LOG_DEBUG);
assert_se(sd_journal_open(&j, 0) >= 0);
assert_se(sd_journal_add_match(j, "foobar", 0) < 0);
assert_se(sd_journal_add_match(j, "foobar=waldo", 0) < 0);
assert_se(sd_journal_add_match(j, "", 0) < 0);
assert_se(sd_journal_add_match(j, "=", 0) < 0);
assert_se(sd_journal_add_match(j, "=xxxxx", 0) < 0);
assert_se(sd_journal_add_match(j, "HALLO=WALDO", 0) >= 0);