journalctl: add --output-fields= (#7181)
This option allows restricting the shown fields in the output modes that would normally show all fields. It allows clients that are only interested in a subset of the fields to access those more efficiently. Also, it makes the resulting size of the output more predictable. It has no effect on the various `short` output modes, because those already only show a subset of the fields.
This commit is contained in:
parent
a8caf8b251
commit
cc25a67e2a
|
@ -384,6 +384,20 @@
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--output-fields=</option></term>
|
||||||
|
|
||||||
|
<listitem><para>A comma separated list of the fields which should
|
||||||
|
be included in the output. This only has an effect for the output modes
|
||||||
|
which would normally show all fields (<option>verbose</option>,
|
||||||
|
<option>export</option>, <option>json</option>,
|
||||||
|
<option>json-pretty</option>, and <option>json-sse</option>). The
|
||||||
|
<literal>__CURSOR</literal>, <literal>__REALTIME_TIMESTAMP</literal>,
|
||||||
|
<literal>__MONOTONIC_TIMESTAMP</literal>, and
|
||||||
|
<literal>_BOOT_ID</literal> fields are always
|
||||||
|
printed.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><option>--utc</option></term>
|
<term><option>--utc</option></term>
|
||||||
|
|
||||||
|
|
|
@ -225,7 +225,7 @@ static ssize_t request_reader_entries(
|
||||||
return MHD_CONTENT_READER_END_WITH_ERROR;
|
return MHD_CONTENT_READER_END_WITH_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
|
r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL, NULL);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
log_error_errno(r, "Failed to serialize item: %m");
|
log_error_errno(r, "Failed to serialize item: %m");
|
||||||
return MHD_CONTENT_READER_END_WITH_ERROR;
|
return MHD_CONTENT_READER_END_WITH_ERROR;
|
||||||
|
|
|
@ -123,6 +123,7 @@ static const char *arg_machine = NULL;
|
||||||
static uint64_t arg_vacuum_size = 0;
|
static uint64_t arg_vacuum_size = 0;
|
||||||
static uint64_t arg_vacuum_n_files = 0;
|
static uint64_t arg_vacuum_n_files = 0;
|
||||||
static usec_t arg_vacuum_time = 0;
|
static usec_t arg_vacuum_time = 0;
|
||||||
|
static char **arg_output_fields = NULL;
|
||||||
|
|
||||||
static enum {
|
static enum {
|
||||||
ACTION_SHOW,
|
ACTION_SHOW,
|
||||||
|
@ -377,6 +378,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
ARG_VACUUM_FILES,
|
ARG_VACUUM_FILES,
|
||||||
ARG_VACUUM_TIME,
|
ARG_VACUUM_TIME,
|
||||||
ARG_NO_HOSTNAME,
|
ARG_NO_HOSTNAME,
|
||||||
|
ARG_OUTPUT_FIELDS,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct option options[] = {
|
static const struct option options[] = {
|
||||||
|
@ -435,6 +437,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
{ "vacuum-files", required_argument, NULL, ARG_VACUUM_FILES },
|
{ "vacuum-files", required_argument, NULL, ARG_VACUUM_FILES },
|
||||||
{ "vacuum-time", required_argument, NULL, ARG_VACUUM_TIME },
|
{ "vacuum-time", required_argument, NULL, ARG_VACUUM_TIME },
|
||||||
{ "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME },
|
{ "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME },
|
||||||
|
{ "output-fields", required_argument, NULL, ARG_OUTPUT_FIELDS },
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -842,6 +845,24 @@ static int parse_argv(int argc, char *argv[]) {
|
||||||
arg_action = ACTION_SYNC;
|
arg_action = ACTION_SYNC;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ARG_OUTPUT_FIELDS: {
|
||||||
|
_cleanup_strv_free_ char **v = NULL;
|
||||||
|
|
||||||
|
v = strv_split(optarg, ",");
|
||||||
|
if (!v)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
if (!arg_output_fields) {
|
||||||
|
arg_output_fields = v;
|
||||||
|
v = NULL;
|
||||||
|
} else {
|
||||||
|
r = strv_extend_strv(&arg_output_fields, v, true);
|
||||||
|
if (r < 0)
|
||||||
|
return log_oom();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case '?':
|
case '?':
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
|
@ -2452,7 +2473,7 @@ int main(int argc, char *argv[]) {
|
||||||
arg_utc * OUTPUT_UTC |
|
arg_utc * OUTPUT_UTC |
|
||||||
arg_no_hostname * OUTPUT_NO_HOSTNAME;
|
arg_no_hostname * OUTPUT_NO_HOSTNAME;
|
||||||
|
|
||||||
r = output_journal(stdout, j, arg_output, 0, flags, &ellipsized);
|
r = output_journal(stdout, j, arg_output, 0, flags, arg_output_fields, &ellipsized);
|
||||||
need_seek = true;
|
need_seek = true;
|
||||||
if (r == -EADDRNOTAVAIL)
|
if (r == -EADDRNOTAVAIL)
|
||||||
break;
|
break;
|
||||||
|
@ -2498,6 +2519,7 @@ finish:
|
||||||
strv_free(arg_syslog_identifier);
|
strv_free(arg_syslog_identifier);
|
||||||
strv_free(arg_system_units);
|
strv_free(arg_system_units);
|
||||||
strv_free(arg_user_units);
|
strv_free(arg_user_units);
|
||||||
|
strv_free(arg_output_fields);
|
||||||
|
|
||||||
free(arg_root);
|
free(arg_root);
|
||||||
free(arg_verify_key);
|
free(arg_verify_key);
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
#include "stdio-util.h"
|
#include "stdio-util.h"
|
||||||
#include "string-table.h"
|
#include "string-table.h"
|
||||||
#include "string-util.h"
|
#include "string-util.h"
|
||||||
|
#include "strv.h"
|
||||||
#include "terminal-util.h"
|
#include "terminal-util.h"
|
||||||
#include "time-util.h"
|
#include "time-util.h"
|
||||||
#include "utf8.h"
|
#include "utf8.h"
|
||||||
|
@ -136,6 +137,19 @@ static int parse_fieldv(const void *data, size_t length, const ParseFieldVec *fi
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int field_set_test(Set *fields, const char *name, size_t n) {
|
||||||
|
char *s = NULL;
|
||||||
|
|
||||||
|
if (!fields)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
s = strndupa(name, n);
|
||||||
|
if (!s)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
return set_get(fields, s) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
static bool shall_print(const char *p, size_t l, OutputFlags flags) {
|
static bool shall_print(const char *p, size_t l, OutputFlags flags) {
|
||||||
assert(p);
|
assert(p);
|
||||||
|
|
||||||
|
@ -353,7 +367,8 @@ static int output_short(
|
||||||
sd_journal *j,
|
sd_journal *j,
|
||||||
OutputMode mode,
|
OutputMode mode,
|
||||||
unsigned n_columns,
|
unsigned n_columns,
|
||||||
OutputFlags flags) {
|
OutputFlags flags,
|
||||||
|
Set *output_fields) {
|
||||||
|
|
||||||
int r;
|
int r;
|
||||||
const void *data;
|
const void *data;
|
||||||
|
@ -466,7 +481,8 @@ static int output_verbose(
|
||||||
sd_journal *j,
|
sd_journal *j,
|
||||||
OutputMode mode,
|
OutputMode mode,
|
||||||
unsigned n_columns,
|
unsigned n_columns,
|
||||||
OutputFlags flags) {
|
OutputFlags flags,
|
||||||
|
Set *output_fields) {
|
||||||
|
|
||||||
const void *data;
|
const void *data;
|
||||||
size_t length;
|
size_t length;
|
||||||
|
@ -527,6 +543,12 @@ static int output_verbose(
|
||||||
}
|
}
|
||||||
fieldlen = c - (const char*) data;
|
fieldlen = c - (const char*) data;
|
||||||
|
|
||||||
|
r = field_set_test(output_fields, data, fieldlen);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (!r)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (flags & OUTPUT_COLOR && startswith(data, "MESSAGE=")) {
|
if (flags & OUTPUT_COLOR && startswith(data, "MESSAGE=")) {
|
||||||
on = ANSI_HIGHLIGHT;
|
on = ANSI_HIGHLIGHT;
|
||||||
off = ANSI_NORMAL;
|
off = ANSI_NORMAL;
|
||||||
|
@ -564,7 +586,8 @@ static int output_export(
|
||||||
sd_journal *j,
|
sd_journal *j,
|
||||||
OutputMode mode,
|
OutputMode mode,
|
||||||
unsigned n_columns,
|
unsigned n_columns,
|
||||||
OutputFlags flags) {
|
OutputFlags flags,
|
||||||
|
Set *output_fields) {
|
||||||
|
|
||||||
sd_id128_t boot_id;
|
sd_id128_t boot_id;
|
||||||
char sid[33];
|
char sid[33];
|
||||||
|
@ -601,6 +624,7 @@ static int output_export(
|
||||||
sd_id128_to_string(boot_id, sid));
|
sd_id128_to_string(boot_id, sid));
|
||||||
|
|
||||||
JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
|
JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) {
|
||||||
|
const char *c;
|
||||||
|
|
||||||
/* We already printed the boot id, from the data in
|
/* We already printed the boot id, from the data in
|
||||||
* the header, hence let's suppress it here */
|
* the header, hence let's suppress it here */
|
||||||
|
@ -608,18 +632,23 @@ static int output_export(
|
||||||
startswith(data, "_BOOT_ID="))
|
startswith(data, "_BOOT_ID="))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
c = memchr(data, '=', length);
|
||||||
|
if (!c) {
|
||||||
|
log_error("Invalid field.");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = field_set_test(output_fields, data, c - (const char *) data);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (!r)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (utf8_is_printable_newline(data, length, false))
|
if (utf8_is_printable_newline(data, length, false))
|
||||||
fwrite(data, length, 1, f);
|
fwrite(data, length, 1, f);
|
||||||
else {
|
else {
|
||||||
const char *c;
|
|
||||||
uint64_t le64;
|
uint64_t le64;
|
||||||
|
|
||||||
c = memchr(data, '=', length);
|
|
||||||
if (!c) {
|
|
||||||
log_error("Invalid field.");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
fwrite(data, c - (const char*) data, 1, f);
|
fwrite(data, c - (const char*) data, 1, f);
|
||||||
fputc('\n', f);
|
fputc('\n', f);
|
||||||
le64 = htole64(length - (c - (const char*) data) - 1);
|
le64 = htole64(length - (c - (const char*) data) - 1);
|
||||||
|
@ -695,7 +724,8 @@ static int output_json(
|
||||||
sd_journal *j,
|
sd_journal *j,
|
||||||
OutputMode mode,
|
OutputMode mode,
|
||||||
unsigned n_columns,
|
unsigned n_columns,
|
||||||
OutputFlags flags) {
|
OutputFlags flags,
|
||||||
|
Set *output_fields) {
|
||||||
|
|
||||||
uint64_t realtime, monotonic;
|
uint64_t realtime, monotonic;
|
||||||
_cleanup_free_ char *cursor = NULL;
|
_cleanup_free_ char *cursor = NULL;
|
||||||
|
@ -814,13 +844,6 @@ static int output_json(
|
||||||
if (!eq)
|
if (!eq)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (separator) {
|
|
||||||
if (mode == OUTPUT_JSON_PRETTY)
|
|
||||||
fputs(",\n\t", f);
|
|
||||||
else
|
|
||||||
fputs(", ", f);
|
|
||||||
}
|
|
||||||
|
|
||||||
m = eq - (const char*) data;
|
m = eq - (const char*) data;
|
||||||
|
|
||||||
n = strndup(data, m);
|
n = strndup(data, m);
|
||||||
|
@ -829,6 +852,18 @@ static int output_json(
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (output_fields && !set_get(output_fields, n)) {
|
||||||
|
free(n);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (separator) {
|
||||||
|
if (mode == OUTPUT_JSON_PRETTY)
|
||||||
|
fputs(",\n\t", f);
|
||||||
|
else
|
||||||
|
fputs(", ", f);
|
||||||
|
}
|
||||||
|
|
||||||
u = PTR_TO_UINT(hashmap_get2(h, n, (void**) &kk));
|
u = PTR_TO_UINT(hashmap_get2(h, n, (void**) &kk));
|
||||||
if (u == 0) {
|
if (u == 0) {
|
||||||
/* We already printed this, let's jump to the next */
|
/* We already printed this, let's jump to the next */
|
||||||
|
@ -913,7 +948,8 @@ static int output_cat(
|
||||||
sd_journal *j,
|
sd_journal *j,
|
||||||
OutputMode mode,
|
OutputMode mode,
|
||||||
unsigned n_columns,
|
unsigned n_columns,
|
||||||
OutputFlags flags) {
|
OutputFlags flags,
|
||||||
|
Set *output_fields) {
|
||||||
|
|
||||||
const void *data;
|
const void *data;
|
||||||
size_t l;
|
size_t l;
|
||||||
|
@ -946,7 +982,8 @@ static int (*output_funcs[_OUTPUT_MODE_MAX])(
|
||||||
sd_journal*j,
|
sd_journal*j,
|
||||||
OutputMode mode,
|
OutputMode mode,
|
||||||
unsigned n_columns,
|
unsigned n_columns,
|
||||||
OutputFlags flags) = {
|
OutputFlags flags,
|
||||||
|
Set *output_fields) = {
|
||||||
|
|
||||||
[OUTPUT_SHORT] = output_short,
|
[OUTPUT_SHORT] = output_short,
|
||||||
[OUTPUT_SHORT_ISO] = output_short,
|
[OUTPUT_SHORT_ISO] = output_short,
|
||||||
|
@ -969,16 +1006,28 @@ int output_journal(
|
||||||
OutputMode mode,
|
OutputMode mode,
|
||||||
unsigned n_columns,
|
unsigned n_columns,
|
||||||
OutputFlags flags,
|
OutputFlags flags,
|
||||||
|
char **output_fields,
|
||||||
bool *ellipsized) {
|
bool *ellipsized) {
|
||||||
|
|
||||||
int ret;
|
int ret;
|
||||||
|
_cleanup_set_free_free_ Set *fields = NULL;
|
||||||
assert(mode >= 0);
|
assert(mode >= 0);
|
||||||
assert(mode < _OUTPUT_MODE_MAX);
|
assert(mode < _OUTPUT_MODE_MAX);
|
||||||
|
|
||||||
if (n_columns <= 0)
|
if (n_columns <= 0)
|
||||||
n_columns = columns();
|
n_columns = columns();
|
||||||
|
|
||||||
ret = output_funcs[mode](f, j, mode, n_columns, flags);
|
if (output_fields) {
|
||||||
|
fields = set_new(&string_hash_ops);
|
||||||
|
if (!fields)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
ret = set_put_strdupv(fields, output_fields);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = output_funcs[mode](f, j, mode, n_columns, flags, fields);
|
||||||
|
|
||||||
if (ellipsized && ret > 0)
|
if (ellipsized && ret > 0)
|
||||||
*ellipsized = true;
|
*ellipsized = true;
|
||||||
|
@ -1060,7 +1109,7 @@ static int show_journal(FILE *f,
|
||||||
line++;
|
line++;
|
||||||
maybe_print_begin_newline(f, &flags);
|
maybe_print_begin_newline(f, &flags);
|
||||||
|
|
||||||
r = output_journal(f, j, mode, n_columns, flags, ellipsized);
|
r = output_journal(f, j, mode, n_columns, flags, NULL, ellipsized);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ int output_journal(
|
||||||
OutputMode mode,
|
OutputMode mode,
|
||||||
unsigned n_columns,
|
unsigned n_columns,
|
||||||
OutputFlags flags,
|
OutputFlags flags,
|
||||||
|
char **output_fields,
|
||||||
bool *ellipsized);
|
bool *ellipsized);
|
||||||
|
|
||||||
int add_match_this_boot(sd_journal *j, const char *machine);
|
int add_match_this_boot(sd_journal *j, const char *machine);
|
||||||
|
|
|
@ -51,6 +51,18 @@ journalctl --sync
|
||||||
journalctl -b -o cat -t "$ID" >/output
|
journalctl -b -o cat -t "$ID" >/output
|
||||||
cmp /expected /output
|
cmp /expected /output
|
||||||
|
|
||||||
|
# --output-fields restricts output
|
||||||
|
ID=$(journalctl --new-id128 | sed -n 2p)
|
||||||
|
printf $'foo' | systemd-cat -t "$ID" --level-prefix false
|
||||||
|
journalctl --sync
|
||||||
|
journalctl -b -o export --output-fields=MESSAGE,FOO --output-fields=PRIORITY,MESSAGE -t "$ID" >/output
|
||||||
|
[[ `grep -c . /output` -eq 6 ]]
|
||||||
|
grep -q '^__CURSOR=' /output
|
||||||
|
grep -q '^MESSAGE=foo$' /output
|
||||||
|
grep -q '^PRIORITY=6$' /output
|
||||||
|
! grep -q '^FOO=' /output
|
||||||
|
! grep -q '^SYSLOG_FACILITY=' /output
|
||||||
|
|
||||||
# Don't lose streams on restart
|
# Don't lose streams on restart
|
||||||
systemctl start forever-print-hola
|
systemctl start forever-print-hola
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
Loading…
Reference in a new issue